From f423e53090e3c90738459ce6f4315255d1db2e63 Mon Sep 17 00:00:00 2001 From: Victor Trofin Date: Wed, 10 Aug 2022 13:58:34 +0900 Subject: [PATCH 1/2] add union type for the result of a validation handler --- example/Example.res | 8 ++++++-- src/Validation.res | 4 ++++ src/Validation.resi | 8 ++++++-- src/ValidationResult.res | 5 +++++ src/ValidationResult.resi | 8 ++++++++ 5 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 src/ValidationResult.res create mode 100644 src/ValidationResult.resi diff --git a/example/Example.res b/example/Example.res index 86f83f9..0560b16 100644 --- a/example/Example.res +++ b/example/Example.res @@ -52,13 +52,17 @@ module Form = { ( "validEmail", Validation.sync(value => - value->ReCode.Decode.string->Belt.Result.getWithDefault("")->String.contains('@') + ValidationResult.boolResult( + value->ReCode.Decode.string->Belt.Result.getWithDefault("")->String.contains('@'), + ) ), ), ( "validLength", Validation.sync(value => - value->ReCode.Decode.string->Belt.Result.getWithDefault("")->String.length >= 8 + ValidationResult.boolResult( + value->ReCode.Decode.string->Belt.Result.getWithDefault("")->String.length >= 8, + ) ), ), ]), diff --git a/src/Validation.res b/src/Validation.res index 133b385..b1ebb17 100644 --- a/src/Validation.res +++ b/src/Validation.res @@ -3,4 +3,8 @@ type rec t = Any('a): t let sync = syncHandler => Any(syncHandler) +let syncWithCustomError = syncHandler => Any(syncHandler) + let async = asyncHandler => Any(asyncHandler) + +let asyncWithCustomError = asyncHandler => Any(asyncHandler) diff --git a/src/Validation.resi b/src/Validation.resi index eeb59b7..d14771e 100644 --- a/src/Validation.resi +++ b/src/Validation.resi @@ -3,7 +3,11 @@ type rec t @ocaml.doc("Synchronous validation") -let sync: (Js.Json.t => bool) => t +let sync: (Js.Json.t => ValidationResult.t) => t + +let syncWithCustomError: (Js.Json.t => ValidationResult.t) => t @ocaml.doc("Asynchronous validation") -let async: (Js.Json.t => Js.Promise.t) => t +let async: (Js.Json.t => Js.Promise.t) => t + +let asyncWithCustomError: (Js.Json.t => Js.Promise.t) => t diff --git a/src/ValidationResult.res b/src/ValidationResult.res new file mode 100644 index 0000000..df018fe --- /dev/null +++ b/src/ValidationResult.res @@ -0,0 +1,5 @@ +@unboxed type rec t = Any('value): t + +let boolResult = validationResult => Any(validationResult) + +let stringResult = validationResult => Any(validationResult) diff --git a/src/ValidationResult.resi b/src/ValidationResult.resi new file mode 100644 index 0000000..8e63253 --- /dev/null +++ b/src/ValidationResult.resi @@ -0,0 +1,8 @@ +@ocaml.doc( + "The union type for the result of a validator function; allows validation functions to return either boolean or a string" +) +type rec t + +let boolResult: bool => t + +let stringResult: string => t From e11db03d41b987c420c131eaaa3f826f556e8f48 Mon Sep 17 00:00:00 2001 From: Victor Trofin Date: Wed, 10 Aug 2022 14:22:32 +0900 Subject: [PATCH 2/2] update example --- example/Example.res | 25 +++++++++++++++++++++++++ example/Validations.res | 23 +++++++++++++++++++++++ example/Values.res | 7 ++++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 example/Validations.res diff --git a/example/Example.res b/example/Example.res index 0560b16..f1d344d 100644 --- a/example/Example.res +++ b/example/Example.res @@ -18,6 +18,7 @@ module Form = { "lastName": "", "acceptTerms": false, "hobbies": [{"name": ""}], + "location": "", }), (), ), @@ -143,6 +144,30 @@ module Form = { React.string} /> } /> + +
+ + onBlur()} + onChange={event => onChange(Controller.OnChangeArg.event(event))} + ref + value={value->ReCode.Decode.string->Belt.Result.getWithDefault("")} + /> + React.string} /> +
} + /> { + let decodedValue = value->Js.Json.decodeString->Belt.Option.getWithDefault("") + + ValidationResult.boolResult(decodedValue->Js.String2.length >= 3) +} + +let minThreeChars = Validation.sync(minThreeCharsValue) + +@ocaml.doc( + "Custom validator fuction to check that the input has at most ten characters. Will throw a custom error otherwise." +) +let maxTenCharsValue = value => { + let decodedValue = value->Js.Json.decodeString->Belt.Option.getWithDefault("") + + decodedValue->Js.String2.length > 10 + ? ValidationResult.stringResult("There are over ten characters in this input.") + : ValidationResult.boolResult(true) +} + +let maxTenChars = Validation.syncWithCustomError(maxTenCharsValue) diff --git a/example/Values.res b/example/Values.res index 38902f3..5006905 100644 --- a/example/Values.res +++ b/example/Values.res @@ -23,6 +23,7 @@ type t = { email: string, firstName: string, lastName: string, + location: string, acceptTerms: bool, hobbies: array, } @@ -30,14 +31,16 @@ type t = { let email = "email" let firstName = "firstName" let lastName = "lastName" +let location = "location" let acceptTerms = "acceptTerms" let hobbies = "hobbies" let hobby = index => `${hobbies}.${index->Belt.Int.toString}.${Hobby.name}` -let make = (email, firstName, lastName, acceptTerms, hobbies) => { +let make = (email, firstName, lastName, acceptTerms, hobbies, location) => { email: email, firstName: firstName, lastName: lastName, + location: location, acceptTerms: acceptTerms, hobbies: hobbies, } @@ -52,6 +55,7 @@ let decoder = { ->Object.required(lastName, string) ->Object.required(acceptTerms, bool) ->Object.required(hobbies, array(Hobby.decoder)) + ->Object.required(location, string) } let encoder = values => { @@ -61,6 +65,7 @@ let encoder = values => { (email, string(values["email"])), (firstName, string(values["firstName"])), (lastName, string(values["lastName"])), + (location, string(values["location"])), (acceptTerms, bool(values["acceptTerms"])), (hobbies, array(Hobby.encoder, values["hobbies"])), ])