stoeffel / elm-verify / Verify

Verify allows you to validate a model into a structure that makes forbidden states impossible.


type alias Validator error input verified =
input -> Result ( error
, List error ) verifie
}

This is just an alias for a function from an input to a result. The result is either:

ok : finally -> input -> Result ( error, List error ) finally

This allows you to lift any value into a validator. This is particularly useful to initialize a pipeline.

ok "always ok" Nothing
--> Ok "always ok"

fail : error -> input -> Result ( error, List error ) verified

Allows you to create a failing Validator.

fail "always fail" Nothing
--> Err ( "always fail" , [])

validate : finally -> Validator error input finally

Allows you to start a validation pipeline. It is a synonym for Verify.ok, intended to make things clearer to read.

import Maybe.Verify


type alias User =
    { id : Int
    , firstName : String
    }

validator : Verify.Validator String { a | id : Int, firstName : Maybe String } User
validator =
    Verify.validate User
        |> Verify.keep .id
        |> Verify.verify .firstName (Maybe.Verify.isJust "You need to provide a first name.")

validator { id = 1, firstName = Nothing }
--> Err ( "You need to provide a first name." , [])

validator { id = 1, firstName = Just "Stöffel" }
--> Ok { id = 1, firstName = "Stöffel" }

verify : (bigger -> smaller) -> Validator error smaller verified -> Validator error bigger (verified -> finally) -> Validator error bigger finally

Allows you to verify a part of a structure.

import Maybe.Verify


validator : Verify.Validator String { a | firstName : Maybe String } String
validator =
    Verify.validate identity
        |> Verify.verify .firstName (Maybe.Verify.isJust "You need to provide a first name.")

validator { firstName = Nothing }
--> Err ( "You need to provide a first name." , [])

validator { firstName = Just "Stöffel" }
--> Ok "Stöffel"

keep : (bigger -> smaller) -> Validator error bigger (smaller -> finally) -> Validator error bigger finally

You can use keep if you want a value to be in the verified structure without any verification.

import Maybe.Verify


validator : Validator String { a | id : Int, firstName : Maybe String } (Int, String)
validator =
    Verify.validate (,)
        |> Verify.keep .id
        |> Verify.verify .firstName (Maybe.Verify.isJust "You need to provide a first name.")


validator { id = 1, firstName = Nothing }
--> Err ( "You need to provide a first name." , [])

validator { id = 1, firstName = Just "Stöffel" }
--> Ok (1, "Stöffel")

custom : Validator error input verified -> Validator error input (verified -> finally) -> input -> Result ( error, List error ) finally

Sometimes the verification of a part only makes sense in a bigger context. This means your Validator needs access to the whole structure.

import Maybe.Verify


validator : Verify.Validator String { a | username : Maybe String, level: Int, strength: Int } (String, Int)
validator =
    Verify.validate (,)
        |> Verify.verify .username (Maybe.Verify.isJust "You need to provide a username.")
        |> Verify.custom (\{level, strength} ->
            if strength > level then
                Err ( "Your strength can exceed your level." , [])
            else
                Ok strength
            )


validator { username = Just "Ork1", level = 3, strength = 5 }
--> Err ( "Your strength can exceed your level." , [])

validator { username = Just "Ork1", level = 6, strength = 5 }
--> Ok ("Ork1", 5)

validator { username = Nothing, level = 3, strength = 5 }
--> Err ( "You need to provide a username."
-->     , [ "Your strength can exceed your level."]
-->     )

compose : Validator error verified finally -> Validator error input verified -> Validator error input finally

This allows you to compose multiple Validators.

import Maybe.Verify
import String.Verify


validator { firstName = Nothing }
--> Err ( "You need to provide a first name." , [])

validator { firstName = Just "   " }
--> Err ( "You need to provide a none empty first name." , [])

validator { firstName = Just "Stöffel" }
--> Ok "Stöffel"

validator : Verify.Validator String { a | firstName : Maybe String } String
validator =
    Verify.validate identity
        |> Verify.verify .firstName verifyName

verifyName : Verify.Validator String (Maybe String) String
verifyName =
    Maybe.Verify.isJust "You need to provide a first name."
        |> Verify.compose (String.Verify.notBlank "You need to provide a none empty first name.")

andThen : (a -> Validator error input b) -> Validator error input a -> Validator error input b

This allows you to chain multiple Validators.

import Maybe.Verify


validator { firstName = Nothing }
--> Err ( "You need to provide a first name." , [])

validator { firstName = Just "   " }
--> Err ( "Name is too short" , [])

validator { firstName = Just "Stöffel" }
--> Ok "Stöffel"

validator : Verify.Validator String { a | firstName : Maybe String } String
validator =
    Verify.validate identity
        |> Verify.verify .firstName verifyName

verifyName : Verify.Validator String (Maybe String) String
verifyName =
    Maybe.Verify.isJust "You need to provide a first name."
        |> Verify.andThen (\name ->
            if String.length name > 5 then
                Verify.ok name
            else
                Verify.fail "Name is too short"
        )

fromMaybe : (input -> Maybe verified) -> error -> Validator error input verified

This is a convenient function to create a Validator from a function that returns a maybe instead of a Result. It fails if the function returns a Nothing. This allows you to verify a input and return a verified result of a different type.

fromMaybe hasInitial "error" ""
--> Err ( "error" , [])

fromMaybe hasInitial "error" "Christoph"
--> Ok 'C'

hasInitial : String -> Maybe Char
hasInitial str =
    case String.uncons str of
        Just (initial, _) -> Just initial
        Nothing -> Nothing