Personally, I’m a big fan of a data structure that looks roughly like this when modeling data that can be validated:
type Validated a
= Initial -- Field is neither valid nor invalid on initial render
| Valid a -- We can turn the valid raw string into a custom value `a`
| Invalid String String -- We track both the raw string and the error message
You can wrap any field in it, including fields that get transformed to fancier types:
type Model =
{ name : Validated String
, phone : Validated PhoneNumber
, email : Validated Email
}
I walked through data modeling a multi-step form with validation in this discourse thread. I also created an executable example on Ellie that uses this.