Code Monkey home page Code Monkey logo

elm-verify's People

Contributors

andys8 avatar gyzerok avatar kornicameister avatar stoeffel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

elm-verify's Issues

Consider creating Verify.map

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

How best to express that a value is either one thing or another?

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.

Get a list of errors for one field

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!

IsValid function

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.

Unexpected type signature for `andThen`

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"

Parallel composition of Validators

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

Please add a changelog

I see that v4 was just released. I was looking for some docs on what has changed.
A CHANGELOG.md would be great.
Thanks

switch namespaces

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

using a non empty list for errors.

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

Adjust definition of Verify.andThen

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.

Motivation for module structure

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?

Missing a helper function to create validators from predicates

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?

when and predicate

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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.