Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use a slightly more explicit api surface to define validations without the need for explicit lists #30

Merged
merged 1 commit into from
Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions example/Validations.res
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,33 @@ module Label = {
type t = [#required | #email | #equals]
}

let required = {
Description.names: list{#required},
validator: ({label, value}) =>
let required = (
#name(#required),
({Validator.Args.label: label, value}) =>
switch value {
| "" => #error(#error(("required", label)))
| _ => #ok(value)
},
}
)

let emailRegEx = %re("/.+@.+/")

let email = {
Description.names: list{#email},
validator: ({label, value}) =>
let email = (
#name(#email),
({Validator.Args.label: label, value}) =>
if emailRegEx->Js.Re.test_(value) {
#ok(value)
} else {
#error(#error(("email", label)))
},
}
)

let equals = lens => {
Description.names: list{#equals},
validator: ({label, value, values}) =>
let equals = lens => (
#name(#equals),
({Validator.Args.label: label, value, values}) =>
if lens.Optic.Lens.get(values) == value {
#ok(value)
} else {
#error(#error(("equals", label)))
},
}
)
6 changes: 2 additions & 4 deletions src/Formidable.res
Original file line number Diff line number Diff line change
Expand Up @@ -316,16 +316,14 @@ module Make = (ValidationLabel: Type, Error: Type, Values: Values): (

let validationNames =
validations->Option.mapWithDefault([], validations =>
validations->ArrayExtra.flatMap(validation =>
validation->Validations.getNames->List.toArray
)
validations->ArrayExtra.flatMap(Validations.getNames)
)

let hasValidation = name => validationNames->Js.Array2.includes(name)

let validate = React.useCallback1((validationContext, values) =>
validations->Option.mapWithDefault(#valid, validations =>
switch validations->Array.keepMap(((strategy, {validator})) =>
switch validations->Array.keepMap(((strategy, (_, validator))) =>
if Validations.shouldValidate(~context=validationContext, ~strategy) {
switch validator({
Validations.Validator.Args.label: errorLabel->OptionExtra.or(label),
Expand Down
30 changes: 17 additions & 13 deletions src/FormidableValidations.res
Original file line number Diff line number Diff line change
Expand Up @@ -27,34 +27,38 @@ module Validator = {
}

module Description = {
type t<'values, 'value, 'error, 'label> = {
names: list<'label>,
validator: Validator.t<'values, 'value, 'error>,
}
@ocaml.doc(`Only the first "kind" is meant to be used by an application.
The second one is used only internally when composing validations`)
type kind<'label> = [#name('label) | #names(array<'label>)]

let resolveKind = (kind: kind<'label>) =>
switch kind {
| #name(label) => [label]
| #names(labels) => labels
}

type t<'values, 'value, 'error, 'label> = (kind<'label>, Validator.t<'values, 'value, 'error>)
}

type t<'values, 'value, 'error, 'label> = (
Strategy.t,
Description.t<'values, 'value, 'error, 'label>,
)

let compose = (
{Description.names: names, validator},
{Description.names: names', validator: validator'},
) => {
Description.names: names->List.concat(names'),
validator: field =>
let compose = ((names, validator), (names', validator')) => (
names->Description.resolveKind->Js.Array2.concat(names'->Description.resolveKind)->#names,
field =>
switch validator(field) {
| #ok(_) => validator'(field)
| #error(_) as error => error
},
}
)

let getStrategy = ((strategy, _)) => strategy

let getNames = ((_, {Description.names: names})) => names
let getNames = ((_, (names, _))) => names->Description.resolveKind

let getValidator = ((_, {Description.validator: validator})) => validator
let getValidator = ((_, (_, validator))) => validator
Copy link
Contributor

@infothien infothien Jan 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we will use tuple in the end and not convert to record @gaku-sei -san?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah I see just read the description ! sorry :P

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only for this reason:

No more a record, since we have #name it's probably explicit enough, and it prevents this: names: #name(#required) which is a bit weird

It's really weird to define a validation with names: name 😕

But I don't mind to bring them back ^^

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you are using kind like above, I think it is clear enough :D. And this one looks simpler than 22 so probably good with it


@ocaml.doc(`Returns true if a validation should be performed in the given context.
If no context is provided, always returns true`)
Expand Down