Packages

Utilities to create encoders and decoders from basic and complex types

Current section

Files

Jump to
convert src convert.erl
Raw

src/convert.erl

-module(convert).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-define(FILEPATH, "src/convert.gleam").
-export([object/1, success/1, encode/1, decode/1, string/0, bool/0, float/0, int/0, dynamic/0, bit_array/0, null/0, list/1, result/2, field/4, optional/1, dict/2, enum/2, map/4, type_def/1, default_value/1]).
-export_type([glitr_type/0, glitr_value/0, converter/1, partial_converter/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-type glitr_type() :: string |
bool |
float |
int |
dynamic |
bit_array |
null |
{list, glitr_type()} |
{dict, glitr_type(), glitr_type()} |
{object, list({binary(), glitr_type()})} |
{optional, glitr_type()} |
{result, glitr_type(), glitr_type()} |
{enum, list({binary(), glitr_type()})}.
-type glitr_value() :: {string_value, binary()} |
{bool_value, boolean()} |
{float_value, float()} |
{int_value, integer()} |
{dynamic_value, gleam@dynamic:dynamic_()} |
{bit_array_value, bitstring()} |
null_value |
{list_value, list(glitr_value())} |
{dict_value, gleam@dict:dict(glitr_value(), glitr_value())} |
{object_value, list({binary(), glitr_value()})} |
{optional_value, gleam@option:option(glitr_value())} |
{result_value, {ok, glitr_value()} | {error, glitr_value()}} |
{enum_value, binary(), glitr_value()}.
-opaque converter(DTS) :: {converter,
fun((DTS) -> glitr_value()),
fun((glitr_value()) -> {ok, DTS} |
{error, list(gleam@dynamic@decode:decode_error())}),
glitr_type(),
DTS}.
-opaque partial_converter(DTT) :: {partial_converter,
fun((DTT) -> glitr_value()),
fun((glitr_value()) -> {ok, DTT} |
{error, list(gleam@dynamic@decode:decode_error())}),
list({binary(), glitr_type()}),
{ok, DTT} | {error, list(gleam@dynamic@decode:decode_error())}}.
-file("src/convert.gleam", 84).
?DOC(
" Create a Converter from a PartialConverter\n"
" \n"
" Example:\n"
" ```\n"
" type Person {\n"
" Person(name: String, age: Int)\n"
" }\n"
" \n"
" let convert = object({\n"
" use name <- field(\"name\", fn(v: Person) { Ok(v.name) }, string())\n"
" use age <- field(\"age\", fn(v: Person) { Ok(v.age) }, int())\n"
" success(Person(name:, age:))\n"
" })\n"
" ```\n"
).
-spec object(partial_converter(DTU)) -> converter(DTU).
object(Converter) ->
Default_value@1 = case erlang:element(5, Converter) of
{ok, Default_value} -> Default_value;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"convert"/utf8>>,
function => <<"object"/utf8>>,
line => 85,
value => _assert_fail,
start => 2558,
'end' => 2612,
pattern_start => 2569,
pattern_end => 2586})
end,
{converter,
erlang:element(2, Converter),
erlang:element(3, Converter),
{object, erlang:element(4, Converter)},
Default_value@1}.
-file("src/convert.gleam", 157).
?DOC(
" Used to initialize a PartialConverter \n"
" See `object` for its usage details\n"
).
-spec success(DUE) -> partial_converter(DUE).
success(C) ->
{partial_converter,
fun(_) -> {object_value, []} end,
fun(_) -> {ok, C} end,
[],
{ok, C}}.
-file("src/convert.gleam", 522).
-spec get_type(glitr_value()) -> binary().
get_type(Val) ->
case Val of
{bool_value, _} ->
<<"BoolValue"/utf8>>;
{dict_value, _} ->
<<"DictValue"/utf8>>;
{enum_value, _, _} ->
<<"EnumValue"/utf8>>;
{float_value, _} ->
<<"FloatValue"/utf8>>;
{int_value, _} ->
<<"IntValue"/utf8>>;
{dynamic_value, _} ->
<<"DynamicValue"/utf8>>;
{bit_array_value, _} ->
<<"BitArrayValue"/utf8>>;
{list_value, _} ->
<<"ListValue"/utf8>>;
null_value ->
<<"NullValue"/utf8>>;
{object_value, _} ->
<<"ObjectValue"/utf8>>;
{optional_value, _} ->
<<"OptionalValue"/utf8>>;
{result_value, _} ->
<<"ResultValue"/utf8>>;
{string_value, _} ->
<<"StringValue"/utf8>>
end.
-file("src/convert.gleam", 542).
?DOC(
" Encode a value into the corresponding GlitrValue using the converter. \n"
" If the converter isn't valid, a NullValue is returned.\n"
).
-spec encode(converter(DVU)) -> fun((DVU) -> glitr_value()).
encode(Converter) ->
erlang:element(2, Converter).
-file("src/convert.gleam", 547).
?DOC(" Decode a GlitrValue using the provided converter.\n").
-spec decode(converter(DVW)) -> fun((glitr_value()) -> {ok, DVW} |
{error, list(gleam@dynamic@decode:decode_error())}).
decode(Converter) ->
erlang:element(3, Converter).
-file("src/convert.gleam", 162).
?DOC(" Basic converter for a String value\n").
-spec string() -> converter(binary()).
string() ->
{converter, fun(V) -> {string_value, V} end, fun(V@1) -> case V@1 of
{string_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"StringValue"/utf8>>,
get_type(Other),
[]}]}
end end, string, <<""/utf8>>}.
-file("src/convert.gleam", 177).
?DOC(" Basic converter for a Bool value\n").
-spec bool() -> converter(boolean()).
bool() ->
{converter, fun(V) -> {bool_value, V} end, fun(V@1) -> case V@1 of
{bool_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"BoolValue"/utf8>>,
get_type(Other),
[]}]}
end end, bool, false}.
-file("src/convert.gleam", 192).
?DOC(" Basic converter for a Float value\n").
-spec float() -> converter(float()).
float() ->
{converter, fun(V) -> {float_value, V} end, fun(V@1) -> case V@1 of
{float_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"FloatValue"/utf8>>,
get_type(Other),
[]}]}
end end, float, +0.0}.
-file("src/convert.gleam", 207).
?DOC(" Basic converter for a Int value\n").
-spec int() -> converter(integer()).
int() ->
{converter, fun(V) -> {int_value, V} end, fun(V@1) -> case V@1 of
{int_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"IntValue"/utf8>>,
get_type(Other),
[]}]}
end end, int, 0}.
-file("src/convert.gleam", 222).
?DOC(" Basic converter for Dynamic values\n").
-spec dynamic() -> converter(gleam@dynamic:dynamic_()).
dynamic() ->
{converter, fun(V) -> {dynamic_value, V} end, fun(V@1) -> case V@1 of
{dynamic_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"DynamicValue"/utf8>>,
get_type(Other),
[]}]}
end end, dynamic, gleam@dynamic:nil()}.
-file("src/convert.gleam", 238).
?DOC(" Basic converter for BitArray values\n").
-spec bit_array() -> converter(bitstring()).
bit_array() ->
{converter, fun(V) -> {bit_array_value, V} end, fun(V@1) -> case V@1 of
{bit_array_value, Val} ->
{ok, Val};
Other ->
{error,
[{decode_error,
<<"BitArrayValue"/utf8>>,
get_type(Other),
[]}]}
end end, bit_array, <<>>}.
-file("src/convert.gleam", 254).
?DOC(" Basic converter for a Nil value\n").
-spec null() -> converter(nil).
null() ->
{converter, fun(_) -> null_value end, fun(V) -> case V of
null_value ->
{ok, nil};
Other ->
{error,
[{decode_error,
<<"NullValue"/utf8>>,
get_type(Other),
[]}]}
end end, null, nil}.
-file("src/convert.gleam", 271).
?DOC(
" Basic converter for a List value. \n"
" \n"
" `of` is a converter for the type of the elements.\n"
).
-spec list(converter(DUN)) -> converter(list(DUN)).
list(Of) ->
{converter,
fun(V) ->
{list_value,
begin
_pipe = V,
gleam@list:map(_pipe, erlang:element(2, Of))
end}
end,
fun(V@1) -> case V@1 of
{list_value, Vals} ->
_pipe@1 = Vals,
gleam@list:fold(
_pipe@1,
{ok, []},
fun(Result, Val) ->
case {Result, (erlang:element(3, Of))(Val)} of
{{ok, Res}, {ok, New_res}} ->
{ok, lists:append(Res, [New_res])};
{{error, Errs}, {error, New_errs}} ->
{error, lists:append(Errs, New_errs)};
{_, {error, Errs@1}} ->
{error, Errs@1};
{{error, Errs@1}, _} ->
{error, Errs@1}
end
end
);
Other ->
{error,
[{decode_error,
<<"ListValue"/utf8>>,
get_type(Other),
[]}]}
end end,
{list, erlang:element(4, Of)},
[]}.
-file("src/convert.gleam", 317).
?DOC(
" Basic converter for a Result value.\n"
" \n"
" `res` is a converter for the Ok value.\n"
" `error` is a converter for the Error value.\n"
).
-spec result(converter(DUV), converter(DUX)) -> converter({ok, DUV} |
{error, DUX}).
result(Res, Error) ->
{converter,
fun(V) ->
{result_value,
begin
_pipe = V,
_pipe@1 = gleam@result:map(_pipe, erlang:element(2, Res)),
gleam@result:map_error(_pipe@1, erlang:element(2, Error))
end}
end,
fun(V@1) -> case V@1 of
{result_value, {ok, Val}} ->
_pipe@2 = Val,
_pipe@3 = (erlang:element(3, Res))(_pipe@2),
gleam@result:map(_pipe@3, fun(Field@0) -> {ok, Field@0} end);
{result_value, {error, Val@1}} ->
_pipe@4 = Val@1,
_pipe@5 = (erlang:element(3, Error))(_pipe@4),
gleam@result:map(
_pipe@5,
fun(Field@0) -> {error, Field@0} end
);
Other ->
{error,
[{decode_error,
<<"ResultValue"/utf8>>,
get_type(Other),
[]}]}
end end,
{result, erlang:element(4, Res), erlang:element(4, Error)},
{ok, erlang:element(5, Res)}}.
-file("src/convert.gleam", 101).
?DOC(
" Add a field to a PartialConverter \n"
" See `object` for its usage details\n"
" \n"
" 'field_name' is the field name that will be used in the converted value. It may not be equal to the actual field name. \n"
" 'field_getter' is a function that returns the value of the field from the complete object. \n"
" 'field_type' is a Converter associated to the type of the field.\n"
).
-spec field(
binary(),
fun((DTX) -> {ok, DTY} | {error, nil}),
converter(DTY),
fun((DTY) -> partial_converter(DTX))
) -> partial_converter(DTX).
field(Field_name, Field_getter, Field_type, Next) ->
{partial_converter,
fun(Base) ->
Value = Field_getter(Base),
case Value of
{error, nil} ->
null_value;
{ok, Field_value} ->
Converter = Next(Field_value),
case (erlang:element(2, Converter))(Base) of
{object_value, Fields} ->
{object_value,
[{Field_name,
(erlang:element(2, Field_type))(
Field_value
)} |
Fields]};
_ ->
null_value
end
end
end,
fun(V) -> case V of
{object_value, Values} ->
Field_value@1 = begin
_pipe = Values,
_pipe@1 = gleam@list:key_find(_pipe, Field_name),
_pipe@2 = gleam@result:replace_error(
_pipe@1,
[{decode_error,
<<"Value"/utf8>>,
<<"None"/utf8>>,
[Field_name]}]
),
gleam@result:then(
_pipe@2,
erlang:element(3, Field_type)
)
end,
gleam@result:'try'(
Field_value@1,
fun(A) -> (erlang:element(3, Next(A)))(V) end
);
_ ->
{error, []}
end end,
([{Field_name, erlang:element(4, Field_type)} |
erlang:element(4, Next(erlang:element(5, Field_type)))]),
(erlang:element(5, Next(erlang:element(5, Field_type))))}.
-file("src/convert.gleam", 296).
?DOC(
" Basic converter for a Option value.\n"
" \n"
" `of` is a converter for the optional value.\n"
).
-spec optional(converter(DUR)) -> converter(gleam@option:option(DUR)).
optional(Of) ->
{converter,
fun(V) ->
{optional_value,
begin
_pipe = V,
gleam@option:map(_pipe, erlang:element(2, Of))
end}
end,
fun(V@1) -> case V@1 of
{optional_value, none} ->
{ok, none};
{optional_value, {some, Val}} ->
_pipe@1 = Val,
_pipe@2 = (erlang:element(3, Of))(_pipe@1),
gleam@result:map(
_pipe@2,
fun(Field@0) -> {some, Field@0} end
);
Other ->
{error,
[{decode_error,
<<"OptionalValue"/utf8>>,
get_type(Other),
[]}]}
end end,
{optional, erlang:element(4, Of)},
none}.
-file("src/convert.gleam", 348).
?DOC(
" Basic converter for a Dict value.\n"
" \n"
" `key` is a converter for the keys.\n"
" `value` is a converter for the values.\n"
" \n"
" Example:\n"
" ```\n"
" let converter: Converter(Dict(String, Int)) = dict(string(), int())\n"
" ```\n"
).
-spec dict(converter(DVC), converter(DVE)) -> converter(gleam@dict:dict(DVC, DVE)).
dict(Key, Value) ->
{converter,
fun(V) ->
{dict_value,
begin
_pipe = V,
_pipe@1 = maps:to_list(_pipe),
_pipe@4 = gleam@list:map(
_pipe@1,
fun(Kv) ->
{begin
_pipe@2 = erlang:element(1, Kv),
(erlang:element(2, Key))(_pipe@2)
end,
begin
_pipe@3 = erlang:element(2, Kv),
(erlang:element(2, Value))(_pipe@3)
end}
end
),
maps:from_list(_pipe@4)
end}
end,
fun(V@1) -> case V@1 of
{dict_value, D} ->
_pipe@5 = D,
_pipe@6 = maps:to_list(_pipe@5),
_pipe@7 = gleam@list:fold(
_pipe@6,
{ok, []},
fun(Result, Kv@1) ->
case {Result,
(erlang:element(3, Key))(
erlang:element(1, Kv@1)
),
(erlang:element(3, Value))(
erlang:element(2, Kv@1)
)} of
{{ok, Values}, {ok, New_k}, {ok, New_v}} ->
{ok, lists:append(Values, [{New_k, New_v}])};
{{error, Errs}, {ok, _}, {ok, _}} ->
{error, Errs};
{{ok, _}, {ok, _}, {error, Errs}} ->
{error, Errs};
{{ok, _}, {error, Errs}, {ok, _}} ->
{error, Errs};
{{ok, _}, {error, Errs_1}, {error, Errs_2}} ->
{error, lists:append(Errs_1, Errs_2)};
{{error, Errs_1}, {error, Errs_2}, {ok, _}} ->
{error, lists:append(Errs_1, Errs_2)};
{{error, Errs_1}, {ok, _}, {error, Errs_2}} ->
{error, lists:append(Errs_1, Errs_2)};
{{error, Errs@1},
{error, Errs_k},
{error, Errs_v}} ->
{error,
gleam@list:flatten(
[Errs@1, Errs_k, Errs_v]
)}
end
end
),
gleam@result:map(_pipe@7, fun maps:from_list/1);
Other ->
{error,
[{decode_error,
<<"DictValue"/utf8>>,
get_type(Other),
[]}]}
end end,
{dict, erlang:element(4, Key), erlang:element(4, Value)},
maps:new()}.
-file("src/convert.gleam", 436).
?DOC(
" Create a converter for an enum type\n"
" \n"
" `tags` is a function that associate a tag to each variant of the enum\n"
" `converters` is a list of converters, each associated with a tag\n"
" \n"
" Example:\n"
" ```\n"
" type Action {\n"
" Open(id: String)\n"
" Close(id: String)\n"
" }\n"
" \n"
" let open_converter = object({\n"
" use id <- field(\"id\", fn(v: Action) {\n"
" case v {\n"
" Open(id) -> Ok(id)\n"
" _ -> Error(Nil)\n"
" }\n"
" }, string())\n"
" success(Open(id:))\n"
" })\n"
" \n"
" let close_converter = object({\n"
" use id <- field(\"id\", fn(v: Action) {\n"
" case v {\n"
" Close(id) -> Ok(id)\n"
" _ -> Error(Nil)\n"
" }\n"
" }, string())\n"
" success(Close(id:))\n"
" })\n"
" \n"
" let action_converter = enum(\n"
" fn(v) {\n"
" case v {\n"
" Open(_) -> \"Open\"\n"
" Close(_) -> \"Close\"\n"
" }\n"
" },\n"
" [\n"
" #(\"Open\", open_converter),\n"
" #(\"Close\", close_converter),\n"
" ]\n"
" )\n"
" ```\n"
).
-spec enum(fun((DVJ) -> binary()), list({binary(), converter(DVJ)})) -> converter(DVJ).
enum(Tags, Converters) ->
{converter,
fun(V) ->
Tag = Tags(V),
case begin
_pipe = Converters,
gleam@list:key_find(_pipe, Tag)
end of
{ok, Variant} ->
{enum_value, Tag, (erlang:element(2, Variant))(V)};
{error, _} ->
null_value
end
end,
fun(V@1) -> case V@1 of
{enum_value, Variant_name, Value} ->
gleam@result:'try'(
begin
_pipe@1 = Converters,
_pipe@2 = gleam@list:key_find(_pipe@1, Variant_name),
gleam@result:replace_error(
_pipe@2,
[{decode_error,
<<"One of: "/utf8,
(begin
_pipe@3 = Converters,
_pipe@4 = gleam@list:map(
_pipe@3,
fun(V@2) ->
erlang:element(1, V@2)
end
),
gleam@string:join(
_pipe@4,
<<"/"/utf8>>
)
end)/binary>>,
Variant_name,
[<<"0"/utf8>>]}]
)
end,
fun(Variant@1) ->
(erlang:element(3, Variant@1))(Value)
end
);
Other ->
{error,
[{decode_error,
<<"EnumValue"/utf8>>,
get_type(Other),
[]}]}
end end,
{enum,
begin
_pipe@5 = Converters,
gleam@list:map(
_pipe@5,
fun(Var) ->
{erlang:element(1, Var),
erlang:element(4, (erlang:element(2, Var)))}
end
)
end},
begin
First@1 = case Converters of
[First | _] -> First;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"convert"/utf8>>,
function => <<"enum"/utf8>>,
line => 471,
value => _assert_fail,
start => 12558,
'end' => 12593,
pattern_start => 12569,
pattern_end => 12580})
end,
erlang:element(5, (erlang:element(2, First@1)))
end}.
-file("src/convert.gleam", 501).
?DOC(
" Create a converter by mapping the encode and decode functions from an existing one\n"
" \n"
" Example:\n"
" ```\n"
" pub type Date {\n"
" Date(year: Int, month: Int, day: Int)\n"
" }\n"
" \n"
" // We are storing the date as a string for optimized memory storage\n"
" pub fn date_converter() -> Converter(Date) {\n"
" string()\n"
" |> map(\n"
" fn(v: Date) { [v.year, v.month, v.day] |> list.map(int.to_string) |> string.join(\"/\") },\n"
" fn(v: String) { \n"
" let elems = string.split(v, \"/\")\n"
" case elems {\n"
" [y, m, d, ..] -> Ok(Date(y, m, d))\n"
" _ -> Error([])\n"
" },\n"
" Date(0, 0, 0) // This is required for now...\n"
" }\n"
" )\n"
" }\n"
" ```\n"
).
-spec map(
converter(DVN),
fun((DVP) -> DVN),
fun((DVN) -> {ok, DVP} | {error, list(gleam@dynamic@decode:decode_error())}),
DVP
) -> converter(DVP).
map(Converter, Encode_map, Decode_map, Default_value) ->
{converter,
fun(V) ->
A_value = Encode_map(V),
(erlang:element(2, Converter))(A_value)
end,
fun(V@1) -> _pipe = (erlang:element(3, Converter))(V@1),
gleam@result:then(_pipe, Decode_map) end,
erlang:element(4, Converter),
Default_value}.
-file("src/convert.gleam", 554).
?DOC(" Return the GlitrType associated with the converter\n").
-spec type_def(converter(any())) -> glitr_type().
type_def(Converter) ->
erlang:element(4, Converter).
-file("src/convert.gleam", 570).
?DOC(
" Retrieve the default value associated with a converter.\n"
" \n"
" This value is used as a fallback when decoding fails,\n"
" especially when building decoders that must return a value\n"
" even in the presence of errors.\n"
"\n"
" ## Example\n"
" ```\n"
" let string_conv = string()\n"
" let default = default_value(string_conv)\n"
" // default == \"\"\n"
" ```\n"
).
-spec default_value(converter(DWD)) -> DWD.
default_value(Conv) ->
erlang:element(5, Conv).