Skip to content

Commit

Permalink
refactor(validations): Use functor pattern to clean the validations
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin COMBRIAT committed Jan 14, 2021
1 parent 776683f commit 1ea2a87
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 41 deletions.
2 changes: 2 additions & 0 deletions example/App.res
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ let makeForm = Formidable.make(~values=module(Values), ~error=module(I18n.Error)
// In many cases, it's not needed, especially thanks to the Consumer component
module Form = unpack(makeForm(~onSubmit=_values => (), ~onSubmitError=(_values, _errors) => (), ()))

module Validations = Validations.Make(Values)

open Validations

// Validations can be defined in an other module, and re-used easily
Expand Down
60 changes: 33 additions & 27 deletions example/Validations.res
Original file line number Diff line number Diff line change
@@ -1,32 +1,38 @@
include Formidable.Validations

let required = {
Description.names: list{"required"},
validator: ({label, value}) =>
switch value {
| "" => #error(#error(("required", label)))
| _ => #ok(value)
},
module type Values = {
type t
}

let emailRegEx = %re("/.+@.+/")
module Make = (Values: Values) => {
include Formidable.Validations

let email = {
Description.names: list{"email"},
validator: ({label, value}) =>
if emailRegEx->Js.Re.test_(value) {
#ok(value)
} else {
#error(#error(("email", label)))
},
}
let required: Description.t<Values.t, _, _> = {
names: ["required"],
validator: ({label, value}) =>
switch value {
| "" => #error(#error(("required", label)))
| _ => #ok(value)
},
}

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

let email: Description.t<Values.t, _, _> = {
names: ["email"],
validator: ({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}) =>
if lens.Optic.Lens.get(values) == value {
#ok(value)
} else {
#error(#error(("equals", label)))
},
let equals = lens => {
Description.names: ["equals"],
validator: ({label, value, values}) =>
if lens.Optic.Lens.get(values) == value {
#ok(value)
} else {
#error(#error(("equals", label)))
},
}
}
4 changes: 1 addition & 3 deletions src/Formidable.res
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,7 @@ module Make = (

let validationNames =
props["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)
Expand Down
20 changes: 9 additions & 11 deletions src/FormidableValidations.res
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
/* * Strategy used during validation, on change, on blur, or on demand.
@ocaml.doc(`Strategy used during validation, on change, on blur, or on demand.
* On demand validation will not be triggered automatically, and you need
* to use the validate function manually.
*/
* to use the validate function manually.`)
module Strategy = {
type t = [#onBlur | #onChange | #onDemand]
}

/* * The value returned by a validator, ok or error */
@ocaml.doc(`The value returned by a validator, ok or error`)
module Value = {
type t<'value, 'error> = [#ok('value) | #error('error)]
}

/* * The validator is the function that performs the validation */
@ocaml.doc(`The validator is the function that performs the validation`)
module Validator = {
/* * The arguments provided to the validator function */
@ocaml.doc(`The arguments provided to the validator function`)
module Args = {
type t<'values, 'value, 'error> = {
label: option<string>,
Expand All @@ -29,7 +28,7 @@ module Validator = {

module Description = {
type t<'values, 'value, 'error> = {
names: list<string>,
names: array<string>,
validator: Validator.t<'values, 'value, 'error>,
}
}
Expand All @@ -44,7 +43,7 @@ let compose = (
{Description.names: names, validator},
{Description.names: names', validator: validator'},
) => {
Description.names: names->List.concat(names'),
Description.names: names->Js.Array2.concat(names'),
validator: field =>
switch validator(field) {
| #ok(_) => validator'(field)
Expand All @@ -58,9 +57,8 @@ let getNames = ((_, {Description.names: names})) => names

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

/* * Returns true if a validation should be performed in the given context.
If no context is provided, always returns true */

@ocaml.doc(`Returns true if a validation should be performed in the given context.
If no context is provided, always returns true`)
let shouldValidate = (~context, ~strategy) =>
switch (context, strategy) {
| (None, _)
Expand Down

0 comments on commit 1ea2a87

Please sign in to comment.