diff --git a/src/api/validator2.ts b/src/api/validator2.ts index ed58f445d..04dff59aa 100644 --- a/src/api/validator2.ts +++ b/src/api/validator2.ts @@ -1,17 +1,20 @@ import * as mongo from 'mongodb'; import hasDuplicates from '../common/has-duplicates'; -type CustomValidator = (value: T) => boolean | string; +type Validator = (value: T) => boolean | string; +type Modifier = (value: T) => T; -interface Validator { +interface Fuctory { get: () => [any, string]; - required: () => Validator; + required: () => Fuctory; - validate: (validator: CustomValidator) => Validator; + validate: (validator: Validator) => Fuctory; + + modify: (modifier: Modifier) => Fuctory; } -class ValidatorCore implements Validator { +class FuctoryCore implements Fuctory { value: any; error: string; @@ -20,6 +23,9 @@ class ValidatorCore implements Validator { this.error = null; } + /** + * この値が undefined または null の場合エラーにします + */ required() { if (this.error === null && this.value === null) { this.error = 'required'; @@ -27,11 +33,19 @@ class ValidatorCore implements Validator { return this; } + /** + * このインスタンスの値およびエラーを取得します + */ get(): [any, string] { return [this.value, this.error]; } - validate(validator: CustomValidator) { + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { if (this.error || this.value === null) return this; const result = validator(this.value); if (result === false) { @@ -41,9 +55,59 @@ class ValidatorCore implements Validator { } return this; } + + modify(modifier: Modifier) { + if (this.error || this.value === null) return this; + try { + this.value = modifier(this.value); + } catch (e) { + this.error = e; + } + return this; + } } -class NumberValidator extends ValidatorCore { +class BooleanFuctory extends FuctoryCore { + value: boolean; + error: string; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'boolean') { + this.error = 'must-be-a-boolean'; + } else { + this.value = value; + } + } + + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [boolean, string] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class NumberFuctory extends FuctoryCore { value: number; error: string; @@ -58,6 +122,11 @@ class NumberValidator extends ValidatorCore { } } + /** + * 値が指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ range(min: number, max: number) { if (this.error || this.value === null) return this; if (this.value < min || this.value > max) { @@ -70,28 +139,242 @@ class NumberValidator extends ValidatorCore { return super.required(); } + /** + * このインスタンスの値およびエラーを取得します + */ get(): [number, string] { return super.get(); } - validate(validator: CustomValidator) { + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { return super.validate(validator); } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class StringFuctory extends FuctoryCore { + value: string; + error: string; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'string') { + this.error = 'must-be-a-string'; + } else { + this.value = value; + } + } + + /** + * 文字数が指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ + range(min: number, max: number) { + if (this.error || this.value === null) return this; + if (this.value.length < min || this.value.length > max) { + this.error = 'invalid-range'; + } + return this; + } + + trim() { + if (this.error || this.value === null) return this; + this.value = this.value.trim(); + return this; + } + + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [string, string] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class ArrayFuctory extends FuctoryCore { + value: any[]; + error: string; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (!Array.isArray(value)) { + this.error = 'must-be-an-array'; + } else { + this.value = value; + } + } + + /** + * 配列の値がユニークでない場合(=重複した項目がある場合)エラーにします + */ + unique() { + if (this.error || this.value === null) return this; + if (hasDuplicates(this.value)) { + this.error = 'must-be-unique'; + } + return this; + } + + /** + * 配列の長さが指定された範囲内にない場合エラーにします + * @param min 下限 + * @param max 上限 + */ + range(min: number, max: number) { + if (this.error || this.value === null) return this; + if (this.value.length < min || this.value.length > max) { + this.error = 'invalid-range'; + } + return this; + } + + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any[], string] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class IdFuctory extends FuctoryCore { + value: mongo.ObjectID; + error: string; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'string' || !mongo.ObjectID.isValid(value)) { + this.error = 'must-be-an-id'; + } else { + this.value = new mongo.ObjectID(value); + } + } + + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any[], string] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } +} + +class ObjectFuctory extends FuctoryCore { + value: any; + error: string; + + constructor(value) { + super(); + if (value === undefined || value === null) { + this.value = null; + } else if (typeof value != 'object') { + this.error = 'must-be-an-object'; + } else { + this.value = value; + } + } + + required() { + return super.required(); + } + + /** + * このインスタンスの値およびエラーを取得します + */ + get(): [any, string] { + return super.get(); + } + + /** + * このインスタンスの値に対して妥当性を検証します + * バリデータが false または(エラーを表す)文字列を返した場合エラーにします + * @param validator バリデータ + */ + validate(validator: Validator) { + return super.validate(validator); + } + + modify(modifier: Modifier) { + return super.modify(modifier); + } } const it = (value: any) => ({ must: { be: { a: { - string: 0, - number: () => new NumberValidator(value), - boolean: 0, - set: 0 + string: () => new StringFuctory(value), + number: () => new NumberFuctory(value), + boolean: () => new BooleanFuctory(value) }, an: { - id: 0, - array: 0, - object: 0 + id: () => new IdFuctory(value), + array: () => new ArrayFuctory(value), + object: () => new ObjectFuctory(value) } } }