From e08947657cca2b9b78d53f316e9a7da8af162beb Mon Sep 17 00:00:00 2001 From: Kevin COMBRIAT Date: Fri, 15 Jan 2021 12:08:54 +0900 Subject: [PATCH] refactor(validation): Use a slightly more explicit api surface to define validations without the need for explicit lists --- example/Validations.res | 24 ++++++++++++------------ src/Formidable.res | 6 ++---- src/FormidableValidations.res | 30 +++++++++++++++++------------- 3 files changed, 31 insertions(+), 29 deletions(-) diff --git a/example/Validations.res b/example/Validations.res index 56b7a7a..ad05cab 100644 --- a/example/Validations.res +++ b/example/Validations.res @@ -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))) }, -} +) diff --git a/src/Formidable.res b/src/Formidable.res index f200b75..418d756 100644 --- a/src/Formidable.res +++ b/src/Formidable.res @@ -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), diff --git a/src/FormidableValidations.res b/src/FormidableValidations.res index 506c53d..c6b82b2 100644 --- a/src/FormidableValidations.res +++ b/src/FormidableValidations.res @@ -27,10 +27,17 @@ 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> = ( @@ -38,23 +45,20 @@ type t<'values, 'value, 'error, 'label> = ( 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 @ocaml.doc(`Returns true if a validation should be performed in the given context. If no context is provided, always returns true`)