paricafe/src/api/it.ts

614 lines
17 KiB
TypeScript
Raw Normal View History

2017-03-02 11:42:17 -06:00
/**
* it
*
*/
2017-03-02 15:11:11 -06:00
/**
* Usage Examples
2017-03-02 16:27:47 -06:00
*
2017-03-02 15:11:11 -06:00
* const [val, err] = it(x).must.be.a.string().or('asc desc').default('desc').qed();
* xは文字列でなければならず'asc''desc''desc'
2017-03-02 16:27:47 -06:00
*
2017-03-02 15:11:11 -06:00
* const [val, err] = it(x).must.be.a.number().required().range(0, 100).qed();
* xは数値でなければならず0~100
2017-03-02 16:27:47 -06:00
*
2017-03-02 15:11:11 -06:00
* const [val, err] = it(x).must.be.an.array().unique().required().validate(x => x[0] != 'strawberry pasta').qed();
* xは配列でなければならず'strawberry pasta'
2017-03-02 16:27:47 -06:00
*
2017-03-02 18:56:58 -06:00
* required default
*
2017-03-02 15:11:11 -06:00
* ~~
* const [val, err] = it(x).must.be.a.string().required().qed();
*
* const [val, err] = it(x, 'string', true);
*
2017-03-02 16:27:47 -06:00
*
2017-03-02 15:11:11 -06:00
* ~BDD風記法~
* must.be.a(n)  expect :
* const [val, err] = it(x).expect.string().required().qed();
*/
2017-03-02 18:48:06 -06:00
/**
* null undefined
*
* nullまたはundefined
*
*
* nullとundefinedを次のように区別しています:
* null ...
* undefined ...
*
* APIに次のデータを含むリクエストが来たとします:
* { name: 'Alice' }
* birthday
* name
* birthday undefined
* null(=birthdayを未設定にしたい)
* undefined null
* undefined null
*
* .require()
* () .notUndefined()
* .notNull() 使
*/
2017-03-02 06:54:46 -06:00
import * as mongo from 'mongodb';
import hasDuplicates from '../common/has-duplicates';
type Validator<T> = (value: T) => boolean | Error;
2017-03-02 14:33:47 -06:00
interface Query {
2017-03-02 11:10:27 -06:00
/**
2017-03-02 12:03:14 -06:00
* qedはQ.E.D.'QueryEnD'
2017-03-02 11:10:27 -06:00
*/
qed: () => [any, Error];
2017-03-02 06:54:46 -06:00
2017-03-02 14:33:47 -06:00
required: () => Query;
2017-03-02 06:54:46 -06:00
2017-03-02 14:33:47 -06:00
default: (value: any) => Query;
2017-03-02 11:42:17 -06:00
2017-03-02 14:33:47 -06:00
validate: (validator: Validator<any>) => Query;
2017-03-02 06:54:46 -06:00
}
2017-03-02 14:33:47 -06:00
class QueryCore implements Query {
2017-03-02 06:54:46 -06:00
value: any;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
if (value === null && !nullable) {
this.value = undefined;
this.error = new Error('must-be-not-a-null');
} else {
this.value = value;
this.error = null;
}
2017-03-02 06:54:46 -06:00
}
2017-03-02 18:15:38 -06:00
get isUndefined() {
return this.value === undefined;
}
get isNull() {
return this.value === null;
}
2017-03-02 17:56:07 -06:00
get isEmpty() {
2017-03-02 18:15:38 -06:00
return this.isUndefined || this.isNull;
2017-03-02 17:56:07 -06:00
}
2017-03-02 16:27:47 -06:00
/**
2017-03-02 17:56:07 -06:00
*
2017-03-02 16:27:47 -06:00
*/
get shouldSkip() {
2017-03-02 17:56:07 -06:00
return this.error !== null || this.isEmpty;
2017-03-02 16:27:47 -06:00
}
2017-03-02 06:54:46 -06:00
/**
2017-03-02 18:15:38 -06:00
*
2017-03-02 06:54:46 -06:00
*/
required() {
2017-03-02 17:56:07 -06:00
if (this.error === null && this.isEmpty) {
2017-03-02 06:54:46 -06:00
this.error = new Error('required');
}
return this;
}
2017-03-02 11:42:17 -06:00
/**
2017-03-02 18:15:38 -06:00
* (=undefined)
*/
notUndefined() {
if (this.error === null && this.isUndefined) {
this.error = new Error('required');
}
return this;
}
/**
* null
*/
notNull() {
if (this.error === null && this.isNull) {
this.error = new Error('required');
}
return this;
}
/**
* (=undefined)
2017-03-02 11:42:17 -06:00
*/
default(value: any) {
2017-03-02 18:15:38 -06:00
if (this.isUndefined) {
2017-03-02 11:42:17 -06:00
this.value = value;
}
return this;
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [any, Error] {
2017-03-02 06:54:46 -06:00
return [this.value, this.error];
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<any>) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
const result = validator(this.value);
if (result === false) {
this.error = new Error('invalid-format');
} else if (result instanceof Error) {
this.error = result;
}
return this;
}
}
2017-03-02 14:33:47 -06:00
class BooleanQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: boolean;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && typeof value != 'boolean') {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-a-boolean');
}
}
2017-03-02 11:42:17 -06:00
/**
*
*/
default(value: boolean) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [boolean, Error] {
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<boolean>) {
return super.validate(validator);
}
}
2017-03-02 14:33:47 -06:00
class NumberQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: number;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && !Number.isFinite(value)) {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-a-number');
}
}
/**
*
* @param min
* @param max
*/
range(min: number, max: number) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
if (this.value < min || this.value > max) {
this.error = new Error('invalid-range');
}
return this;
}
2017-03-02 11:42:17 -06:00
/**
*
* @param value
*/
min(value: number) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 11:42:17 -06:00
if (this.value < value) {
this.error = new Error('invalid-range');
}
return this;
}
/**
*
* @param value
*/
max(value: number) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 11:42:17 -06:00
if (this.value > value) {
this.error = new Error('invalid-range');
}
return this;
}
/**
*
*/
default(value: number) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [number, Error] {
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<number>) {
return super.validate(validator);
}
}
2017-03-02 14:33:47 -06:00
class StringQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: string;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && typeof value != 'string') {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-a-string');
}
}
/**
*
* @param min
* @param max
*/
range(min: number, max: number) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
if (this.value.length < min || this.value.length > max) {
this.error = new Error('invalid-range');
}
return this;
}
trim() {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
this.value = this.value.trim();
return this;
}
2017-03-02 11:42:17 -06:00
/**
*
*/
default(value: string) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [string, Error] {
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<string>) {
return super.validate(validator);
}
2017-03-02 14:52:12 -06:00
/**
*
*
* @param pattern
*/
or(pattern: string | string[]) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 14:52:12 -06:00
if (typeof pattern == 'string') pattern = pattern.split(' ');
const match = pattern.some(x => x === this.value);
if (!match) this.error = new Error('not-match-pattern');
return this;
}
/**
*
*
* @param pattern
*/
match(pattern: RegExp) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 14:52:12 -06:00
if (!pattern.test(this.value)) this.error = new Error('not-match-pattern');
return this;
}
2017-03-02 06:54:46 -06:00
}
2017-03-02 14:33:47 -06:00
class ArrayQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: any[];
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && !Array.isArray(value)) {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-an-array');
}
}
/**
* (=)
*/
unique() {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
if (hasDuplicates(this.value)) {
this.error = new Error('must-be-unique');
}
return this;
}
/**
*
* @param min
* @param max
*/
range(min: number, max: number) {
2017-03-02 16:27:47 -06:00
if (this.shouldSkip) return this;
2017-03-02 06:54:46 -06:00
if (this.value.length < min || this.value.length > max) {
this.error = new Error('invalid-range');
}
return this;
}
2017-03-02 17:56:07 -06:00
/**
*
*
*/
allString() {
if (this.shouldSkip) return this;
if (this.value.some(x => typeof x != 'string')) {
this.error = new Error('dirty-array');
}
return this;
}
2017-03-02 11:42:17 -06:00
/**
*
*/
default(value: any[]) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [any[], Error] {
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<any[]>) {
return super.validate(validator);
}
}
2017-03-02 14:33:47 -06:00
class IdQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: mongo.ObjectID;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && (typeof value != 'string' || !mongo.ObjectID.isValid(value))) {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-an-id');
}
}
2017-03-02 11:42:17 -06:00
/**
*
*/
default(value: mongo.ObjectID) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 17:24:48 -06:00
qed(): [mongo.ObjectID, Error] {
2017-03-02 11:10:27 -06:00
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
2017-03-02 17:24:48 -06:00
validate(validator: Validator<mongo.ObjectID>) {
2017-03-02 06:54:46 -06:00
return super.validate(validator);
}
}
2017-03-02 14:33:47 -06:00
class ObjectQuery extends QueryCore {
2017-03-02 06:54:46 -06:00
value: any;
error: Error;
2017-03-02 19:24:56 -06:00
constructor(value: any, nullable: boolean = false) {
super(value, nullable);
2017-03-02 17:56:07 -06:00
if (!this.isEmpty && typeof value != 'object') {
2017-03-02 06:54:46 -06:00
this.error = new Error('must-be-an-object');
}
}
2017-03-02 11:42:17 -06:00
/**
*
*/
default(value: any) {
return super.default(value);
}
2017-03-02 06:54:46 -06:00
/**
*
*/
2017-03-02 11:10:27 -06:00
qed(): [any, Error] {
return super.qed();
2017-03-02 06:54:46 -06:00
}
/**
*
* false
* @param validator
*/
validate(validator: Validator<any>) {
return super.validate(validator);
}
}
2017-03-02 11:10:27 -06:00
type It = {
2017-03-02 06:54:46 -06:00
must: {
be: {
a: {
2017-03-02 14:33:47 -06:00
string: () => StringQuery;
number: () => NumberQuery;
boolean: () => BooleanQuery;
2017-03-02 19:24:56 -06:00
nullable: {
string: () => StringQuery;
number: () => NumberQuery;
boolean: () => BooleanQuery;
id: () => IdQuery;
array: () => ArrayQuery;
object: () => ObjectQuery;
};
2017-03-02 06:54:46 -06:00
};
an: {
2017-03-02 14:33:47 -06:00
id: () => IdQuery;
array: () => ArrayQuery;
object: () => ObjectQuery;
2017-03-02 06:54:46 -06:00
};
};
};
2017-03-02 11:10:27 -06:00
expect: {
2017-03-02 14:33:47 -06:00
string: () => StringQuery;
number: () => NumberQuery;
boolean: () => BooleanQuery;
id: () => IdQuery;
array: () => ArrayQuery;
object: () => ObjectQuery;
2017-03-02 19:24:56 -06:00
nullable: {
string: () => StringQuery;
number: () => NumberQuery;
boolean: () => BooleanQuery;
id: () => IdQuery;
array: () => ArrayQuery;
object: () => ObjectQuery;
};
2017-03-02 11:10:27 -06:00
};
2017-03-02 06:54:46 -06:00
};
2017-03-02 11:10:27 -06:00
const it = (value: any) => ({
2017-03-02 06:54:46 -06:00
must: {
be: {
a: {
2017-03-02 14:33:47 -06:00
string: () => new StringQuery(value),
number: () => new NumberQuery(value),
2017-03-02 19:24:56 -06:00
boolean: () => new BooleanQuery(value),
nullable: {
string: () => new StringQuery(value, true),
number: () => new NumberQuery(value, true),
boolean: () => new BooleanQuery(value, true),
id: () => new IdQuery(value, true),
array: () => new ArrayQuery(value, true),
object: () => new ObjectQuery(value, true)
}
2017-03-02 06:54:46 -06:00
},
an: {
2017-03-02 14:33:47 -06:00
id: () => new IdQuery(value),
array: () => new ArrayQuery(value),
object: () => new ObjectQuery(value)
2017-03-02 06:54:46 -06:00
}
}
2017-03-02 11:10:27 -06:00
},
expect: {
2017-03-02 14:33:47 -06:00
string: () => new StringQuery(value),
number: () => new NumberQuery(value),
boolean: () => new BooleanQuery(value),
id: () => new IdQuery(value),
array: () => new ArrayQuery(value),
2017-03-02 19:24:56 -06:00
object: () => new ObjectQuery(value),
nullable: {
string: () => new StringQuery(value, true),
number: () => new NumberQuery(value, true),
boolean: () => new BooleanQuery(value, true),
id: () => new IdQuery(value, true),
array: () => new ArrayQuery(value, true),
object: () => new ObjectQuery(value, true)
}
2017-03-02 06:54:46 -06:00
}
});
type Type = 'id' | 'string' | 'number' | 'boolean' | 'array' | 'set' | 'object';
2017-03-02 11:10:27 -06:00
function x(value: any): It;
function x(value: any, type: 'id', isRequired?: boolean, validator?: Validator<mongo.ObjectID> | Validator<mongo.ObjectID>[]): [mongo.ObjectID, Error];
function x(value: any, type: 'string', isRequired?: boolean, validator?: Validator<string> | Validator<string>[]): [string, Error];
function x(value: any, type: 'number', isRequired?: boolean, validator?: Validator<number> | Validator<number>[]): [number, Error];
function x(value: any, type: 'boolean', isRequired?: boolean): [boolean, Error];
function x(value: any, type: 'array', isRequired?: boolean, validator?: Validator<any[]> | Validator<any[]>[]): [any[], Error];
function x(value: any, type: 'set', isRequired?: boolean, validator?: Validator<any[]> | Validator<any[]>[]): [any[], Error];
function x(value: any, type: 'object', isRequired?: boolean, validator?: Validator<any> | Validator<any>[]): [any, Error];
function x(value: any, type?: Type, isRequired?: boolean, validator?: Validator<any> | Validator<any>[]): any {
if (typeof type === 'undefined') return it(value);
2017-03-02 06:54:46 -06:00
2017-03-02 14:33:47 -06:00
let q: Query = null;
2017-03-02 06:54:46 -06:00
2017-03-02 11:10:27 -06:00
switch (type) {
2017-03-02 14:33:47 -06:00
case 'id': q = it(value).expect.id(); break;
case 'string': q = it(value).expect.string(); break;
case 'number': q = it(value).expect.number(); break;
case 'boolean': q = it(value).expect.boolean(); break;
case 'array': q = it(value).expect.array(); break;
case 'set': q = it(value).expect.array().unique(); break;
case 'object': q = it(value).expect.object(); break;
2017-03-02 06:54:46 -06:00
}
2017-03-02 14:33:47 -06:00
if (isRequired) q = q.required();
2017-03-02 06:54:46 -06:00
2017-03-02 11:10:27 -06:00
if (validator) {
(Array.isArray(validator) ? validator : [validator])
2017-03-02 14:33:47 -06:00
.forEach(v => q = q.validate(v));
2017-03-02 06:54:46 -06:00
}
2017-03-02 14:33:47 -06:00
return q;
2017-03-02 06:54:46 -06:00
}
2017-03-02 11:10:27 -06:00
export default x;