-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
233 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { assert, IsExact } from "conditional-type-checks"; | ||
|
||
import { FormValidator } from "../types/form-validator"; | ||
|
||
import { createFormSchema } from "./create-form-schema"; | ||
import { createFormValidator } from "./create-form-validator"; | ||
|
||
describe("createFormValidator", () => { | ||
const Schema = createFormSchema( | ||
fields => ({ | ||
string: fields.string(), | ||
choice: fields.choice("A", "B", "C"), | ||
num: fields.number(), | ||
bool: fields.bool(), | ||
}), | ||
errors => errors<"err1" | "err2">() | ||
); | ||
|
||
it("resolves ok", () => { | ||
const formValidator = createFormValidator(Schema, _validate => []); | ||
|
||
type Actual = typeof formValidator; | ||
type Expected = FormValidator< | ||
{ | ||
string: string; | ||
choice: "A" | "B" | "C"; | ||
num: number | ""; | ||
bool: boolean; | ||
}, | ||
"err1" | "err2" | ||
>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
import { FormSchema } from "../types/form-schema"; | ||
import { | ||
FieldValidator, | ||
FormValidator, | ||
ValidateFn, | ||
} from "../types/form-validator"; | ||
|
||
/** | ||
* Create form validator based on provided set of validation rules. | ||
* Error type of all validation rules is specified by the FormSchema. | ||
* You can also specify validation dependencies between fields and validation triggers. | ||
* | ||
* @example | ||
* ``` | ||
* const validator = createForm.validator(Schema, validate => [ | ||
* validate({ | ||
* field: Schema.password, | ||
* rules: () => [required(), minLength(6)] | ||
* }), | ||
* validate({ | ||
* field: Schema.passwordConfirm, | ||
* dependencies: [Schema.password], | ||
* triggers: ["blur", "submit"], | ||
* rules: (password) => [ | ||
* required(), | ||
* val => val === password ? null : { code: "passwordMismatch" }, | ||
* ] | ||
* }), | ||
* validate.each({ | ||
* field: Schema.promoCodes, | ||
* rules: () => [optional(), exactLength(6)], | ||
* }) | ||
* ]) | ||
* ``` | ||
*/ | ||
export const createFormValidator = <Values extends object, Err>( | ||
_schema: FormSchema<Values, Err>, | ||
_builder: ( | ||
validate: ValidateFn | ||
) => Array<FieldValidator<unknown, Err, unknown[]>> | ||
): FormValidator<Values, Err> => { | ||
return {} as any; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { assert, IsExact } from "conditional-type-checks"; | ||
|
||
import { FieldDescriptor } from "./field-descriptor"; | ||
import { FieldValidator, ValidateFn } from "./form-validator"; | ||
|
||
const validator: ValidateFn = (() => {}) as any; | ||
|
||
describe("validateFn", () => { | ||
type Err = { code: "err1" | "err2" }; | ||
const fd1: FieldDescriptor<string, Err> = {} as any; | ||
const fd2: FieldDescriptor<number, Err> = {} as any; | ||
const fd3: FieldDescriptor<"a" | "b" | "c", Err> = {} as any; | ||
const fd4: FieldDescriptor<Date[], Err> = {} as any; | ||
const fd5: FieldDescriptor<{ parent: { child: string[] } }, Err> = {} as any; | ||
|
||
it("resolves properly for string", () => { | ||
const stringFieldValidator = validator({ | ||
field: fd1, | ||
dependencies: [fd2, fd3], | ||
rules: (_number, _choice) => [false], | ||
}); | ||
|
||
type Actual = typeof stringFieldValidator; | ||
type Expected = FieldValidator<string, Err, [number, "a" | "b" | "c"]>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
|
||
it("resolves properly for choice", () => { | ||
const choiceFieldValidator = validator({ | ||
field: fd3, | ||
dependencies: [fd1, fd4], | ||
rules: (_string, _dateArray) => [false], | ||
}); | ||
|
||
type Actual = typeof choiceFieldValidator; | ||
type Expected = FieldValidator<"a" | "b" | "c", Err, [string, Date[]]>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
|
||
it("resolves properly for array", () => { | ||
const arrayFieldValidator = validator({ | ||
field: fd4, | ||
dependencies: [fd1, fd2, fd3, fd5], | ||
rules: (_string, _number, _choice, _obj) => [false], | ||
}); | ||
|
||
type Actual = typeof arrayFieldValidator; | ||
type Expected = FieldValidator< | ||
Date[], | ||
Err, | ||
[string, number, "a" | "b" | "c", { parent: { child: string[] } }] | ||
>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
|
||
it("resolves properly for object array", () => { | ||
const objFieldValidator = validator({ | ||
field: fd5, | ||
dependencies: [fd1], | ||
rules: _string => [false], | ||
}); | ||
|
||
type Actual = typeof objFieldValidator; | ||
type Expected = FieldValidator< | ||
{ parent: { child: string[] } }, | ||
Err, | ||
[string] | ||
>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
|
||
it("resolves properly with no dependencies", () => { | ||
const fieldValidator = validator({ | ||
field: fd1, | ||
rules: () => [false], | ||
}); | ||
|
||
type Actual = typeof fieldValidator; | ||
type Expected = FieldValidator<string, Err, []>; | ||
|
||
assert<IsExact<Actual, Expected>>(true); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { Falsy } from "../../utils"; | ||
|
||
import { FieldDescriptor } from "./field-descriptor"; | ||
|
||
/** | ||
* Function responsible for validating single field. | ||
* | ||
* @param value - value to be validated of type `T` | ||
* | ||
* @returns validation error of type `Err`, or `null` when field is valid | ||
*/ | ||
export type Validator<T, Err> = Validator.Sync<T, Err>; | ||
// | Validator.Async<T, Err>; | ||
|
||
export namespace Validator { | ||
export type Sync<T, Err> = { | ||
(value: T): Err | null; | ||
}; | ||
export type Async<T, Err> = { | ||
(value: T): Promise<Err | null>; | ||
}; | ||
} | ||
|
||
export type ValidationTrigger = "change" | "blur" | "submit"; | ||
|
||
export type FormValidator<Values extends object, Err> = { | ||
validate: ( | ||
fields: Array<FieldDescriptor<unknown, Err>>, | ||
getValue: <P>(field: FieldDescriptor<P, Err>) => P, | ||
trigger?: ValidationTrigger | ||
) => Array<{ field: FieldDescriptor<unknown, Err>; error: Err | null }>; | ||
}; | ||
|
||
export type FieldValidator<T, Err, Dependencies extends any[]> = { | ||
type: "inner" | "outer"; | ||
field: FieldDescriptor<T, Err>; | ||
validators: (...deps: [...Dependencies]) => Array<Falsy | Validator<T, Err>>; | ||
triggers?: Array<ValidationTrigger>; | ||
dependencies?: readonly [...FieldDescTuple<Dependencies>]; | ||
}; | ||
|
||
export type ValidateFn = { | ||
each: ValidateEachFn; | ||
|
||
<T, Err, Dependencies extends any[]>(config: { | ||
field: FieldDescriptor<T, Err>; | ||
triggers?: ValidationTrigger[]; | ||
dependencies?: readonly [...FieldDescTuple<Dependencies>]; | ||
rules: (...deps: [...Dependencies]) => Array<Falsy | Validator<T, Err>>; | ||
}): FieldValidator<T, Err, Dependencies>; | ||
}; | ||
|
||
export type ValidateEachFn = <T, Err, Dependencies extends any[]>(config: { | ||
field: FieldDescriptor<T[], Err>; | ||
triggers?: ValidationTrigger[]; | ||
dependencies?: readonly [...FieldDescTuple<Dependencies>]; | ||
rules: (...deps: [...Dependencies]) => Array<Falsy | Validator<T, Err>>; | ||
}) => FieldValidator<T[], Err, Dependencies>; | ||
|
||
type FieldDescTuple<ValuesTuple extends readonly any[]> = { | ||
[Index in keyof ValuesTuple]: FieldDescriptor<ValuesTuple[Index]>; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters