Current section

Files

Jump to
caffeine_lang src caffeine_lang@common@decoders.erl
Raw

src/caffeine_lang@common@decoders.erl

-module(caffeine_lang@common@decoders).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/caffeine_lang/common/decoders.gleam").
-export([named_reference_decoder/2, non_empty_string_decoder/0, decode_value_to_string/1, decode_list_values_to_strings/1, accepted_types_decoder/0]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/caffeine_lang/common/decoders.gleam", 15).
?DOC(
" Creates a decoder that validates a string is a valid reference to an item in a collection.\n"
" Returns the string if it matches a name in the collection, otherwise fails decoding.\n"
).
-spec named_reference_decoder(list(FSX), fun((FSX) -> binary())) -> gleam@dynamic@decode:decoder(binary()).
named_reference_decoder(Collection, Name_extraction) ->
Names = begin
_pipe = Collection,
gleam@list:map(_pipe, Name_extraction)
end,
Default = {error, <<""/utf8>>},
gleam@dynamic@decode:new_primitive_decoder(
<<"NamedReference"/utf8>>,
fun(Dyn) ->
case gleam@dynamic@decode:run(
Dyn,
{decoder, fun gleam@dynamic@decode:decode_string/1}
) of
{ok, X} ->
case begin
_pipe@1 = Names,
gleam@list:contains(_pipe@1, X)
end of
true ->
{ok, X};
false ->
Default
end;
_ ->
Default
end
end
).
-file("src/caffeine_lang/common/decoders.gleam", 36).
?DOC(" Decoder for non-empty strings. Fails if the string is empty.\n").
-spec non_empty_string_decoder() -> gleam@dynamic@decode:decoder(binary()).
non_empty_string_decoder() ->
Default = {error, <<""/utf8>>},
gleam@dynamic@decode:new_primitive_decoder(
<<"NonEmptyString"/utf8>>,
fun(Dyn) ->
case gleam@dynamic@decode:run(
Dyn,
{decoder, fun gleam@dynamic@decode:decode_string/1}
) of
{ok, <<""/utf8>>} ->
Default;
{ok, S} ->
{ok, S};
_ ->
Default
end
end
).
-file("src/caffeine_lang/common/decoders.gleam", 63).
?DOC(" Decoder that decodes a dynamic value based on an AcceptedTypes and returns its String representation.\n").
-spec decode_value_to_string(caffeine_lang@common@helpers:accepted_types()) -> gleam@dynamic@decode:decoder(binary()).
decode_value_to_string(Typ) ->
case Typ of
boolean ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_bool/1},
fun(Val) ->
gleam@dynamic@decode:success(gleam@bool:to_string(Val))
end
);
float ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_float/1},
fun(Val@1) ->
gleam@dynamic@decode:success(
gleam_stdlib:float_to_string(Val@1)
)
end
);
integer ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_int/1},
fun(Val@2) ->
gleam@dynamic@decode:success(
erlang:integer_to_binary(Val@2)
)
end
);
string ->
{decoder, fun gleam@dynamic@decode:decode_string/1};
{dict, _, _} ->
gleam@dynamic@decode:failure(<<""/utf8>>, <<"Dict"/utf8>>);
{list, _} ->
gleam@dynamic@decode:failure(<<""/utf8>>, <<"List"/utf8>>);
{optional, Inner_type} ->
gleam@dynamic@decode:then(
gleam@dynamic@decode:optional(
decode_value_to_string(Inner_type)
),
fun(Maybe_val) -> case Maybe_val of
{some, Val@3} ->
gleam@dynamic@decode:success(Val@3);
none ->
gleam@dynamic@decode:success(<<""/utf8>>)
end end
);
{defaulted, Inner_type@1, Default_val} ->
gleam@dynamic@decode:then(
gleam@dynamic@decode:optional(
decode_value_to_string(Inner_type@1)
),
fun(Maybe_val@1) -> case Maybe_val@1 of
{some, Val@4} ->
gleam@dynamic@decode:success(Val@4);
none ->
gleam@dynamic@decode:success(Default_val)
end end
)
end.
-file("src/caffeine_lang/common/decoders.gleam", 102).
?DOC(" Decoder that decodes a list of dynamic values and returns a List(String).\n").
-spec decode_list_values_to_strings(
caffeine_lang@common@helpers:accepted_types()
) -> gleam@dynamic@decode:decoder(list(binary())).
decode_list_values_to_strings(Inner_type) ->
gleam@dynamic@decode:list(decode_value_to_string(Inner_type)).
-file("src/caffeine_lang/common/decoders.gleam", 185).
?DOC(
" Validates that a default value string is compatible with the given type.\n"
" For basic types, checks that the string can be parsed as that type.\n"
" For complex types (List, Dict), we accept any string since the default\n"
" is used as a literal string value in templates.\n"
).
-spec validate_default_value(
caffeine_lang@common@helpers:accepted_types(),
binary()
) -> boolean().
validate_default_value(Typ, Default_val) ->
case Typ of
boolean ->
(Default_val =:= <<"True"/utf8>>) orelse (Default_val =:= <<"False"/utf8>>);
integer ->
case gleam_stdlib:parse_int(Default_val) of
{ok, _} ->
true;
{error, _} ->
false
end;
float ->
case gleam_stdlib:parse_float(Default_val) of
{ok, _} ->
true;
{error, _} ->
false
end;
string ->
true;
{list, _} ->
true;
{dict, _, _} ->
true;
{optional, _} ->
false;
{defaulted, _, _} ->
false
end.
-file("src/caffeine_lang/common/decoders.gleam", 217).
-spec find_type_default_split_helper(binary(), binary(), integer(), integer()) -> {ok,
{binary(), binary()}} |
{error, nil}.
find_type_default_split_helper(Original, S, Index, Paren_depth) ->
case gleam_stdlib:string_pop_grapheme(S) of
{error, nil} ->
{error, nil};
{ok, {Char, Rest}} ->
case {Char, Paren_depth} of
{<<"("/utf8>>, _} ->
find_type_default_split_helper(
Original,
Rest,
Index + 1,
Paren_depth + 1
);
{<<")"/utf8>>, _} ->
find_type_default_split_helper(
Original,
Rest,
Index + 1,
Paren_depth - 1
);
{<<","/utf8>>, 0} ->
Type_str = gleam@string:slice(Original, 0, Index),
Default_val = Rest,
{ok, {Type_str, Default_val}};
{_, _} ->
find_type_default_split_helper(
Original,
Rest,
Index + 1,
Paren_depth
)
end
end.
-file("src/caffeine_lang/common/decoders.gleam", 213).
?DOC(
" Finds the split point between the type and default value in a Defaulted inner string.\n"
" For \"Integer, 10\" returns Ok(#(\"Integer\", \"10\"))\n"
" For \"List(String), hello\" returns Ok(#(\"List(String)\", \"hello\"))\n"
" Handles nested parentheses correctly.\n"
).
-spec find_type_default_split(binary()) -> {ok, {binary(), binary()}} |
{error, nil}.
find_type_default_split(Inner) ->
find_type_default_split_helper(Inner, Inner, 0, 0).
-file("src/caffeine_lang/common/decoders.gleam", 148).
?DOC(
" Parses a Defaulted type string like \"Defaulted(Integer, 10)\" into Defaulted(Integer, \"10\")\n"
" Also validates that the default value is compatible with the inner type.\n"
).
-spec parse_defaulted_type(binary()) -> {ok,
caffeine_lang@common@helpers:accepted_types()} |
{error, nil}.
parse_defaulted_type(Raw) ->
case gleam_stdlib:string_starts_with(Raw, <<"Defaulted("/utf8>>) andalso gleam_stdlib:string_ends_with(
Raw,
<<")"/utf8>>
) of
false ->
{error, nil};
true ->
Inner = begin
_pipe = Raw,
_pipe@1 = gleam@string:drop_start(_pipe, 10),
gleam@string:drop_end(_pipe@1, 1)
end,
case find_type_default_split(Inner) of
{error, nil} ->
{error, nil};
{ok, {Type_str, Default_val}} ->
Trimmed_type = gleam@string:trim(Type_str),
Trimmed_default = gleam@string:trim(Default_val),
case parse_accepted_type(Trimmed_type) of
{ok, Inner_type} ->
case validate_default_value(
Inner_type,
Trimmed_default
) of
true ->
{ok,
{defaulted, Inner_type, Trimmed_default}};
false ->
{error, nil}
end;
{error, nil} ->
{error, nil}
end
end
end.
-file("src/caffeine_lang/common/decoders.gleam", 109).
?DOC(" Parses a raw string into an AcceptedType.\n").
-spec parse_accepted_type(binary()) -> {ok,
caffeine_lang@common@helpers:accepted_types()} |
{error, nil}.
parse_accepted_type(Raw_accepted_type) ->
case Raw_accepted_type of
<<"Boolean"/utf8>> ->
{ok, boolean};
<<"Float"/utf8>> ->
{ok, float};
<<"Integer"/utf8>> ->
{ok, integer};
<<"String"/utf8>> ->
{ok, string};
<<"Dict(String, String)"/utf8>> ->
{ok, {dict, string, string}};
<<"Dict(String, Integer)"/utf8>> ->
{ok, {dict, string, integer}};
<<"Dict(String, Float)"/utf8>> ->
{ok, {dict, string, float}};
<<"Dict(String, Boolean)"/utf8>> ->
{ok, {dict, string, boolean}};
<<"List(String)"/utf8>> ->
{ok, {list, string}};
<<"List(Integer)"/utf8>> ->
{ok, {list, integer}};
<<"List(Boolean)"/utf8>> ->
{ok, {list, boolean}};
<<"List(Float)"/utf8>> ->
{ok, {list, float}};
<<"Optional(String)"/utf8>> ->
{ok, {optional, string}};
<<"Optional(Integer)"/utf8>> ->
{ok, {optional, integer}};
<<"Optional(Float)"/utf8>> ->
{ok, {optional, float}};
<<"Optional(Boolean)"/utf8>> ->
{ok, {optional, boolean}};
<<"Optional(List(String))"/utf8>> ->
{ok, {optional, {list, string}}};
<<"Optional(List(Integer))"/utf8>> ->
{ok, {optional, {list, integer}}};
<<"Optional(List(Float))"/utf8>> ->
{ok, {optional, {list, float}}};
<<"Optional(List(Boolean))"/utf8>> ->
{ok, {optional, {list, boolean}}};
<<"Optional(Dict(String, String))"/utf8>> ->
{ok, {optional, {dict, string, string}}};
<<"Optional(Dict(String, Integer))"/utf8>> ->
{ok, {optional, {dict, string, integer}}};
<<"Optional(Dict(String, Float))"/utf8>> ->
{ok, {optional, {dict, string, float}}};
<<"Optional(Dict(String, Boolean))"/utf8>> ->
{ok, {optional, {dict, string, boolean}}};
_ ->
parse_defaulted_type(Raw_accepted_type)
end.
-file("src/caffeine_lang/common/decoders.gleam", 54).
?DOC(
" Decoder for AcceptedTypes from a raw string representation.\n"
" i.e. for \"Dict(String, String)\" will return a decoder for \n"
" Ok(helpers.Dict(helpers.String, helpers.String))).\n"
" \n"
" Note that the default for the error is decode.failure(Boolean, \"AcceptedType\")\n"
" which is a bit odd, but nonetheless should do the job for now.\n"
).
-spec accepted_types_decoder() -> gleam@dynamic@decode:decoder(caffeine_lang@common@helpers:accepted_types()).
accepted_types_decoder() ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Raw_string) -> case parse_accepted_type(Raw_string) of
{ok, T} ->
gleam@dynamic@decode:success(T);
{error, nil} ->
gleam@dynamic@decode:failure(
boolean,
<<"AcceptedType"/utf8>>
)
end end
).