Update form parser API.

This commit is contained in:
Dillon Kearns 2022-06-03 07:23:26 -07:00
parent d133441cb7
commit cfad541029
2 changed files with 208 additions and 2 deletions

View File

@ -16,6 +16,14 @@ type Parser error decoded
= Parser (Dict String (List error) -> Form.FormState -> ( Maybe decoded, Dict String (List error) ))
optional : String -> Parser error (Maybe String)
optional name =
(\errors form ->
( Just (form |> Dict.get name |> Maybe.map .value), errors )
)
|> Parser
required : String -> error -> Parser error String
required name error =
(\errors form ->
@ -32,6 +40,27 @@ required name error =
|> Parser
int : String -> error -> Parser error Int
int name error =
(\errors form ->
case form |> Dict.get name |> Maybe.map .value of
Just "" ->
( Nothing, errors |> addError name error )
Just nonEmptyValue ->
case nonEmptyValue |> String.toInt of
Just parsedInt ->
( Just parsedInt, errors )
Nothing ->
( Nothing, errors |> addError name error )
Nothing ->
( Nothing, errors |> addError name error )
)
|> Parser
map2 : (value1 -> value2 -> combined) -> Parser error value1 -> Parser error value2 -> Parser error combined
map2 combineFn (Parser parser1) (Parser parser2) =
(\errors form ->
@ -55,11 +84,104 @@ map2 combineFn (Parser parser1) (Parser parser2) =
|> Parser
map : (original -> mapped) -> Parser error original -> Parser error mapped
map mapFn (Parser parser) =
(\errors form ->
let
( combined1, allErrors1 ) =
parser errors form
in
( Maybe.map mapFn combined1
, allErrors1
)
)
|> Parser
validate : String -> (original -> Result error mapped) -> Parser error original -> Parser error mapped
validate name mapFn (Parser parser) =
(\errors form ->
let
( combined1, allErrors1 ) =
parser errors form
in
case combined1 |> Maybe.map mapFn of
Just (Ok okResult) ->
( Just okResult
, allErrors1
)
Just (Err error) ->
( Nothing
, allErrors1 |> addError name error
)
Nothing ->
( Nothing
, allErrors1
)
)
|> Parser
succeed : value -> Parser error value
succeed value =
Parser (\errors form -> ( Just value, Dict.empty ))
fail : error -> Parser error value
fail error =
Parser (\errors form -> ( Nothing, Dict.fromList [ ( "global", [ error ] ) ] ))
andThen : (value1 -> Parser error value2) -> Parser error value1 -> Parser error value2
andThen andThenFn (Parser parser1) =
(\errors form ->
let
( combined1, allErrors1 ) =
parser1 errors form
foo : Maybe (Parser error value2)
foo =
Maybe.map andThenFn combined1
in
case foo of
Just (Parser parser2) ->
let
( combined2, allErrors2 ) =
parser2 errors form
in
( combined2
, Dict.merge (\name errors1 dict -> ( name, errors1 ) :: dict)
(\name errors1 errors2 dict -> ( name, errors1 ++ errors2 ) :: dict)
(\name errors2 dict -> ( name, errors2 ) :: dict)
allErrors1
allErrors2
[]
|> Dict.fromList
)
Nothing ->
( Nothing, allErrors1 )
)
|> Parser
run : Form.FormState -> Parser error decoded -> ( Maybe decoded, Dict String (List error) )
run formState (Parser parser) =
parser Dict.empty formState
runOnList : List ( String, String ) -> Parser error decoded -> ( Maybe decoded, Dict String (List error) )
runOnList rawFormData (Parser parser) =
(rawFormData
|> List.map
(Tuple.mapSecond (\value -> { value = value, status = Form.NotVisited }))
|> Dict.fromList
)
|> parser Dict.empty
addError : String -> error -> Dict String (List error) -> Dict String (List error)
addError name error allErrors =
allErrors

View File

@ -14,10 +14,19 @@ formDecoder =
(FormParser.required "last" "Last is required")
type Uuid
= Uuid String
type Action
= Signout
| SetQuantity Uuid Int
all : Test
all =
describe "Path"
[ test "join two segments" <|
describe "Form Parser"
[ test "error for missing required fields" <|
\() ->
FormParser.run
(Dict.fromList
@ -41,4 +50,79 @@ all =
, ( "last", [ "Last is required" ] )
]
)
, test "parse into custom type" <|
\() ->
FormParser.run
(Dict.fromList
[ ( "kind"
, { value = "signout"
, status = Pages.Form.NotVisited
}
)
]
)
(FormParser.required "kind" "Kind is required"
|> FormParser.andThen
(\kind ->
if kind == "signout" then
FormParser.succeed Signout
else if kind == "add" then
FormParser.map2 SetQuantity
(FormParser.required "itemId" "First is required" |> FormParser.map Uuid)
(FormParser.int "setQuantity" "Expected setQuantity to be an integer")
else
FormParser.fail "Error"
)
)
|> Expect.equal
( Just Signout
, Dict.empty
)
, test "parse into custom type with int" <|
\() ->
FormParser.run
(fields
[ ( "kind", "add" )
, ( "itemId", "123" )
, ( "setQuantity", "1" )
]
)
(FormParser.required "kind" "Kind is required"
|> FormParser.andThen
(\kind ->
if kind == "signout" then
FormParser.succeed Signout
else if kind == "add" then
FormParser.map2 SetQuantity
(FormParser.required "itemId" "First is required" |> FormParser.map Uuid)
-- TODO what's the best way to combine together int and required? Should it be `requiredInt`, or `Form.required |> Form.int`?
(FormParser.int "setQuantity" "Expected setQuantity to be an integer")
else
FormParser.fail "Error"
)
)
|> Expect.equal
( Just (SetQuantity (Uuid "123") 1)
, Dict.empty
)
]
field : String -> String -> ( String, Pages.Form.FieldState )
field name value =
( name
, { value = value
, status = Pages.Form.NotVisited
}
)
fields : List ( String, String ) -> Dict String Pages.Form.FieldState
fields list =
list
|> List.map (\( name, value ) -> field name value)
|> Dict.fromList