-
Notifications
You must be signed in to change notification settings - Fork 19
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
14 changed files
with
328 additions
and
14 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
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
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
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,89 @@ | ||
import type { ValidatorType } from "../shared/schemas"; | ||
export type { ValidatorType }; | ||
|
||
type StringValidatorType = { type: "string" }; | ||
type NumberValidatorType = { type: "number" }; | ||
type BooleanValidatorType = { type: "boolean" }; | ||
|
||
export const string = () => ({ type: "string" }) as StringValidatorType; | ||
export const number = () => ({ type: "number" }) as NumberValidatorType; | ||
export const boolean = () => ({ type: "boolean" }) as BooleanValidatorType; | ||
|
||
export const optional = < | ||
T extends StringValidatorType | NumberValidatorType | BooleanValidatorType, | ||
>( | ||
type: T | ||
) => ({ ...type, optional: true }) as const; | ||
|
||
export type Infer<T> = T extends StringValidatorType | ||
? T extends { optional: true } | ||
? string | null | undefined | ||
: string | ||
: T extends NumberValidatorType | ||
? T extends { optional: true } | ||
? number | null | undefined | ||
: number | ||
: T extends BooleanValidatorType | ||
? T extends { optional: true } | ||
? boolean | null | undefined | ||
: boolean | ||
: never; | ||
|
||
type Errors = Array<{ | ||
property: string; | ||
message: string; | ||
}>; | ||
|
||
export function validate<T extends Record<string, ValidatorType>>( | ||
userValidator: T, | ||
user: Record<string, unknown> | ||
): | ||
| { | ||
errors: Errors; | ||
value?: never; | ||
} | ||
| { | ||
errors?: never; | ||
value: { | ||
[key in keyof T]: Infer<T[keyof T]>; | ||
}; | ||
} { | ||
const returnObject: { | ||
[key in keyof T]: Infer<T[keyof T]>; | ||
} = {} as any; | ||
const errors: Errors = []; | ||
for (const key in userValidator) { | ||
const validator = userValidator[key]; | ||
const value = user[key]; | ||
|
||
if (value === undefined && "optional" in validator) { | ||
continue; | ||
} | ||
|
||
if (validator.type === "string" && typeof value !== "string") { | ||
errors.push({ | ||
property: key, | ||
message: `Expected string but got ${typeof value}`, | ||
}); | ||
} | ||
|
||
if (validator.type === "number" && typeof value !== "number") { | ||
errors.push({ | ||
property: key, | ||
message: `Expected number but got ${typeof value}`, | ||
}); | ||
} | ||
|
||
if (validator.type === "boolean" && typeof value !== "boolean") { | ||
errors.push({ | ||
property: key, | ||
message: `Expected boolean but got ${typeof value}`, | ||
}); | ||
} | ||
returnObject[key] = value as Infer<typeof validator>; | ||
} | ||
if (errors.length > 0) { | ||
return { errors }; | ||
} | ||
return { value: returnObject }; | ||
} |
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
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,90 @@ | ||
import * as validation from "../src/validation"; | ||
|
||
describe("validation", () => { | ||
it("produces proper types", () => { | ||
const booleanValidation = validation.boolean(); | ||
expectTypeOf< | ||
validation.Infer<typeof booleanValidation> | ||
>().toEqualTypeOf<boolean>(); | ||
|
||
const stringValidation = validation.string(); | ||
expectTypeOf< | ||
validation.Infer<typeof stringValidation> | ||
>().toEqualTypeOf<string>(); | ||
|
||
const numberValidation = validation.number(); | ||
expectTypeOf< | ||
validation.Infer<typeof numberValidation> | ||
>().toEqualTypeOf<number>(); | ||
|
||
const optionalNumberValidation = validation.optional(validation.number()); | ||
|
||
expectTypeOf< | ||
validation.Infer<typeof optionalNumberValidation> | ||
>().toEqualTypeOf<number | undefined | null>(); | ||
|
||
const optionalStringValidation = validation.optional(validation.string()); | ||
|
||
expectTypeOf< | ||
validation.Infer<typeof optionalStringValidation> | ||
>().toEqualTypeOf<string | undefined | null>(); | ||
|
||
const optionalBooleanValidation = validation.optional(validation.boolean()); | ||
|
||
expectTypeOf< | ||
validation.Infer<typeof optionalBooleanValidation> | ||
>().toEqualTypeOf<boolean | undefined | null>(); | ||
}); | ||
|
||
it("validates properly", () => { | ||
const userValidator = { | ||
name: validation.string(), | ||
age: validation.number(), | ||
isDeveloper: validation.boolean(), | ||
}; | ||
const user = { | ||
name: "John", | ||
age: 25, | ||
isDeveloper: true, | ||
}; | ||
expect(validation.validate(userValidator, user)).toEqual({ | ||
value: user, | ||
}); | ||
}); | ||
|
||
it("validates properly with optional fields", () => { | ||
const userValidator = { | ||
name: validation.string(), | ||
age: validation.optional(validation.number()), | ||
isDeveloper: validation.boolean(), | ||
}; | ||
const user = { | ||
name: "John", | ||
isDeveloper: true, | ||
}; | ||
expect(validation.validate(userValidator, user)).toEqual({ | ||
value: user, | ||
}); | ||
}); | ||
|
||
it("shows errors properly", () => { | ||
const userValidator = { | ||
name: validation.string(), | ||
age: validation.number(), | ||
isDeveloper: validation.boolean(), | ||
}; | ||
const user = { | ||
name: "John", | ||
age: "25", | ||
isDeveloper: true, | ||
}; | ||
expect(validation.validate(userValidator, user)).toEqual({ | ||
errors: [ | ||
{ | ||
property: "age", | ||
message: "Expected number but got string", | ||
}, | ||
], | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.