Initial commit

This commit is contained in:
Hugo Mårdbrink 2025-11-30 15:44:22 +01:00
commit a6272848f9
379 changed files with 74829 additions and 0 deletions

View file

@ -0,0 +1,947 @@
import {
Ok,
Error,
toList,
Empty as $Empty,
prepend as listPrepend,
CustomType as $CustomType,
isEqual,
} from "../../gleam.mjs";
import * as $bit_array from "../../gleam/bit_array.mjs";
import * as $dict from "../../gleam/dict.mjs";
import * as $dynamic from "../../gleam/dynamic.mjs";
import * as $int from "../../gleam/int.mjs";
import * as $list from "../../gleam/list.mjs";
import * as $option from "../../gleam/option.mjs";
import { None, Some } from "../../gleam/option.mjs";
import {
index as bare_index,
int as dynamic_int,
float as dynamic_float,
bit_array as dynamic_bit_array,
list as decode_list,
dict as decode_dict,
identity as cast,
is_null,
string as dynamic_string,
} from "../../gleam_stdlib.mjs";
export class DecodeError extends $CustomType {
constructor(expected, found, path) {
super();
this.expected = expected;
this.found = found;
this.path = path;
}
}
export const DecodeError$DecodeError = (expected, found, path) =>
new DecodeError(expected, found, path);
export const DecodeError$isDecodeError = (value) =>
value instanceof DecodeError;
export const DecodeError$DecodeError$expected = (value) => value.expected;
export const DecodeError$DecodeError$0 = (value) => value.expected;
export const DecodeError$DecodeError$found = (value) => value.found;
export const DecodeError$DecodeError$1 = (value) => value.found;
export const DecodeError$DecodeError$path = (value) => value.path;
export const DecodeError$DecodeError$2 = (value) => value.path;
class Decoder extends $CustomType {
constructor(function$) {
super();
this.function = function$;
}
}
/**
* Run a decoder on a `Dynamic` value, decoding the value if it is of the
* desired type, or returning errors.
*
* # Examples
*
* ```gleam
* let decoder = {
* use name <- decode.field("email", decode.string)
* use email <- decode.field("password", decode.string)
* decode.success(SignUp(name: name, email: email))
* }
*
* decode.run(data, decoder)
* ```
*/
export function run(data, decoder) {
let $ = decoder.function(data);
let maybe_invalid_data;
let errors;
maybe_invalid_data = $[0];
errors = $[1];
if (errors instanceof $Empty) {
return new Ok(maybe_invalid_data);
} else {
return new Error(errors);
}
}
/**
* Finalise a decoder having successfully extracted a value.
*
* # Examples
*
* ```gleam
* let data = dynamic.properties([
* #(dynamic.string("email"), dynamic.string("lucy@example.com")),
* #(dynamic.string("name"), dynamic.string("Lucy")),
* ]))
*
* let decoder = {
* use name <- decode.field("name", string)
* use email <- decode.field("email", string)
* decode.success(SignUp(name: name, email: email))
* }
*
* let result = decode.run(data, decoder)
* assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
* ```
*/
export function success(data) {
return new Decoder((_) => { return [data, toList([])]; });
}
function decode_dynamic(data) {
return [data, toList([])];
}
/**
* Apply a transformation function to any value decoded by the decoder.
*
* # Examples
*
* ```gleam
* let decoder = decode.int |> decode.map(int.to_string)
* let result = decode.run(dynamic.int(1000), decoder)
* assert result == Ok("1000")
* ```
*/
export function map(decoder, transformer) {
return new Decoder(
(d) => {
let $ = decoder.function(d);
let data;
let errors;
data = $[0];
errors = $[1];
return [transformer(data), errors];
},
);
}
/**
* Apply a transformation function to any errors returned by the decoder.
*/
export function map_errors(decoder, transformer) {
return new Decoder(
(d) => {
let $ = decoder.function(d);
let data;
let errors;
data = $[0];
errors = $[1];
return [data, transformer(errors)];
},
);
}
/**
* Create a new decoder based upon the value of a previous decoder.
*
* This may be useful to run one previous decoder to use in further decoding.
*/
export function then$(decoder, next) {
return new Decoder(
(dynamic_data) => {
let $ = decoder.function(dynamic_data);
let data;
let errors;
data = $[0];
errors = $[1];
let decoder$1 = next(data);
let $1 = decoder$1.function(dynamic_data);
let layer;
let data$1;
layer = $1;
data$1 = $1[0];
if (errors instanceof $Empty) {
return layer;
} else {
return [data$1, errors];
}
},
);
}
function run_decoders(loop$data, loop$failure, loop$decoders) {
while (true) {
let data = loop$data;
let failure = loop$failure;
let decoders = loop$decoders;
if (decoders instanceof $Empty) {
return failure;
} else {
let decoder = decoders.head;
let decoders$1 = decoders.tail;
let $ = decoder.function(data);
let layer;
let errors;
layer = $;
errors = $[1];
if (errors instanceof $Empty) {
return layer;
} else {
loop$data = data;
loop$failure = failure;
loop$decoders = decoders$1;
}
}
}
}
/**
* Create a new decoder from several other decoders. Each of the inner
* decoders is run in turn, and the value from the first to succeed is used.
*
* If no decoder succeeds then the errors from the first decoder is used.
* If you wish for different errors then you may wish to use the
* `collapse_errors` or `map_errors` functions.
*
* # Examples
*
* ```gleam
* let decoder = decode.one_of(decode.string, or: [
* decode.int |> decode.map(int.to_string),
* decode.float |> decode.map(float.to_string),
* ])
* decode.run(dynamic.int(1000), decoder)
* // -> Ok("1000")
* ```
*/
export function one_of(first, alternatives) {
return new Decoder(
(dynamic_data) => {
let $ = first.function(dynamic_data);
let layer;
let errors;
layer = $;
errors = $[1];
if (errors instanceof $Empty) {
return layer;
} else {
return run_decoders(dynamic_data, layer, alternatives);
}
},
);
}
/**
* Create a decoder that can refer to itself, useful for decoding deeply
* nested data.
*
* Attempting to create a recursive decoder without this function could result
* in an infinite loop. If you are using `field` or other `use`able functions
* then you may not need to use this function.
*
* ```gleam
* type Nested {
* Nested(List(Nested))
* Value(String)
* }
*
* fn nested_decoder() -> decode.Decoder(Nested) {
* use <- decode.recursive
* decode.one_of(decode.string |> decode.map(Value), [
* decode.list(nested_decoder()) |> decode.map(Nested),
* ])
* }
* ```
*/
export function recursive(inner) {
return new Decoder(
(data) => {
let decoder = inner();
return decoder.function(data);
},
);
}
/**
* A decoder that decodes nullable values of a type decoded by with a given
* decoder.
*
* This function can handle common representations of null on all runtimes, such as
* `nil`, `null`, and `undefined` on Erlang, and `undefined` and `null` on
* JavaScript.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.int(100), decode.optional(decode.int))
* assert result == Ok(option.Some(100))
* ```
*
* ```gleam
* let result = decode.run(dynamic.nil(), decode.optional(decode.int))
* assert result == Ok(option.None)
* ```
*/
export function optional(inner) {
return new Decoder(
(data) => {
let $ = is_null(data);
if ($) {
return [new $option.None(), toList([])];
} else {
let $1 = inner.function(data);
let data$1;
let errors;
data$1 = $1[0];
errors = $1[1];
return [new $option.Some(data$1), errors];
}
},
);
}
/**
* A decoder that decodes `Dynamic` values. This decoder never returns an error.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.float(3.14), decode.dynamic)
* assert result == Ok(dynamic.float(3.14))
* ```
*/
export const dynamic = /* @__PURE__ */ new Decoder(decode_dynamic);
/**
* Construct a decode error for some unexpected dynamic data.
*/
export function decode_error(expected, found) {
return toList([
new DecodeError(expected, $dynamic.classify(found), toList([])),
]);
}
function run_dynamic_function(data, name, f) {
let $ = f(data);
if ($ instanceof Ok) {
let data$1 = $[0];
return [data$1, toList([])];
} else {
let zero = $[0];
return [
zero,
toList([new DecodeError(name, $dynamic.classify(data), toList([]))]),
];
}
}
function decode_bool(data) {
let $ = isEqual(cast(true), data);
if ($) {
return [true, toList([])];
} else {
let $1 = isEqual(cast(false), data);
if ($1) {
return [false, toList([])];
} else {
return [false, decode_error("Bool", data)];
}
}
}
function decode_int(data) {
return run_dynamic_function(data, "Int", dynamic_int);
}
function decode_float(data) {
return run_dynamic_function(data, "Float", dynamic_float);
}
function decode_bit_array(data) {
return run_dynamic_function(data, "BitArray", dynamic_bit_array);
}
/**
* Replace all errors produced by a decoder with one single error for a named
* expected type.
*
* This function may be useful if you wish to simplify errors before
* presenting them to a user, particularly when using the `one_of` function.
*
* # Examples
*
* ```gleam
* let decoder = decode.string |> decode.collapse_errors("MyThing")
* let result = decode.run(dynamic.int(1000), decoder)
* assert result == Error([DecodeError("MyThing", "Int", [])])
* ```
*/
export function collapse_errors(decoder, name) {
return new Decoder(
(dynamic_data) => {
let $ = decoder.function(dynamic_data);
let layer;
let data;
let errors;
layer = $;
data = $[0];
errors = $[1];
if (errors instanceof $Empty) {
return layer;
} else {
return [data, decode_error(name, dynamic_data)];
}
},
);
}
/**
* Define a decoder that always fails. The parameter for this function is the
* name of the type that has failed to decode.
*/
export function failure(zero, expected) {
return new Decoder((d) => { return [zero, decode_error(expected, d)]; });
}
/**
* Create a decoder for a new data type from a decoding function.
*
* This function is used for new primitive types. For example, you might
* define a decoder for Erlang's pid type.
*
* A default "zero" value is also required to make a decoder. When this
* decoder is used as part of a larger decoder this zero value used as
* a placeholder so that the rest of the decoder can continue to run and
* collect all decoding errors.
*
* If you were to make a decoder for the `String` type (rather than using the
* build-in `string` decoder) you would define it like so:
*
* ```gleam
* pub fn string_decoder() -> decode.Decoder(String) {
* let default = ""
* decode.new_primitive_decoder("String", fn(data) {
* case dynamic.string(data) {
* Ok(x) -> Ok(x)
* Error(_) -> Error(default)
* }
* })
* }
* ```
*/
export function new_primitive_decoder(name, decoding_function) {
return new Decoder(
(d) => {
let $ = decoding_function(d);
if ($ instanceof Ok) {
let t = $[0];
return [t, toList([])];
} else {
let zero = $[0];
return [
zero,
toList([new DecodeError(name, $dynamic.classify(d), toList([]))]),
];
}
},
);
}
/**
* A decoder that decodes `Bool` values.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.bool(True), decode.bool)
* assert result == Ok(True)
* ```
*/
export const bool = /* @__PURE__ */ new Decoder(decode_bool);
/**
* A decoder that decodes `Int` values.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.int(147), decode.int)
* assert result == Ok(147)
* ```
*/
export const int = /* @__PURE__ */ new Decoder(decode_int);
/**
* A decoder that decodes `Float` values.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.float(3.14), decode.float)
* assert result == Ok(3.14)
* ```
*/
export const float = /* @__PURE__ */ new Decoder(decode_float);
/**
* A decoder that decodes `BitArray` values. This decoder never returns an error.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.bit_array(<<5, 7>>), decode.bit_array)
* assert result == Ok(<<5, 7>>)
* ```
*/
export const bit_array = /* @__PURE__ */ new Decoder(decode_bit_array);
function decode_string(data) {
return run_dynamic_function(data, "String", dynamic_string);
}
/**
* A decoder that decodes `String` values.
*
* # Examples
*
* ```gleam
* let result = decode.run(dynamic.string("Hello!"), decode.string)
* assert result == Ok("Hello!")
* ```
*/
export const string = /* @__PURE__ */ new Decoder(decode_string);
function fold_dict(acc, key, value, key_decoder, value_decoder) {
let $ = key_decoder(key);
let $1 = $[1];
if ($1 instanceof $Empty) {
let key$1 = $[0];
let $2 = value_decoder(value);
let $3 = $2[1];
if ($3 instanceof $Empty) {
let value$1 = $2[0];
let dict$1 = $dict.insert(acc[0], key$1, value$1);
return [dict$1, acc[1]];
} else {
let errors = $3;
return push_path([$dict.new$(), errors], toList(["values"]));
}
} else {
let errors = $1;
return push_path([$dict.new$(), errors], toList(["keys"]));
}
}
/**
* A decoder that decodes dicts where all keys and vales are decoded with
* given decoders.
*
* # Examples
*
* ```gleam
* let values = dynamic.properties([
* #(dynamic.string("one"), dynamic.int(1)),
* #(dynamic.string("two"), dynamic.int(2)),
* ])
*
* let result =
* decode.run(values, decode.dict(decode.string, decode.int))
* assert result == Ok(values)
* ```
*/
export function dict(key, value) {
return new Decoder(
(data) => {
let $ = decode_dict(data);
if ($ instanceof Ok) {
let dict$1 = $[0];
return $dict.fold(
dict$1,
[$dict.new$(), toList([])],
(a, k, v) => {
let $1 = a[1];
if ($1 instanceof $Empty) {
return fold_dict(a, k, v, key.function, value.function);
} else {
return a;
}
},
);
} else {
return [$dict.new$(), decode_error("Dict", data)];
}
},
);
}
/**
* A decoder that decodes lists where all elements are decoded with a given
* decoder.
*
* # Examples
*
* ```gleam
* let result =
* [1, 2, 3]
* |> list.map(dynamic.int)
* |> dynamic.list
* |> decode.run(decode.list(of: decode.int))
* assert result == Ok([1, 2, 3])
* ```
*/
export function list(inner) {
return new Decoder(
(data) => {
return decode_list(
data,
inner.function,
(p, k) => { return push_path(p, toList([k])); },
0,
toList([]),
);
},
);
}
function push_path(layer, path) {
let decoder = one_of(
string,
toList([
(() => {
let _pipe = int;
return map(_pipe, $int.to_string);
})(),
]),
);
let path$1 = $list.map(
path,
(key) => {
let key$1 = cast(key);
let $ = run(key$1, decoder);
if ($ instanceof Ok) {
let key$2 = $[0];
return key$2;
} else {
return ("<" + $dynamic.classify(key$1)) + ">";
}
},
);
let errors = $list.map(
layer[1],
(error) => {
return new DecodeError(
error.expected,
error.found,
$list.append(path$1, error.path),
);
},
);
return [layer[0], errors];
}
function index(
loop$path,
loop$position,
loop$inner,
loop$data,
loop$handle_miss
) {
while (true) {
let path = loop$path;
let position = loop$position;
let inner = loop$inner;
let data = loop$data;
let handle_miss = loop$handle_miss;
if (path instanceof $Empty) {
let _pipe = data;
let _pipe$1 = inner(_pipe);
return push_path(_pipe$1, $list.reverse(position));
} else {
let key = path.head;
let path$1 = path.tail;
let $ = bare_index(data, key);
if ($ instanceof Ok) {
let $1 = $[0];
if ($1 instanceof Some) {
let data$1 = $1[0];
loop$path = path$1;
loop$position = listPrepend(key, position);
loop$inner = inner;
loop$data = data$1;
loop$handle_miss = handle_miss;
} else {
return handle_miss(data, listPrepend(key, position));
}
} else {
let kind = $[0];
let $1 = inner(data);
let default$;
default$ = $1[0];
let _pipe = [
default$,
toList([new DecodeError(kind, $dynamic.classify(data), toList([]))]),
];
return push_path(_pipe, $list.reverse(position));
}
}
}
}
/**
* The same as [`field`](#field), except taking a path to the value rather
* than a field name.
*
* This function will index into dictionaries with any key type, and if the key is
* an int then it'll also index into Erlang tuples and JavaScript arrays, and
* the first eight elements of Gleam lists.
*
* # Examples
*
* ```gleam
* let data = dynamic.properties([
* #(dynamic.string("data"), dynamic.properties([
* #(dynamic.string("email"), dynamic.string("lucy@example.com")),
* #(dynamic.string("name"), dynamic.string("Lucy")),
* ])
* ]))
*
* let decoder = {
* use name <- decode.subfield(["data", "name"], decode.string)
* use email <- decode.subfield(["data", "email"], decode.string)
* decode.success(SignUp(name: name, email: email))
* }
* let result = decode.run(data, decoder)
* assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
* ```
*/
export function subfield(field_path, field_decoder, next) {
return new Decoder(
(data) => {
let $ = index(
field_path,
toList([]),
field_decoder.function,
data,
(data, position) => {
let $1 = field_decoder.function(data);
let default$;
default$ = $1[0];
let _pipe = [
default$,
toList([new DecodeError("Field", "Nothing", toList([]))]),
];
return push_path(_pipe, $list.reverse(position));
},
);
let out;
let errors1;
out = $[0];
errors1 = $[1];
let $1 = next(out).function(data);
let out$1;
let errors2;
out$1 = $1[0];
errors2 = $1[1];
return [out$1, $list.append(errors1, errors2)];
},
);
}
/**
* A decoder that decodes a value that is nested within other values. For
* example, decoding a value that is within some deeply nested JSON objects.
*
* This function will index into dictionaries with any key type, and if the key is
* an int then it'll also index into Erlang tuples and JavaScript arrays, and
* the first eight elements of Gleam lists.
*
* # Examples
*
* ```gleam
* let decoder = decode.at(["one", "two"], decode.int)
*
* let data = dynamic.properties([
* #(dynamic.string("one"), dynamic.properties([
* #(dynamic.string("two"), dynamic.int(1000)),
* ])),
* ]))
*
*
* decode.run(data, decoder)
* // -> Ok(1000)
* ```
*
* ```gleam
* dynamic.nil()
* |> decode.run(decode.optional(decode.int))
* // -> Ok(option.None)
* ```
*/
export function at(path, inner) {
return new Decoder(
(data) => {
return index(
path,
toList([]),
inner.function,
data,
(data, position) => {
let $ = inner.function(data);
let default$;
default$ = $[0];
let _pipe = [
default$,
toList([new DecodeError("Field", "Nothing", toList([]))]),
];
return push_path(_pipe, $list.reverse(position));
},
);
},
);
}
/**
* Run a decoder on a field of a `Dynamic` value, decoding the value if it is
* of the desired type, or returning errors. An error is returned if there is
* no field for the specified key.
*
* This function will index into dictionaries with any key type, and if the key is
* an int then it'll also index into Erlang tuples and JavaScript arrays, and
* the first eight elements of Gleam lists.
*
* # Examples
*
* ```gleam
* let data = dynamic.properties([
* #(dynamic.string("email"), dynamic.string("lucy@example.com")),
* #(dynamic.string("name"), dynamic.string("Lucy")),
* ]))
*
* let decoder = {
* use name <- decode.field("name", string)
* use email <- decode.field("email", string)
* decode.success(SignUp(name: name, email: email))
* }
*
* let result = decode.run(data, decoder)
* assert result == Ok(SignUp(name: "Lucy", email: "lucy@example.com"))
* ```
*
* If you wish to decode a value that is more deeply nested within the dynamic
* data, see [`subfield`](#subfield) and [`at`](#at).
*
* If you wish to return a default in the event that a field is not present,
* see [`optional_field`](#optional_field) and / [`optionally_at`](#optionally_at).
*/
export function field(field_name, field_decoder, next) {
return subfield(toList([field_name]), field_decoder, next);
}
/**
* Run a decoder on a field of a `Dynamic` value, decoding the value if it is
* of the desired type, or returning errors. The given default value is
* returned if there is no field for the specified key.
*
* This function will index into dictionaries with any key type, and if the key is
* an int then it'll also index into Erlang tuples and JavaScript arrays, and
* the first eight elements of Gleam lists.
*
* # Examples
*
* ```gleam
* let data = dynamic.properties([
* #(dynamic.string("name"), dynamic.string("Lucy")),
* ]))
*
* let decoder = {
* use name <- decode.field("name", string)
* use email <- decode.optional_field("email", "n/a", string)
* decode.success(SignUp(name: name, email: email))
* }
*
* let result = decode.run(data, decoder)
* assert result == Ok(SignUp(name: "Lucy", email: "n/a"))
* ```
*/
export function optional_field(key, default$, field_decoder, next) {
return new Decoder(
(data) => {
let _block;
let _block$1;
let $1 = bare_index(data, key);
if ($1 instanceof Ok) {
let $2 = $1[0];
if ($2 instanceof Some) {
let data$1 = $2[0];
_block$1 = field_decoder.function(data$1);
} else {
_block$1 = [default$, toList([])];
}
} else {
let kind = $1[0];
_block$1 = [
default$,
toList([new DecodeError(kind, $dynamic.classify(data), toList([]))]),
];
}
let _pipe = _block$1;
_block = push_path(_pipe, toList([key]));
let $ = _block;
let out;
let errors1;
out = $[0];
errors1 = $[1];
let $2 = next(out).function(data);
let out$1;
let errors2;
out$1 = $2[0];
errors2 = $2[1];
return [out$1, $list.append(errors1, errors2)];
},
);
}
/**
* A decoder that decodes a value that is nested within other values. For
* example, decoding a value that is within some deeply nested JSON objects.
*
* This function will index into dictionaries with any key type, and if the key is
* an int then it'll also index into Erlang tuples and JavaScript arrays, and
* the first eight elements of Gleam lists.
*
* # Examples
*
* ```gleam
* let decoder = decode.optionally_at(["one", "two"], 100, decode.int)
*
* let data = dynamic.properties([
* #(dynamic.string("one"), dynamic.properties([])),
* ]))
*
*
* decode.run(data, decoder)
* // -> Ok(100)
* ```
*/
export function optionally_at(path, default$, inner) {
return new Decoder(
(data) => {
return index(
path,
toList([]),
inner.function,
data,
(_, _1) => { return [default$, toList([])]; },
);
},
);
}