toy
Types
A decoder is a function that takes a Dynamic
value and returns a tuple
containing the default value of the same type and a Result
with the decoded
value or a list of errors
pub type Decoder(a) {
Decoder(
run: fn(dynamic.Dynamic) -> #(a, Result(a, List(ToyError))),
)
}
Constructors
-
Decoder( run: fn(dynamic.Dynamic) -> #(a, Result(a, List(ToyError))), )
Contains decoding or validation errors
pub type ToyError {
ToyError(error: ToyFieldError, path: List(String))
}
Constructors
-
ToyError(error: ToyFieldError, path: List(String))
Each type of error that can be returned by the decoders
pub type ToyFieldError {
InvalidType(expected: String, found: String)
Missing(expected: String)
ValidationFailed(
check: String,
expected: String,
found: String,
)
AllFailed(failures: List(List(ToyError)))
}
Constructors
-
InvalidType(expected: String, found: String)
-
Missing(expected: String)
-
ValidationFailed(check: String, expected: String, found: String)
-
AllFailed(failures: List(List(ToyError)))
Constants
pub const base16_string: Decoder(BitArray)
Decodes a BitArray
from a base16 encoded string
pub const base64_string: Decoder(BitArray)
Decodes a BitArray
from a base64 encoded string
pub const base64_url_string: Decoder(BitArray)
Decodes a BitArray
from a url safe base64 encoded string
using -
instead of +
and _
instead of /
pub const bool_string: Decoder(Bool)
Decodes a String
and parses it as Bool
with the given variants:
case value {
"True" | "true" -> Ok(True)
"False" | "false" -> Ok(False)
_ -> Error(Nil)
}
In case pattern matching on the string fails,
returns ValidationFailed
error
Error type: bool_string
pub const dynamic: Decoder(Dynamic)
Always decodes the provided value as Dynamic
.
Error is never returned from this decoder
pub const float_string: Decoder(Float)
Decode a String
and parse it as Float
Allows for values that look like floats “1.345” and integers “12345”
In case parsing failed, returns ValidationFailed
error
Error type: float_string
pub const int_string: Decoder(Int)
Decode a String
and parse it as Int
In case parsing failed, returns ValidationFailed
error
Error type: int_string
pub const nullish: Decoder(Nil)
Decodes a nullish value.
In erlang it is one of these atoms: undefined
, null
, nil
.
In javascript it is one of these values: undefined
, null
Functions
pub fn decode(
data: Dynamic,
decoder: Decoder(a),
) -> Result(a, List(ToyError))
Takes a Dynamic
value and runs a Decoder
on it, returning the result
of the decoding process
pub fn decoded(value: a) -> Decoder(a)
Creates a decoder which directly returns the provided value. It is useful when decoding records.
pub fn user_decoder() {
use name <- toy.field("name", toy.string)
toy.decoded(User(name:))
}
pub fn dict(
key_type: Decoder(a),
value_type: Decoder(b),
) -> Decoder(Dict(a, b))
Decodes a Dict
using the provided key and value decoders
pub fn enum(
dec: Decoder(a),
variants: List(#(a, b)),
) -> Decoder(b)
Takes any decoder and a list of mappings from the decoded value to a new value. Returns the new value corresponding to the decoded value.
In case of an error, return a ValidationFailed
with the expected type
being a list of the possible values separated by a comma.
Panics if the list of variants is empty
Error type: enum
pub type Fish {
Salmon
Trout
Shard
Cod
}
let decoder =
toy.string
|> toy.enum([
#("salmon", Salmon),
#("trout", Trout),
#("shard", Shard),
#("cod", Cod),
])
dynamic.from("salmon")
|> toy.decode(decoder)
|> should.equal(Ok(Salmon))
pub fn fail(error: ToyFieldError, default: a) -> Decoder(a)
Returns a decoder that always fails with the provided error
pub fn field(
key: a,
decoder: Decoder(b),
next: fn(b) -> Decoder(c),
) -> Decoder(c)
Decode a field from a Dynamic
value
This function will index into dictionary with any key type, tuples with integer or javascript arrays and objects. The value found under the key will be decoded with the provided decoder.
pub fn user_decoder() {
use name <- toy.field("name", toy.string)
toy.decoded(User(name:))
}
pub fn float_max(
dec: Decoder(Float),
maximum: Float,
) -> Decoder(Float)
Validates that number is less than the provided maximum
Error type: float_max
pub fn float_min(
dec: Decoder(Float),
minimum: Float,
) -> Decoder(Float)
Validates that number is greater or equal to the provided minimum
Error type: float_min
pub fn float_range(
dec: Decoder(Float),
minimum: Float,
maximum: Float,
) -> Decoder(Float)
Validates that the number is withing the provided range [minimum, maximum)
Error type: float_range
pub fn int_max(dec: Decoder(Int), maximum: Int) -> Decoder(Int)
Validates that number is less than the provided maximum
Error type: int_max
pub fn int_min(dec: Decoder(Int), minimum: Int) -> Decoder(Int)
Validates that number is greater or equal to the provided minimum
Error type: int_min
pub fn int_range(
dec: Decoder(Int),
minimum: Int,
maximum: Int,
) -> Decoder(Int)
Validates that number is in the provided range: [minimum, maximum)
Error type: int_range
pub fn is_equal(dec: Decoder(a), literal: a) -> Decoder(a)
Validates that the decoded value is equal to the provided value
The comparison is made using the ==
operator
Error type: is_literal
pub fn list(item: Decoder(a)) -> Decoder(List(a))
Decode a list of values
pub fn fruits_decoder() {
toy.list({
use name <- toy.field("name", toy.string)
toy.decoded(Fruit(name:))
})
}
pub fn list_max(
dec: Decoder(List(a)),
maximum: Int,
) -> Decoder(List(a))
Validates that the length of the list is less than the provided maximum
Error type: list_max
pub fn list_min(
dec: Decoder(List(a)),
minimum: Int,
) -> Decoder(List(a))
Validates that the length of the list is at least the provided number
Error type: list_min
pub fn list_nonempty(dec: Decoder(List(a))) -> Decoder(List(a))
Validates that the list is not empty (contains at least one element)
Error type: list_nonempty
pub fn map(dec: Decoder(a), fun: fn(a) -> b) -> Decoder(b)
Map the result of the decoder to a new value
pub type Unit {
Centimeters(Int)
Milimeters(Int)
}
pub type User {
User(height: Unit)
}
pub fn user_decoder() {
use height <- toy.field("height", toy.int |> toy.map(Centimeters))
toy.decoded(User(:height))
}
pub fn map_errors(
dec: Decoder(a),
fun: fn(List(ToyError)) -> List(ToyError),
) -> Decoder(a)
If the passed in decoder returns an error, the provided function is called to allow you to change or swap the errors
pub fn nullable(of dec: Decoder(a)) -> Decoder(Option(a))
Creates a new decoder from an existing one, which will return None
if
the value is null
or undefined
on javascript, or nil
, null
,
undefined
on erlang. Otherwise it will return the result of the provided
decoder wrapped in Some
pub fn one_of(decoders: List(Decoder(a))) -> Decoder(a)
Attempts to decode the value with each of the decoders in order. The first successful one will be returned. If none of the decoders are successful, an error is returned specifying possible options.
This function will panic if the list of decoders is empty.
let dog_decoder = fn() {
use tag <- toy.field("tag", toy.string)
toy.decoded(Dog(tag:))
}
let cat_decoder = fn() {
use collar <- toy.field("collar", toy.string)
toy.decoded(Cat(collar:))
}
let fish_decoder = fn() {
use color <- toy.field("color", toy.string)
toy.decoded(Fish(color:))
}
let decoder = toy.one_of([dog_decoder(), cat_decoder(), fish_decoder()])
dict.from_list([#("tag", dynamic.from("woof"))])
|> dynamic.from
|> toy.decode(decoder)
|> should.equal(Ok(Dog(tag: "woof")))
dict.from_list([#("feathers", dynamic.from("blue"))])
|> dynamic.from
|> toy.decode(decoder)
|> should.equal(
Error([
toy.ToyError(
toy.AllFailed([
[toy.ToyError(toy.Missing("String"), ["\"tag\""])],
[toy.ToyError(toy.Missing("String"), ["\"collar\""])],
[toy.ToyError(toy.Missing("String"), ["\"color\""])],
]),
[],
),
]),
)
pub fn option(of dec: Decoder(a)) -> Decoder(Option(a))
Decodes a gleam Option
type. In erlang represented as {ok, Value}
or none
.
In javascript represented as an instance of Some
or None
classes.
pub fn optional_field(
key: a,
decoder: Decoder(b),
next: fn(Option(b)) -> Decoder(c),
) -> Decoder(c)
Decode a field from a Dynamic
value
This function will index into dictionary with any key type, tuples with integer or javascript arrays and objects. The value found under the key will be decoded with the provided decoder.
None
is returned only if the field is missing. Otherwise the provided
decoder is used to decode the value.
pub fn reservation_decoder() {
use note <- toy.optional_field("note", toy.string)
toy.decoded(User(name:))
}
pub fn optional_subfield(
keys: List(a),
decoder: Decoder(b),
next: fn(Option(b)) -> Decoder(c),
) -> Decoder(c)
Same as optional_field
but indexes recursively with the provided keys
pub fn user_decoder() {
use name <- toy.optional_subfield(["person", "name"], toy.string)
toy.decoded(User(name:))
}
pub fn refine(
dec: Decoder(a),
fun: fn(a) -> Result(Nil, List(ToyError)),
) -> Decoder(a)
Refine the result of the decoder with a validation function
pub fn user_decoder() {
use name <- toy.field("name", toy.string |> toy.refine(fn(name) {
case name {
"toy" -> Error([toy.ToyError(toy.ValidationFailed("name_taken", "new_name", name), [])])
_ -> Ok(Nil)
}
}))
toy.decoded(User(name:))
}
pub fn string_email(dec: Decoder(String)) -> Decoder(String)
Validates that the string contains an email address. This is done by checking if the string contains the “@” character.
Error type: string_email
pub fn string_max(
dec: Decoder(String),
maximum: Int,
) -> Decoder(String)
Validates that the length of the string is less than the provided number
Error type: string_max
pub fn string_min(
dec: Decoder(String),
minimum: Int,
) -> Decoder(String)
Validates that the length of the string is at least the provided number
Error type: string_min
pub fn string_nonempty(dec: Decoder(String)) -> Decoder(String)
Validates that the string contains some characters that are not whitespace.
Error type: string_nonempty
pub fn string_uri(dec: Decoder(String)) -> Decoder(String)
Validates that the string is a valid uri
Uses the gleam/uri
module on erlang and the URL
object on javascript
Error type: string_uri
pub fn subfield(
keys: List(a),
decoder: Decoder(b),
next: fn(b) -> Decoder(c),
) -> Decoder(c)
Same as field
but indexes recursively with the provided keys
pub fn user_decoder() {
use name <- toy.subfield(["person", "name"], toy.string)
toy.decoded(User(name:))
}
pub fn to_stdlib_decoder(
dec: Decoder(a),
) -> fn(Dynamic) -> Result(a, List(DecodeError))
Converts a toy.Decoder(a)
to a dynamic.Decoder(a)
You shouldn’t use this function if you can avoid it.
But sometimes libraries expect a dynamic.Decoder(a)
and passing
dynamic.dynamic
just makes things hard.
import gleam/dynamic
pub fn user_decoder() {
use name <- toy.field("name", toy.string)
toy.decoded(User(name:))
}
pub fn main() {
let assert Ok(response) =
pgo.execute(sql, db, [pgo.int(1)], toy.to_stdlib_decoder(user_decoder()))
}
Mapping of toy.ToyError
into dynamic.DecodeError
toy.InvalidType("Int", "String")
->dynamic.DecodeError("Int", "String", path)
toy.Missing("Int")
->dynamic.DecodeError("Int", "Nothing", path)
toy.ValidationFailed("int_min", ">=10", "7")
->dynamic.DecodeError("int_min__>=10, int_min__7, path)
toy.AllFailed([...])
->dynamic.DecodeError("AllFailed", "AllFailed", path)
and all the errors in the list translated recursively
pub fn try_map(
dec: Decoder(a),
default: b,
fun: fn(a) -> Result(b, List(ToyError)),
) -> Decoder(b)
Map the result of the decoder to a new value or return an error
pub fn user_decoder() {
use name <- toy.field("name", toy.string |> toy.try_map("", fn(name) {
case name {
"toy" -> Error([toy.ToyError(toy.ValidationFailed("name_taken", "new_name", name), [])])
_ -> Ok(string.uppercase(name))
}
}))
toy.decoded(User(name:))
}