import gleam/int import gleam/list import gleam/dict.{type Dict} import gleam/option.{type Option} import gleam/result import gleam/string_builder @target(erlang) import gleam/bit_array /// `Dynamic` data is data that we don't know the type of yet. /// We likely get data like this from interop with Erlang, or from /// IO with the outside world. /// pub type Dynamic /// Error returned when unexpected data is encountered /// pub type DecodeError { DecodeError(expected: String, found: String, path: List(String)) } pub type DecodeErrors = List(DecodeError) pub type Decoder(t) = fn(Dynamic) -> Result(t, DecodeErrors) /// Converts any Gleam data into `Dynamic` data. /// pub fn from(a) -> Dynamic { do_from(a) } @external(erlang, "gleam_stdlib", "identity") @external(javascript, "../gleam_stdlib.mjs", "identity") fn do_from(a: anything) -> Dynamic /// Unsafely casts a Dynamic value into any other type. /// /// This is an escape hatch for the type system that may be useful when wrapping /// native Erlang APIs. It is to be used as a last measure only! /// /// If you can avoid using this function, do! /// pub fn unsafe_coerce(a: Dynamic) -> anything { do_unsafe_coerce(a) } @external(erlang, "gleam_stdlib", "identity") @external(javascript, "../gleam_stdlib.mjs", "identity") fn do_unsafe_coerce(a: Dynamic) -> a /// Decodes a `Dynamic` value from a `Dynamic` value. /// /// This function doesn't seem very useful at first, but it can be convenient /// when you need to give a decoder function but you don't actually care what /// the to-decode value is. /// pub fn dynamic(value: Dynamic) -> Result(Dynamic, List(DecodeError)) { Ok(value) } /// Checks to see whether a `Dynamic` value is a bit array, and returns that bit /// array if it is. /// /// ## Examples /// /// ```gleam /// > bit_array(from("Hello")) == bit_array.from_string("Hello") /// True /// ``` /// /// ```gleam /// > bit_array(from(123)) /// Error([DecodeError(expected: "BitArray", found: "Int", path: [])]) /// ``` /// pub fn bit_array(from data: Dynamic) -> Result(BitArray, DecodeErrors) { decode_bit_array(data) } @deprecated("Please use `bit_array` instead") pub fn bit_string(from data: Dynamic) -> Result(BitArray, DecodeErrors) { bit_array(data) } @external(erlang, "gleam_stdlib", "decode_bit_array") @external(javascript, "../gleam_stdlib.mjs", "decode_bit_array") fn decode_bit_array(a: Dynamic) -> Result(BitArray, DecodeErrors) /// Checks to see whether a `Dynamic` value is a string, and returns that string if /// it is. /// /// ## Examples /// /// ```gleam /// > string(from("Hello")) /// Ok("Hello") /// ``` /// /// ```gleam /// > string(from(123)) /// Error([DecodeError(expected: "String", found: "Int", path: [])]) /// ``` /// pub fn string(from data: Dynamic) -> Result(String, DecodeErrors) { decode_string(data) } fn map_errors( result: Result(t, DecodeErrors), f: fn(DecodeError) -> DecodeError, ) -> Result(t, DecodeErrors) { result.map_error(result, list.map(_, f)) } @target(erlang) fn decode_string(data: Dynamic) -> Result(String, DecodeErrors) { bit_array(data) |> map_errors(put_expected(_, "String")) |> result.try(fn(raw) { case bit_array.to_string(raw) { Ok(string) -> Ok(string) Error(Nil) -> Error([DecodeError(expected: "String", found: "BitArray", path: [])]) } }) } @target(erlang) fn put_expected(error: DecodeError, expected: String) -> DecodeError { DecodeError(..error, expected: expected) } @target(javascript) @external(javascript, "../gleam_stdlib.mjs", "decode_string") fn decode_string(a: Dynamic) -> Result(String, DecodeErrors) /// Return a string indicating the type of the dynamic value. /// /// ```gleam /// > classify(from("Hello")) /// "String" /// ``` /// pub fn classify(data: Dynamic) -> String { do_classify(data) } @external(erlang, "gleam_stdlib", "classify_dynamic") @external(javascript, "../gleam_stdlib.mjs", "classify_dynamic") fn do_classify(a: Dynamic) -> String /// Checks to see whether a `Dynamic` value is an int, and returns that int if it /// is. /// /// ## Examples /// /// ```gleam /// > int(from(123)) /// Ok(123) /// ``` /// /// ```gleam /// > int(from("Hello")) /// Error([DecodeError(expected: "Int", found: "String", path: [])]) /// ``` /// pub fn int(from data: Dynamic) -> Result(Int, DecodeErrors) { decode_int(data) } @external(erlang, "gleam_stdlib", "decode_int") @external(javascript, "../gleam_stdlib.mjs", "decode_int") fn decode_int(a: Dynamic) -> Result(Int, DecodeErrors) /// Checks to see whether a `Dynamic` value is a float, and returns that float if /// it is. /// /// ## Examples /// /// ```gleam /// > float(from(2.0)) /// Ok(2.0) /// ``` /// /// ```gleam /// > float(from(123)) /// Error([DecodeError(expected: "Float", found: "Int", path: [])]) /// ``` /// pub fn float(from data: Dynamic) -> Result(Float, DecodeErrors) { decode_float(data) } @external(erlang, "gleam_stdlib", "decode_float") @external(javascript, "../gleam_stdlib.mjs", "decode_float") fn decode_float(a: Dynamic) -> Result(Float, DecodeErrors) /// Checks to see whether a `Dynamic` value is a bool, and returns that bool if /// it is. /// /// ## Examples /// /// ```gleam /// > bool(from(True)) /// Ok(True) /// ``` /// /// ```gleam /// > bool(from(123)) /// Error([DecodeError(expected: "Bool", found: "Int", path: [])]) /// ``` /// pub fn bool(from data: Dynamic) -> Result(Bool, DecodeErrors) { decode_bool(data) } @external(erlang, "gleam_stdlib", "decode_bool") @external(javascript, "../gleam_stdlib.mjs", "decode_bool") fn decode_bool(a: Dynamic) -> Result(Bool, DecodeErrors) /// Checks to see whether a `Dynamic` value is a list, and returns that list if it /// is. The types of the elements are not checked. /// /// If you wish to decode all the elements in the list use the `list` function /// instead. /// /// ## Examples /// /// ```gleam /// > shallow_list(from(["a", "b", "c"])) /// Ok([from("a"), from("b"), from("c")]) /// ``` /// /// ```gleam /// > shallow_list(1) /// Error([DecodeError(expected: "List", found: "Int", path: [])]) /// ``` /// pub fn shallow_list(from value: Dynamic) -> Result(List(Dynamic), DecodeErrors) { decode_list(value) } @external(erlang, "gleam_stdlib", "decode_list") @external(javascript, "../gleam_stdlib.mjs", "decode_list") fn decode_list(a: Dynamic) -> Result(List(Dynamic), DecodeErrors) @external(erlang, "gleam_stdlib", "decode_result") @external(javascript, "../gleam_stdlib.mjs", "decode_result") fn decode_result(a: Dynamic) -> Result(Result(a, e), DecodeErrors) /// Checks to see whether a `Dynamic` value is a result of a particular type, and /// returns that result if it is. /// /// The `ok` and `error` arguments are decoders for decoding the `Ok` and /// `Error` values of the result. /// /// ## Examples /// /// ```gleam /// > from(Ok(1)) /// > |> result(ok: int, error: string) /// Ok(Ok(1)) /// ``` /// /// ```gleam /// > from(Error("boom")) /// > |> result(ok: int, error: string) /// Ok(Error("boom")) /// ``` /// /// ```gleam /// > from(123) /// > |> result(ok: int, error: string) /// Error([DecodeError(expected: "Result", found: "Int", path: [])]) /// ``` /// pub fn result( ok decode_ok: Decoder(a), error decode_error: Decoder(e), ) -> Decoder(Result(a, e)) { fn(value) { use inner_result <- result.try(decode_result(value)) case inner_result { Ok(raw) -> { use value <- result.try( decode_ok(raw) |> map_errors(push_path(_, "ok")), ) Ok(Ok(value)) } Error(raw) -> { use value <- result.try( decode_error(raw) |> map_errors(push_path(_, "error")), ) Ok(Error(value)) } } } } /// Checks to see whether a `Dynamic` value is a list of a particular type, and /// returns that list if it is. /// /// The second argument is a decoder function used to decode the elements of /// the list. The list is only decoded if all elements in the list can be /// successfully decoded using this function. /// /// If you do not wish to decode all the elements in the list use the `shallow_list` /// function instead. /// /// ## Examples /// /// ```gleam /// > from(["a", "b", "c"]) /// > |> list(of: string) /// Ok(["a", "b", "c"]) /// ``` /// /// ```gleam /// > from([1, 2, 3]) /// > |> list(of: string) /// Error([DecodeError(expected: "String", found: "Int", path: ["*"])]) /// ``` /// /// ```gleam /// > from("ok") /// > |> list(of: string) /// Error([DecodeError(expected: "List", found: "String", path: [])]) /// ``` /// pub fn list( of decoder_type: fn(Dynamic) -> Result(inner, DecodeErrors), ) -> Decoder(List(inner)) { fn(dynamic) { use list <- result.try(shallow_list(dynamic)) list |> list.try_map(decoder_type) |> map_errors(push_path(_, "*")) } } /// Checks to see if a `Dynamic` value is a nullable version of a particular /// type, and returns a corresponding `Option` if it is. /// /// ## Examples /// /// ```gleam /// > from("Hello") /// > |> optional(string) /// Ok(Some("Hello")) /// ``` /// /// ```gleam /// > from("Hello") /// > |> optional(string) /// Ok(Some("Hello")) /// ``` /// /// ```gleam /// > from(atom.from_string("null")) /// > |> optional(string) /// Ok(None) /// ``` /// /// ```gleam /// > from(atom.from_string("nil")) /// > |> optional(string) /// Ok(None) /// ``` /// /// ```gleam /// > from(atom.from_string("undefined")) /// > |> optional(string) /// Ok(None) /// ``` /// /// ```gleam /// > from(123) /// > |> optional(string) /// Error([DecodeError(expected: "String", found: "Int", path: [])]) /// ``` /// pub fn optional(of decode: Decoder(inner)) -> Decoder(Option(inner)) { fn(value) { decode_optional(value, decode) } } @external(erlang, "gleam_stdlib", "decode_option") @external(javascript, "../gleam_stdlib.mjs", "decode_option") fn decode_optional(a: Dynamic, b: Decoder(a)) -> Result(Option(a), DecodeErrors) /// Checks to see if a `Dynamic` value is a map with a specific field, and returns /// the value of that field if it is. /// /// This will not succeed on a record. /// /// ## Examples /// /// ```gleam /// > import gleam/dict /// > dict.new() /// > |> dict.insert("Hello", "World") /// > |> from /// > |> field(named: "Hello", of: string) /// Ok("World") /// ``` /// /// ```gleam /// > from(123) /// > |> field("Hello", string) /// Error([DecodeError(expected: "Map", found: "Int", path: [])]) /// ``` /// pub fn field(named name: a, of inner_type: Decoder(t)) -> Decoder(t) { fn(value) { let missing_field_error = DecodeError(expected: "field", found: "nothing", path: []) use maybe_inner <- result.try(decode_field(value, name)) maybe_inner |> option.to_result([missing_field_error]) |> result.try(inner_type) |> map_errors(push_path(_, name)) } } /// Checks to see if a `Dynamic` value is a map with a specific field. /// If the map does not have the specified field, returns an `Ok(None)` instead of failing; otherwise, /// returns the decoded field wrapped in `Some(_)`. /// /// ## Examples /// /// ```gleam /// > import gleam/dict /// > dict.new() /// > |> dict.insert("Hello", "World") /// > |> from /// > |> field(named: "Hello", of: string) /// Ok(Some("World")) /// ``` /// /// ```gleam /// > import gleam/dict /// > dict.new() /// > |> from /// > |> field(named: "Hello", of: string) /// Ok(None) /// ``` /// /// ```gleam /// > from(123) /// > |> field("Hello", string) /// Error([DecodeError(expected: "Map", found: "Int", path: [])]) /// ``` /// pub fn optional_field( named name: a, of inner_type: Decoder(t), ) -> Decoder(Option(t)) { fn(value) { use maybe_inner <- result.try(decode_field(value, name)) case maybe_inner { option.None -> Ok(option.None) option.Some(dynamic_inner) -> dynamic_inner |> decode_optional(inner_type) |> map_errors(push_path(_, name)) } } } @external(erlang, "gleam_stdlib", "decode_field") @external(javascript, "../gleam_stdlib.mjs", "decode_field") fn decode_field(a: Dynamic, b: name) -> Result(Option(Dynamic), DecodeErrors) /// Checks to see if a `Dynamic` value is a tuple large enough to have a certain /// index, and returns the value of that index if it is. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2)) /// > |> element(0, int) /// Ok(from(1)) /// ``` /// /// ```gleam /// > from(#(1, 2)) /// > |> element(2, int) /// Error([ /// DecodeError( /// expected: "Tuple of at least 3 elements", /// found: "Tuple of 2 elements", /// path: [], /// ), /// ]) /// ``` /// pub fn element(at index: Int, of inner_type: Decoder(t)) -> Decoder(t) { fn(data: Dynamic) { use tuple <- result.try(decode_tuple(data)) let size = tuple_size(tuple) use data <- result.try(case index >= 0 { True -> case index < size { True -> tuple_get(tuple, index) False -> at_least_decode_tuple_error(index + 1, data) } False -> case int.absolute_value(index) <= size { True -> tuple_get(tuple, size + index) False -> at_least_decode_tuple_error(int.absolute_value(index), data) } }) inner_type(data) |> map_errors(push_path(_, index)) } } fn at_least_decode_tuple_error( size: Int, data: Dynamic, ) -> Result(a, DecodeErrors) { let s = case size { 1 -> "" _ -> "s" } let error = ["Tuple of at least ", int.to_string(size), " element", s] |> string_builder.from_strings |> string_builder.to_string |> DecodeError(found: classify(data), path: []) Error([error]) } // A tuple of unknown size type UnknownTuple @external(erlang, "gleam_stdlib", "decode_tuple") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple") fn decode_tuple(a: Dynamic) -> Result(UnknownTuple, DecodeErrors) @external(erlang, "gleam_stdlib", "decode_tuple2") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple2") fn decode_tuple2(a: Dynamic) -> Result(#(Dynamic, Dynamic), DecodeErrors) @external(erlang, "gleam_stdlib", "decode_tuple3") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple3") fn decode_tuple3( a: Dynamic, ) -> Result(#(Dynamic, Dynamic, Dynamic), DecodeErrors) @external(erlang, "gleam_stdlib", "decode_tuple4") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple4") fn decode_tuple4( a: Dynamic, ) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic), DecodeErrors) @external(erlang, "gleam_stdlib", "decode_tuple5") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple5") fn decode_tuple5( a: Dynamic, ) -> Result(#(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), DecodeErrors) @external(erlang, "gleam_stdlib", "decode_tuple6") @external(javascript, "../gleam_stdlib.mjs", "decode_tuple6") fn decode_tuple6( a: Dynamic, ) -> Result( #(Dynamic, Dynamic, Dynamic, Dynamic, Dynamic, Dynamic), DecodeErrors, ) @external(erlang, "gleam_stdlib", "tuple_get") @external(javascript, "../gleam_stdlib.mjs", "tuple_get") fn tuple_get(a: UnknownTuple, b: Int) -> Result(Dynamic, DecodeErrors) @external(erlang, "gleam_stdlib", "size_of_tuple") @external(javascript, "../gleam_stdlib.mjs", "length") fn tuple_size(a: UnknownTuple) -> Int fn tuple_errors( result: Result(a, List(DecodeError)), name: String, ) -> List(DecodeError) { case result { Ok(_) -> [] Error(errors) -> list.map(errors, push_path(_, name)) } } fn push_path(error: DecodeError, name: t) -> DecodeError { let name = from(name) let decoder = any([string, fn(x) { result.map(int(x), int.to_string) }]) let name = case decoder(name) { Ok(name) -> name Error(_) -> ["<", classify(name), ">"] |> string_builder.from_strings |> string_builder.to_string } DecodeError(..error, path: [name, ..error.path]) } /// Checks to see if a `Dynamic` value is a 2-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2)) /// > |> tuple2(int, int) /// Ok(#(1, 2)) /// ``` /// /// ```gleam /// > from(#(1, 2.0)) /// > |> tuple2(int, float) /// Ok(#(1, 2.0)) /// ``` /// /// ```gleam /// > from([1, 2]) /// > |> tuple2(int, int) /// Ok(#(1, 2)) /// ``` /// /// ```gleam /// > from([from(1), from(2.0)]) /// > |> tuple2(int, float) /// Ok(#(1, 2.0)) /// ``` /// /// ```gleam /// > from(#(1, 2, 3)) /// > |> tuple2(int, float) /// Error([ /// DecodeError(expected: "Tuple of 2 elements", found: "Tuple of 3 elements", path: []), /// ]) /// ``` /// /// ```gleam /// > from("") /// > |> tuple2(int, float) /// Error([DecodeError(expected: "Tuple of 2 elements", found: "String", path: [])]) /// ``` /// pub fn tuple2( first decode1: Decoder(a), second decode2: Decoder(b), ) -> Decoder(#(a, b)) { fn(value) { use #(a, b) <- result.try(decode_tuple2(value)) case decode1(a), decode2(b) { Ok(a), Ok(b) -> Ok(#(a, b)) a, b -> tuple_errors(a, "0") |> list.append(tuple_errors(b, "1")) |> Error } } } /// Checks to see if a `Dynamic` value is a 3-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2, 3)) /// > |> tuple3(int, int, int) /// Ok(#(1, 2, 3)) /// ``` /// /// ```gleam /// > from(#(1, 2.0, "3")) /// > |> tuple3(int, float, string) /// Ok(#(1, 2.0, "3")) /// ``` /// /// ```gleam /// > from([1, 2, 3]) /// > |> tuple3(int, int, int) /// Ok(#(1, 2, 3)) /// ``` /// /// ```gleam /// > from([from(1), from(2.0), from("3")]) /// > |> tuple3(int, float, string) /// Ok(#(1, 2.0, "3")) /// ``` /// /// ```gleam /// > from(#(1, 2)) /// > |> tuple3(int, float, string) /// Error([ /// DecodeError(expected: "Tuple of 3 elements", found: "Tuple of 2 elements", path: [])), /// ]) /// ``` /// /// ```gleam /// > from("") /// > |> tuple3(int, float, string) /// Error([ /// DecodeError(expected: "Tuple of 3 elements", found: "String", path: []), /// ]) /// ``` /// pub fn tuple3( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), ) -> Decoder(#(a, b, c)) { fn(value) { use #(a, b, c) <- result.try(decode_tuple3(value)) case decode1(a), decode2(b), decode3(c) { Ok(a), Ok(b), Ok(c) -> Ok(#(a, b, c)) a, b, c -> tuple_errors(a, "0") |> list.append(tuple_errors(b, "1")) |> list.append(tuple_errors(c, "2")) |> Error } } } /// Checks to see if a `Dynamic` value is a 4-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2, 3, 4)) /// > |> tuple4(int, int, int, int) /// Ok(#(1, 2, 3, 4)) /// ``` /// /// ```gleam /// > from(#(1, 2.0, "3", 4)) /// > |> tuple4(int, float, string, int) /// Ok(#(1, 2.0, "3", 4)) /// ``` /// /// ```gleam /// > from([1, 2, 3, 4]) /// > |> tuple4(int, int, int, int) /// Ok(#(1, 2, 3, 4)) /// ``` /// /// ```gleam /// > from([from(1), from(2.0), from("3"), from(4)]) /// > |> tuple4(int, float, string, int) /// Ok(#(1, 2.0, "3", 4)) /// ``` /// /// ```gleam /// > from(#(1, 2)) /// > |> tuple4(int, float, string, int) /// Error([ /// DecodeError(expected: "Tuple of 4 elements", found: "Tuple of 2 elements", path: []), /// ]) /// ``` /// /// ```gleam /// > from("") /// > |> tuple4(int, float, string, int) /// Error([ /// DecodeError(expected: "Tuple of 4 elements", found: "String", path: []), /// ]) /// ``` /// pub fn tuple4( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), ) -> Decoder(#(a, b, c, d)) { fn(value) { use #(a, b, c, d) <- result.try(decode_tuple4(value)) case decode1(a), decode2(b), decode3(c), decode4(d) { Ok(a), Ok(b), Ok(c), Ok(d) -> Ok(#(a, b, c, d)) a, b, c, d -> tuple_errors(a, "0") |> list.append(tuple_errors(b, "1")) |> list.append(tuple_errors(c, "2")) |> list.append(tuple_errors(d, "3")) |> Error } } } /// Checks to see if a `Dynamic` value is a 5-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2, 3, 4, 5)) /// > |> tuple5(int, int, int, int, int) /// Ok(#(1, 2, 3, 4, 5)) /// ``` /// /// ```gleam /// > from(#(1, 2.0, "3", 4, 5)) /// > |> tuple5(int, float, string, int, int) /// Ok(#(1, 2.0, "3", 4, 5)) /// ``` /// /// ```gleam /// > from([1, 2, 3, 4, 5]) /// > |> tuple5(int, int, int, int, int) /// Ok(#(1, 2, 3, 4, 5)) /// ``` /// /// ```gleam /// > from([from(1), from(2.0), from("3"), from(4), from(True)]) /// > |> tuple5(int, float, string, int, bool) /// Ok(#(1, 2.0, "3", 4, True)) /// ``` /// /// ```gleam /// > from(#(1, 2)) /// > |> tuple5(int, float, string, int, int) /// Error([ /// DecodeError(expected: "Tuple of 5 elements", found: "Tuple of 2 elements", path: [])), /// ]) /// ``` /// /// ```gleam /// > from("") /// > |> tuple5(int, float, string, int, int) /// Error([DecodeError(expected: "Tuple of 5 elements", found: "String", path: [])]) /// ``` /// pub fn tuple5( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), fifth decode5: Decoder(e), ) -> Decoder(#(a, b, c, d, e)) { fn(value) { use #(a, b, c, d, e) <- result.try(decode_tuple5(value)) case decode1(a), decode2(b), decode3(c), decode4(d), decode5(e) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e) -> Ok(#(a, b, c, d, e)) a, b, c, d, e -> tuple_errors(a, "0") |> list.append(tuple_errors(b, "1")) |> list.append(tuple_errors(c, "2")) |> list.append(tuple_errors(d, "3")) |> list.append(tuple_errors(e, "4")) |> Error } } } /// Checks to see if a `Dynamic` value is a 6-element tuple, list or array containing /// specifically typed elements. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2, 3, 4, 5, 6)) /// > |> tuple6(int, int, int, int, int, int) /// Ok(#(1, 2, 3, 4, 5, 6)) /// ``` /// /// ```gleam /// > from(#(1, 2.0, "3", 4, 5, 6)) /// > |> tuple6(int, float, string, int, int, int) /// Ok(#(1, 2.0, "3", 4, 5, 6)) /// ``` /// /// ```gleam /// > from([1, 2, 3, 4, 5, 6]) /// > |> tuple6(int, int, int, int, int, int) /// Ok(#(1, 2, 3, 4, 5, 6)) /// ``` /// /// ```gleam /// > from([from(1), from(2.0), from("3"), from(4), from(True), from(False)]) /// > |> tuple6(int, float, string, int, bool, bool) /// Ok(#(1, 2.0, "3", 4, True, False)) /// ``` /// /// ```gleam /// > from(#(1, 2)) /// > |> tuple6(int, float, string, int, int, int) /// Error([ /// DecodeError(expected: "Tuple of 6 elements", found: "Tuple of 2 elements", path: []), /// ]) /// ``` /// /// ```gleam /// > from("") /// > |> tuple6(int, float, string, int, int, int) /// Error([DecodeError(expected: "Tuple of 6 elements", found: "String", path: [])]) /// ``` /// pub fn tuple6( first decode1: Decoder(a), second decode2: Decoder(b), third decode3: Decoder(c), fourth decode4: Decoder(d), fifth decode5: Decoder(e), sixth decode6: Decoder(f), ) -> Decoder(#(a, b, c, d, e, f)) { fn(value) { use #(a, b, c, d, e, f) <- result.try(decode_tuple6(value)) case decode1(a), decode2(b), decode3(c), decode4(d), decode5(e), decode6(f) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f) -> Ok(#(a, b, c, d, e, f)) a, b, c, d, e, f -> tuple_errors(a, "0") |> list.append(tuple_errors(b, "1")) |> list.append(tuple_errors(c, "2")) |> list.append(tuple_errors(d, "3")) |> list.append(tuple_errors(e, "4")) |> list.append(tuple_errors(f, "5")) |> Error } } } /// Checks to see if a `Dynamic` value is a dict. /// /// ## Examples /// /// ```gleam /// > import gleam/dict /// > dict.new() |> from |> map(string, int) /// Ok(dict.new()) /// ``` /// /// ```gleam /// > from(1) |> map(string, int) /// Error(DecodeError(expected: "Map", found: "Int", path: [])) /// ``` /// /// ```gleam /// > from("") |> map(string, int) /// Error(DecodeError(expected: "Map", found: "String", path: [])) /// ``` /// pub fn dict( of key_type: Decoder(k), to value_type: Decoder(v), ) -> Decoder(Dict(k, v)) { fn(value) { use map <- result.try(decode_map(value)) use pairs <- result.try( map |> dict.to_list |> list.try_map(fn(pair) { let #(k, v) = pair use k <- result.try( key_type(k) |> map_errors(push_path(_, "keys")), ) use v <- result.try( value_type(v) |> map_errors(push_path(_, "values")), ) Ok(#(k, v)) }), ) Ok(dict.from_list(pairs)) } } @deprecated("Use `dict` instead") pub fn map( of key_type: Decoder(k), to value_type: Decoder(v), ) -> Decoder(Dict(k, v)) { dict(key_type, value_type) } @external(erlang, "gleam_stdlib", "decode_map") @external(javascript, "../gleam_stdlib.mjs", "decode_map") fn decode_map(a: Dynamic) -> Result(Dict(Dynamic, Dynamic), DecodeErrors) /// Joins multiple decoders into one. When run they will each be tried in turn /// until one succeeds, or they all fail. /// /// ## Examples /// /// ```gleam /// > import gleam/result /// > let bool_or_string = any(of: [ /// > string, /// > fn(x) { result.map(bool(x), fn(_) { "a bool" }) } /// > ]) /// > bool_or_string(from("ok")) /// Ok("ok") /// ``` /// /// ```gleam /// > bool_or_string(from(True)) /// Ok("a bool") /// ``` /// /// ```gleam /// > bool_or_string(from(1)) /// Error(DecodeError(expected: "another type", found: "Int", path: [])) /// ``` /// pub fn any(of decoders: List(Decoder(t))) -> Decoder(t) { fn(data) { case decoders { [] -> Error([ DecodeError(found: classify(data), expected: "another type", path: []), ]) [decoder, ..decoders] -> case decoder(data) { Ok(decoded) -> Ok(decoded) Error(_) -> any(decoders)(data) } } } } /// Decode 1 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.0, "3")) /// > |> decode1(MyRecord, element(0, int)) /// Ok(MyRecord(1)) /// ``` /// /// ```gleam /// > from(#("", "", "")) /// > |> decode1(MyRecord, element(0, int)) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// ]) /// ``` /// pub fn decode1(constructor: fn(t1) -> t, t1: Decoder(t1)) -> Decoder(t) { fn(value) { case t1(value) { Ok(a) -> Ok(constructor(a)) a -> Error(all_errors(a)) } } } /// Decode 2 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.0, "3")) /// > |> decode2(MyRecord, element(0, int), element(1, float)) /// Ok(MyRecord(1, 2.0)) /// ``` /// /// ```gleam /// > from(#("", "", "")) /// > |> decode2(MyRecord, element(0, int), element(1, float)) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode2( constructor: fn(t1, t2) -> t, t1: Decoder(t1), t2: Decoder(t2), ) -> Decoder(t) { fn(value) { case t1(value), t2(value) { Ok(a), Ok(b) -> Ok(constructor(a, b)) a, b -> Error(list.concat([all_errors(a), all_errors(b)])) } } } /// Decode 3 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.0, "3")) /// > |> decode3(MyRecord, element(0, int), element(1, float), element(2, string)) /// Ok(MyRecord(1, 2.0, "3")) /// ``` /// /// ```gleam /// > from(#("", "", "")) /// > |> decode3(MyRecord, element(0, int), element(1, float), element(2, string)) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode3( constructor: fn(t1, t2, t3) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), ) -> Decoder(t) { fn(value) { case t1(value), t2(value), t3(value) { Ok(a), Ok(b), Ok(c) -> Ok(constructor(a, b, c)) a, b, c -> Error(list.concat([all_errors(a), all_errors(b), all_errors(c)])) } } } /// Decode 4 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4")) /// > |> decode4( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4")) /// ``` /// /// ```gleam /// > from(#("", "", "", "")) /// > |> decode4( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode4( constructor: fn(t1, t2, t3, t4) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x) { Ok(a), Ok(b), Ok(c), Ok(d) -> Ok(constructor(a, b, c, d)) a, b, c, d -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), ])) } } } /// Decode 5 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4", "5")) /// > |> decode5( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4", "5")) /// ``` /// /// ```gleam /// > from(#("", "", "", "", "")) /// > |> decode5( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode5( constructor: fn(t1, t2, t3, t4, t5) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), t5: Decoder(t5), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x), t5(x) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e) -> Ok(constructor(a, b, c, d, e)) a, b, c, d, e -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), all_errors(e), ])) } } } /// Decode 6 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4", "5", "6")) /// > |> decode6( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4", "5", "6")) /// ``` /// /// ```gleam /// > from(#("", "", "", "", "", "")) /// > |> decode6( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode6( constructor: fn(t1, t2, t3, t4, t5, t6) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), t5: Decoder(t5), t6: Decoder(t6), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f) -> Ok(constructor(a, b, c, d, e, f)) a, b, c, d, e, f -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), all_errors(e), all_errors(f), ])) } } } /// Decode 7 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4", "5", "6")) /// > |> decode7( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7")) /// ``` /// /// ```gleam /// > from(#("", "", "", "", "", "", "")) /// > |> decode7( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode7( constructor: fn(t1, t2, t3, t4, t5, t6, t7) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), t5: Decoder(t5), t6: Decoder(t6), t7: Decoder(t7), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g) -> Ok(constructor(a, b, c, d, e, f, g)) a, b, c, d, e, f, g -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), all_errors(e), all_errors(f), all_errors(g), ])) } } } /// Decode 8 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4", "5", "6", "7", "8")) /// > |> decode8( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > element(7, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7", "8")) /// ``` /// /// ```gleam /// > from(#("", "", "", "", "", "", "", "")) /// > |> decode8( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > element(7, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode8( constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), t5: Decoder(t5), t6: Decoder(t6), t7: Decoder(t7), t8: Decoder(t8), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x), t8(x) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g), Ok(h) -> Ok(constructor(a, b, c, d, e, f, g, h)) a, b, c, d, e, f, g, h -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), all_errors(e), all_errors(f), all_errors(g), all_errors(h), ])) } } } /// Decode 9 values from a `Dynamic` value. /// /// ## Examples /// /// ```gleam /// > from(#(1, 2.1, "3", "4", "5", "6", "7", "8", "9")) /// > |> decode9( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > element(7, string), /// > element(8, string), /// > ) /// Ok(MyRecord(1, 2.1, "3", "4", "5", "6", "7", "8", "9")) /// ``` /// /// ```gleam /// > from(#("", "", "", "", "", "", "", "", "")) /// > |> decode9( /// > MyRecord, /// > element(0, int), /// > element(1, float), /// > element(2, string), /// > element(3, string), /// > element(4, string), /// > element(5, string), /// > element(6, string), /// > element(7, string), /// > element(8, string), /// > ) /// Error([ /// DecodeError(expected: "Int", found: "String", path: ["0"]), /// DecodeError(expected: "Float", found: "String", path: ["1"]), /// ]) /// ``` /// pub fn decode9( constructor: fn(t1, t2, t3, t4, t5, t6, t7, t8, t9) -> t, t1: Decoder(t1), t2: Decoder(t2), t3: Decoder(t3), t4: Decoder(t4), t5: Decoder(t5), t6: Decoder(t6), t7: Decoder(t7), t8: Decoder(t8), t9: Decoder(t9), ) -> Decoder(t) { fn(x: Dynamic) { case t1(x), t2(x), t3(x), t4(x), t5(x), t6(x), t7(x), t8(x), t9(x) { Ok(a), Ok(b), Ok(c), Ok(d), Ok(e), Ok(f), Ok(g), Ok(h), Ok(i) -> Ok(constructor(a, b, c, d, e, f, g, h, i)) a, b, c, d, e, f, g, h, i -> Error(list.concat([ all_errors(a), all_errors(b), all_errors(c), all_errors(d), all_errors(e), all_errors(f), all_errors(g), all_errors(h), all_errors(i), ])) } } } fn all_errors(result: Result(a, List(DecodeError))) -> List(DecodeError) { case result { Ok(_) -> [] Error(errors) -> errors } }