Skip to content

Commit

Permalink
Merge pull request #19 from harrygr/flexible-data
Browse files Browse the repository at this point in the history
feat: Allow any data to be validated by a given validator
  • Loading branch information
harrygr authored Nov 27, 2020
2 parents 46c2bde + dcb1758 commit d53ad0b
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 32 deletions.
24 changes: 20 additions & 4 deletions src/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { makeValidator, Constraint } from './'
import { makeValidator } from './'

const equals = <T>(req: T, val: T) =>
val !== req ? 'Should be equal' : undefined
Expand Down Expand Up @@ -43,10 +43,10 @@ describe('Basic validator usage', () => {
}

it('validates a field being equal to another', () => {
const validatePasswords: Constraint<PasswordFields> = (val, fields) =>
const validatePasswords = (val: any, fields: PasswordFields) =>
equals(fields.passwordConfirm, val)

const passwordValidator = makeValidator<PasswordFields>({
const passwordValidator = makeValidator({
password: [required, validatePasswords],
})

Expand All @@ -64,7 +64,7 @@ describe('Basic validator usage', () => {
}

it('does not include the keys of passing fields in the validation result', () => {
const validatePerson = makeValidator<PersonFields>({
const validatePerson = makeValidator({
name: [required],
age: [required],
})
Expand All @@ -78,4 +78,20 @@ describe('Basic validator usage', () => {

expect(result).not.toHaveProperty('name')
})

it('allows any data to be validated', () => {
const validatePerson = makeValidator({
name: [required],
age: [required],
})

const notAPerson = {
colour: 'brown',
legs: 4,
}

const result = validatePerson(notAPerson)

expect(result).toEqual({ name: 'Required', age: 'Required' })
})
})
45 changes: 17 additions & 28 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,27 @@
export type Constraint<F extends object, T = any> = (
val: T,
fields: F,
) => string | undefined
export type Constraints<F extends object> = Partial<
Record<keyof F, Constraint<F>[]>
>
export type ValidationResult<F extends object> = Partial<
Record<keyof F, string | undefined>
export type Rule<D extends object> = (val: any, fields: D) => string | undefined

export type RuleSet<R extends object, D extends object> = Record<
keyof R,
Rule<D>[]
>

export const makeValidator = <F extends object>(
constraints: Constraints<F>,
export const makeValidator = <D extends object, R extends object = Partial<D>>(
ruleSet: RuleSet<R, D>,
) => {
return (fields: F): ValidationResult<F> => {
const fieldNames = Object.keys(fields) as (keyof F)[]
const fieldnamesToValidate = Object.keys(ruleSet)

const getErrors = (
errors: ValidationResult<F>,
fieldName: keyof F,
): ValidationResult<F> => {
const val = fields[fieldName]
const fieldConstraints = (constraints[fieldName] || []) as Constraint<
F,
typeof val
>[]
return (data: D) => {
return fieldnamesToValidate.reduce((acc, field) => {
const rules = ruleSet[field] as Rule<D>[]

const error = fieldConstraints.reduce(
(err, con) => (err ? err : con(val, fields)),
const valueToValidate = data[field]

const error = rules.reduce(
(err, v) => (err ? err : v(valueToValidate, data)),
undefined,
)

return error ? { ...errors, [fieldName]: error } : errors
}

return fieldNames.reduce(getErrors, {} as ValidationResult<F>)
return error ? { ...acc, [field]: error } : acc
}, {} as Record<keyof R, string>)
}
}

0 comments on commit d53ad0b

Please sign in to comment.