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 bit_array: Decoder(BitArray)

Decode a BitArray

pub const bool: Decoder(Bool)

Decode a Bool value

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: Decoder(Float)

Decode a Float value

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: Decoder(Int)

Decode an Int value

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

pub const string: Decoder(String)

Decode a String value

pub const uri: Decoder(Uri)

Decodes a Uri from a string Uses the gleam/uri module on erlang and the URL object on javascript

Error type: uri

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:))
}
Search Document