stoeffel / elm-verify Goto Github PK
View Code? Open in Web Editor NEWHome Page: http://package.elm-lang.org/packages/stoeffel/elm-verify/latest
License: BSD 3-Clause "New" or "Revised" License
Home Page: http://package.elm-lang.org/packages/stoeffel/elm-verify/latest
License: BSD 3-Clause "New" or "Revised" License
To perform the same function as Json.Decode.map
I'm not great at this stuff but I have:
map : (a -> b) -> V.Validator error input a -> V.Validator error input b
map func validator =
validator >> Result.map func
I have a string in my model that I'd like to either be empty or for it to represent an int. It represents a numerical limit on something and no entry (empty string) means no limit. It also means that if it is an int, it should ideally be a positive int.
The validation would convert from a String
to a Maybe Int
.
I think it is reasonable to approach this with custom. I ended up with something like:
type alias EditableCountConstraint =
{ minCount : String
, maxCount : String
}
type alias CountConstraint =
{ minCount : Maybe Int
, maxCount : Maybe Int
}
test : V.Validator String EditableCountConstraint CountConstraint
test =
let
validator value =
if String.isEmpty value then
Ok Nothing
else
case String.toInt value of
Ok int ->
if int > 0 then
Ok <| Just int
else
Err [ "ConstraiPFDnt should be greater than zero" ]
Err _ ->
Err [ "Constraint should be an integer" ]
in
V.ok CountConstraint
|> V.custom (.minCount >> validator)
|> V.custom (.maxCount >> validator)
This is fine, I suspect. And gives granular control over the error messages. But as I'm still clearly somewhat stuck in the mindset of Json.Decode, I initially wanted to have some kind of oneOf
based API. Like (pseudo-code):
oneOf "Constraint must be empty or a positive integer" <|
[ String.Verify.Extra.empty
, String.Verify.isInt |> and Int.Verify.isPositive
]
So I ended up writing:
oneOf : error -> Nonempty (error -> input -> Result (List error) b) -> V.Validator error input b
oneOf error list subject =
let
step : Nonempty (error -> input -> Result (List error) b) -> Result (List error) b
step remainingList =
case ( Nonempty.head remainingList error subject, Nonempty.fromList (Nonempty.tail remainingList) ) of
( Err err, Nothing ) ->
Err err
( Err err, Just tail ) ->
step tail
( Ok value, _ ) ->
Ok value
in
step list
map : (a -> b) -> V.Validator error input a -> V.Validator error input b
map func validator =
validator >> Result.map func
and : (error -> V.Validator error b c) -> (error -> V.Validator error a b) -> (error -> V.Validator error a c)
and second first error input =
first error input
|> Result.andThen (second error)
Which unfortunately requires a Nonempty list to work (I think!) as it is hard to return something appropriate if you don't have anything in the list and all the types are generic.
So to work with that and to make the types happy I've ended up with:
constraintValidator : Validator EditableCountConstraint CountConstraint
constraintValidator =
let
validator =
VE.oneOf "Constraint must be empty or a positive integer" <|
Nonempty String.Verify.Extra.empty
[ (String.Verify.isInt |> VE.and Int.Verify.isPositive) >> VE.map Just
]
in
V.ok CountConstraint
|> V.verify .minCount validator
|> V.verify .maxCount validator
The role of 'VE.and' is to combine validators that don't have specific error messages.
It think it makes sense that oneOf
should take the error message as an argument and it should be returned any everything fails. Though this loses the granularity of the errors we had above, it isn't a bad thing to always present the bigger picture if it is going to appear as an error in the UI.
Honestly, I wouldn't push to include this in the library as I suspect the custom
approach is more readable. It is also more verbose which is off putting at some level. I wish the oneOf
could be implemented with a normal list as that would make the API more pleasant.
Whether it is included or not, I wanted to share it. Either to help others facing similar things or to help me if someone can present a third & better way of doing it :)
Int.Verify
and String.Verify.Extra
are both local packages. Not published.
Int.Verify.isPositive
is defined as:
isPositive : error -> Validator error Int Int
isPositive error input =
if input > 0 then
Ok input
else
Err [ error ]
And String.Verify.Extra.empty
is:
empty : error -> Validator error String (Maybe a)
empty error input =
if String.isEmpty input then
Ok Nothing
else
Err [ error ]
Which I find a bit weird, but I guess makes sense.
The library current uses Verify.ok
to start a validation chain. It might be more readable as an alias called Verify.validate
which would mirror the Json.Decode.Pipeline.decode
function which itself a more readable alias of Json.Decode.succeed
.
We have a list field where we want to get a list of all errors for it. andThen
stops at the next error so we only get one error at a time.
To do this I have ended up with a function like:
formValidator : Validator ( FormField, String ) Form Form
formValidator =
Verify.ok Form
....
|> Verify.verify .password verifyPasswordParts
verifyPasswordParts : Validator ( FormField, String ) String String
verifyPasswordParts =
Verify.ok (\a b c d -> a)
|> Verify.verify identity (mustHaveNumbers ( (FieldPassword FieldPasswordNum), "Must have at least 1 number" ))
|> Verify.verify identity (mustHaveLowerCase ( (FieldPassword FieldPasswordLower), "Must have at least 1 lowercase" ))
|> Verify.verify identity (mustHaveUpperCase ( (FieldPassword FieldPasswordUpper), "Must have at least 1 uppercase" ))
|> Verify.verify identity (mustHaveMinLen ( (FieldPassword FieldPasswordLen), "Must have at least 8 characters" ))
Here is an example with more context:
https://ellie-app.com/cwy697Wwfa1/2
I'm overlooking a simpler way to do this?
Could we have a function to run several validators and get all the errors in a list?
Thanks!
Sometime we just want to know if something is valid without dealing with Result
. e.g. I want to disable a button if the form in invalid.
It would be nice to have a convenience function that takes a validator, a data structure and returns a boolean.
Is the unusual type signature of andThen
intentional? My expectation was that it would behave analogously to Maybe.andThen
, Result.andThen
etc.
The definition I expected is this:
andThen : (a -> Validator err input b) -> Validator err input a -> Validator err input b
andThen callback validator input =
validator input |> Result.andThen (\a -> callback a <| input)
This is useful when the kind of verification to be performed depends on the result of a previous verification.
Your current definition does not allow that but requires for the second verification to be picked statically. I can see how it might be called "andThen", but I always assumed this name to be conventionally reserved for functions of type (a -> t b) -> t a -> t b
, where in this case t = Validator err input
.
Maybe "compose" could be a good name to use instead? Or, less formally: "pipe", "next", "andAlso"
Currently its tricky to compose validators in a parallel fashion, the idea is kind to an arrow type combinator like split (Haskells (&&&) in Control.Arrow) to apply both validators to the same input and then disambiguate which result to pic with a merging function.
I actually had to create this for a project I'm working on, here are two different ways of writing this function:
parallel : (a -> b -> c) -> Validator e i a -> Validator e i b -> Validator e i c
parallel merge v1 v2 =
\a ->
case (v1 a, v2 a) of
(Ok r1, Ok r2) ->
Ok <| merge r1 r2
(Err e1, Err e2) ->
Err (e1 ++ e2)
(Err e, _) ->
Err e
(_, Err e) ->
Err e
parallel_ : (a -> b -> c) -> Validator e i a -> Validator e i b -> Validator e i c
parallel_ merge v1 v2 =
let
map f validator = \i -> validator i |> Result.map f
in
map merge v1
|> Verify.custom v2
I see that v4 was just released. I was looking for some docs on what has changed.
A CHANGELOG.md would be great.
Thanks
Maybe.Verify
feels more in line with Maybe.Extra
and it allows for projects to use the same pattern. As an example List.Selection.Verify
I was wondering if it would be a good idea to ensure that there is always at least one error by using a non-empty list. I like using (a, List a)
for that to avoid depending on a package.
type alias Validator error input verified =
- input -> Result (List error) verified
type alias Validator error input verified =
+ input -> Result (a, List error) verified
Not 100% sure of this one as I don't have everything compiling on my side yet but from my experience with Json.Decode
I expected Verify.andThen
to work like this:
andThen : (a -> Validator error input b) -> Validator error input a -> Validator error input b
andThen func validator =
\input ->
let
result =
validator input
in
result
|> Result.andThen (flip func input)
Then the signature matches what I would expect. The implementation can be condensed a bit.
Again, not 100% sure as my wider program doesn't compile yet but this matches my expectations a bit closer.
Hi @stoeffel!
Can you, please, elaborate a bit on reasoning behind module structure in the library?
It looks like a conscious decision to use String.Verify
and Dict.Verify
instead of Verify.String
and Verify.Dict
. Are there some advantages in former? Are there disadvantages in latter?
Would it make sense to add it to the readme?
It would be nice to have a shorthand for creating a validator from a predicate and an error.
This what I'm using in my project currently:
withPredicate : (a -> Bool) -> e -> Validator e a a
withPredicate pred e =
\a ->
if pred a then a |> Verify.ok a else a |> Verify.fail e
Would a pull request be accepted for this?
example https://ellie-app.com/rXBsnHztMa1/0
should we report all errors or only the first?
should there be a function to get all errors and one for getting the first?
should we add the index/key to the error message (Int, error)
?
should we add specific functions to TYPE.Verify? yes
naming all
, items
, elements
, values
?
It would be nice to have a function to create a Validator
from any predicate.
predicate : (a -> Bool) -> error -> Validator error a a
and maybe we wanna rename when
and make the docs clearer that this allows to go from input -> result
when : (input -> Maybe result) -> error -> Validator error input result
.
Possible naming options: predicateMap
, fromMaybe
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.