-module(fexpr).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/fexpr.gleam").
-export([fexpr_value_to_json/1, fexpr_value_to_string/1, fexpr_value_to_sql_string/1, decode_any_int/0, decode_any_float/0, decoder_of_fexpr_value/1, fexpr_value_compare/2, empty_fexpr/0, fexpr/3, 'and'/4, 'or'/4, and_builder/2, or_builder/2, wrap/1, dual/5, to_string/1, to_sql/1, verify_fexpr/2, verify_fexpr_dyn/2]).
-export_type([fexpr_value/0, operator/0, fexpr_node/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.
-type fexpr_value() :: {string, binary()} |
{int, integer()} |
{float, float()} |
{bool, boolean()} |
null |
{date_time, birl:time()} |
{array, list(fexpr_value())}.
-type operator() :: equal |
not_equal |
greater |
greater_or_equal |
less |
less_or_equal |
contains |
not_contains |
at_least_one_equal |
at_least_one_not_equal |
at_least_one_greater |
at_least_one_greater_or_equal |
at_least_one_less |
at_least_one_less_or_equal |
at_least_one_contains |
at_least_one_not_contains.
-type fexpr_node() :: none |
{wrap, fexpr_node()} |
{fexpr, binary(), operator(), fexpr_value()} |
{or_bin_op, fexpr_node(), fexpr_node()} |
{and_bin_op, fexpr_node(), fexpr_node()}.
-file("src/fexpr.gleam", 28).
-spec fexpr_value_to_json(fexpr_value()) -> gleam@json:json().
fexpr_value_to_json(Value) ->
case Value of
{string, S} ->
gleam@json:string(S);
{int, I} ->
gleam@json:int(I);
{float, F} ->
gleam@json:float(F);
{bool, B} ->
gleam@json:bool(B);
null ->
gleam@json:null();
{date_time, Dt} ->
gleam@json:string(birl:to_iso8601(Dt));
{array, Arr} ->
_pipe = Arr,
_pipe@1 = gleam@list:map(
_pipe,
fun(Node) -> fexpr_value_to_json(Node) end
),
gleam@json:preprocessed_array(_pipe@1)
end.
-file("src/fexpr.gleam", 43).
-spec fexpr_value_to_string(fexpr_value()) -> binary().
fexpr_value_to_string(Value) ->
gleam@json:to_string(fexpr_value_to_json(Value)).
-file("src/fexpr.gleam", 47).
-spec fexpr_value_to_sql_string(fexpr_value()) -> binary().
fexpr_value_to_sql_string(Value) ->
case Value of
{string, S} ->
<<<<"'"/utf8,
(gleam@string:replace(S, <<"'"/utf8>>, <<"''"/utf8>>))/binary>>/binary,
"'"/utf8>>;
{int, I} ->
erlang:integer_to_binary(I);
{float, F} ->
gleam_stdlib:float_to_string(F);
{bool, B} when B ->
<<"1"/utf8>>;
{bool, _} ->
<<"0"/utf8>>;
null ->
<<"NULL"/utf8>>;
{date_time, Dt} ->
erlang:integer_to_binary(birl:to_unix(Dt));
{array, Arr} ->
_pipe = Arr,
_pipe@1 = gleam@list:map(
_pipe,
fun(Node) -> fexpr_value_to_sql_string(Node) end
),
_pipe@2 = gleam@string:join(_pipe@1, <<", "/utf8>>),
(fun(S@1) -> <<<<"("/utf8, S@1/binary>>/binary, ")"/utf8>> end)(
_pipe@2
)
end.
-file("src/fexpr.gleam", 465).
?DOC(" Creates a decoder for the Number type.\n").
-spec decode_any_int() -> gleam@dynamic@decode:decoder(integer()).
decode_any_int() ->
gleam@dynamic@decode:one_of(
{decoder, fun gleam@dynamic@decode:decode_int/1},
[begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_float/1},
gleam@dynamic@decode:map(_pipe, fun erlang:round/1)
end]
).
-file("src/fexpr.gleam", 460).
?DOC(" Creates a decoder for the Number type.\n").
-spec decode_any_float() -> gleam@dynamic@decode:decoder(float()).
decode_any_float() ->
gleam@dynamic@decode:one_of(
{decoder, fun gleam@dynamic@decode:decode_float/1},
[begin
_pipe = {decoder, fun gleam@dynamic@decode:decode_int/1},
gleam@dynamic@decode:map(_pipe, fun erlang:float/1)
end]
).
-file("src/fexpr.gleam", 64).
-spec decoder_of_fexpr_value(fexpr_value()) -> gleam@dynamic@decode:decoder(fexpr_value()).
decoder_of_fexpr_value(Value) ->
Base_decoder = case Value of
{array, Values} ->
gleam@dynamic@decode:one_of(
gleam@dynamic@decode:map(
gleam@dynamic@decode:list(
begin
_pipe = gleam@list:first(Values),
_pipe@1 = gleam@result:map(
_pipe,
fun decoder_of_fexpr_value/1
),
gleam@result:unwrap(
_pipe@1,
gleam@dynamic@decode:success(null)
)
end
),
fun(Field@0) -> {array, Field@0} end
),
[begin
_pipe@2 = gleam@list:first(Values),
_pipe@3 = gleam@result:map(
_pipe@2,
fun decoder_of_fexpr_value/1
),
gleam@result:unwrap(
_pipe@3,
gleam@dynamic@decode:success(null)
)
end]
);
{bool, _} ->
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_bool/1},
fun(Field@0) -> {bool, Field@0} end
);
{date_time, _} ->
gleam@dynamic@decode:then(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Value@1) -> case birl:parse(Value@1) of
{ok, Dt} ->
gleam@dynamic@decode:success({date_time, Dt});
{error, E} ->
gleam@dynamic@decode:failure(
{date_time, birl:now()},
<<"Failed to parse date: "/utf8,
(gleam@string:inspect(E))/binary>>
)
end end
);
{float, _} ->
gleam@dynamic@decode:map(
decode_any_float(),
fun(Field@0) -> {float, Field@0} end
);
{int, _} ->
gleam@dynamic@decode:map(
decode_any_int(),
fun(Field@0) -> {int, Field@0} end
);
null ->
gleam@dynamic@decode:then(
gleam@dynamic@decode:optional(
{decoder, fun gleam@dynamic@decode:decode_dynamic/1}
),
fun(Value@2) -> case Value@2 of
none ->
gleam@dynamic@decode:success(null);
{some, _} ->
gleam@dynamic@decode:failure(
null,
<<"Expected null"/utf8>>
)
end end
);
{string, _} ->
gleam@dynamic@decode:map(
{decoder, fun gleam@dynamic@decode:decode_string/1},
fun(Field@0) -> {string, Field@0} end
)
end,
gleam@dynamic@decode:one_of(
gleam@dynamic@decode:map(
gleam@dynamic@decode:list(Base_decoder),
fun(Field@0) -> {array, Field@0} end
),
[Base_decoder]
).
-file("src/fexpr.gleam", 109).
-spec fexpr_value_compare(fexpr_value(), fexpr_value()) -> {ok,
gleam@order:order()} |
{error, nil}.
fexpr_value_compare(A, B) ->
case {A, B} of
{{int, A@1}, {int, B@1}} ->
{ok, gleam@int:compare(A@1, B@1)};
{{int, A@2}, {float, B@2}} ->
{ok, gleam@float:compare(erlang:float(A@2), B@2)};
{{float, A@3}, {int, B@3}} ->
{ok, gleam@float:compare(A@3, erlang:float(B@3))};
{{float, A@4}, {float, B@4}} ->
{ok, gleam@float:compare(A@4, B@4)};
{{date_time, A@5}, {date_time, B@5}} ->
case birl@duration:blur_to(birl:difference(A@5, B@5), second) of
0 ->
{ok, eq};
_ ->
{ok, birl:compare(A@5, B@5)}
end;
{_, _} ->
{error, nil}
end.
-file("src/fexpr.gleam", 152).
?DOC(" Convert the given operator to its string representation.\n").
-spec operator_to_string(operator()) -> binary().
operator_to_string(Operator) ->
case Operator of
equal ->
<<" = "/utf8>>;
not_equal ->
<<" != "/utf8>>;
greater ->
<<" > "/utf8>>;
greater_or_equal ->
<<" >= "/utf8>>;
less ->
<<" < "/utf8>>;
less_or_equal ->
<<" <= "/utf8>>;
contains ->
<<" ~ "/utf8>>;
not_contains ->
<<" !~ "/utf8>>;
at_least_one_equal ->
<<" ?= "/utf8>>;
at_least_one_not_equal ->
<<" ?!= "/utf8>>;
at_least_one_greater ->
<<" ?> "/utf8>>;
at_least_one_greater_or_equal ->
<<" ?>= "/utf8>>;
at_least_one_less ->
<<" ?< "/utf8>>;
at_least_one_less_or_equal ->
<<" ?<= "/utf8>>;
at_least_one_contains ->
<<" ?~ "/utf8>>;
at_least_one_not_contains ->
<<" ?!~ "/utf8>>
end.
-file("src/fexpr.gleam", 173).
-spec opereator_to_sql_string(operator()) -> binary().
opereator_to_sql_string(Operator) ->
case Operator of
equal ->
<<" = "/utf8>>;
not_equal ->
<<" != "/utf8>>;
greater ->
<<" > "/utf8>>;
greater_or_equal ->
<<" >= "/utf8>>;
less ->
<<" < "/utf8>>;
less_or_equal ->
<<" <= "/utf8>>;
contains ->
<<" LIKE "/utf8>>;
not_contains ->
<<" NOT LIKE "/utf8>>;
at_least_one_equal ->
<<" IN "/utf8>>;
at_least_one_not_equal ->
<<" NOT IN "/utf8>>;
at_least_one_greater ->
<<" > ANY "/utf8>>;
at_least_one_greater_or_equal ->
<<" >= ANY "/utf8>>;
at_least_one_less ->
<<" < ANY "/utf8>>;
at_least_one_less_or_equal ->
<<" <= ANY "/utf8>>;
at_least_one_contains ->
<<" LIKE ANY "/utf8>>;
at_least_one_not_contains ->
<<" NOT LIKE ANY "/utf8>>
end.
-file("src/fexpr.gleam", 208).
?DOC(" Create an empty fexpr node.\n").
-spec empty_fexpr() -> fexpr_node().
empty_fexpr() ->
none.
-file("src/fexpr.gleam", 213).
?DOC(" Create a fexpr node with the given field, operator and value.\n").
-spec fexpr(binary(), operator(), fexpr_value()) -> fexpr_node().
fexpr(Field, Operator, Value) ->
{fexpr, Field, Operator, Value}.
-file("src/fexpr.gleam", 218).
?DOC(" Create a fexpr node that is the logical AND of the given fexpr node and a new fexpr node with the given field, operator and value.\n").
-spec 'and'(fexpr_node(), binary(), operator(), fexpr_value()) -> fexpr_node().
'and'(Fexpr, Field, Operator, Value) ->
{and_bin_op, Fexpr, {fexpr, Field, Operator, Value}}.
-file("src/fexpr.gleam", 231).
?DOC(" Create a fexpr node that is the logical OR of the given fexpr node and a new fexpr node with the given field, operator and value.\n").
-spec 'or'(fexpr_node(), binary(), operator(), fexpr_value()) -> fexpr_node().
'or'(Fexpr, Field, Operator, Value) ->
{or_bin_op, Fexpr, {fexpr, Field, Operator, Value}}.
-file("src/fexpr.gleam", 244).
?DOC(" Create a fexpr node that is the logical AND of the two given fexpr nodes.\n").
-spec and_builder(fexpr_node(), fexpr_node()) -> fexpr_node().
and_builder(Fexpr, Other) ->
{and_bin_op, Fexpr, Other}.
-file("src/fexpr.gleam", 249).
?DOC(" Create a fexpr node that is the logical OR of the two given fexpr nodes.\n").
-spec or_builder(fexpr_node(), fexpr_node()) -> fexpr_node().
or_builder(Fexpr, Other) ->
{or_bin_op, Fexpr, Other}.
-file("src/fexpr.gleam", 254).
?DOC(" Wrap the given fexpr node in parentheses.\n").
-spec wrap(fexpr_node()) -> fexpr_node().
wrap(Fexpr) ->
{wrap, Fexpr}.
-file("src/fexpr.gleam", 258).
-spec dual(binary(), binary(), operator(), fexpr_value(), fexpr_value()) -> fexpr_node().
dual(Field_a, Field_b, Operator, Value_a, Value_b) ->
_pipe = fexpr(Field_a, Operator, Value_a),
_pipe@1 = 'and'(_pipe, Field_b, Operator, Value_b),
_pipe@2 = wrap(_pipe@1),
or_builder(
_pipe@2,
begin
_pipe@3 = fexpr(Field_a, Operator, Value_b),
_pipe@4 = 'and'(_pipe@3, Field_b, Operator, Value_a),
wrap(_pipe@4)
end
).
-file("src/fexpr.gleam", 276).
?DOC(" Convert the given fexpr node to its string representation.\n").
-spec to_string(fexpr_node()) -> binary().
to_string(Node) ->
case Node of
none ->
<<""/utf8>>;
{wrap, Inner} ->
<<<<"("/utf8, (to_string(Inner))/binary>>/binary, ")"/utf8>>;
{fexpr, Field, Operator, Value} ->
<<<<Field/binary, (operator_to_string(Operator))/binary>>/binary,
(fexpr_value_to_string(Value))/binary>>;
{and_bin_op, Lhs, Rhs} ->
<<<<(to_string(Lhs))/binary, "&&"/utf8>>/binary,
(to_string(Rhs))/binary>>;
{or_bin_op, Lhs@1, Rhs@1} ->
<<<<(to_string(Lhs@1))/binary, "||"/utf8>>/binary,
(to_string(Rhs@1))/binary>>
end.
-file("src/fexpr.gleam", 287).
-spec to_sql(fexpr_node()) -> binary().
to_sql(Node) ->
case Node of
none ->
<<"1=1"/utf8>>;
{wrap, Inner} ->
<<<<"("/utf8, (to_sql(Inner))/binary>>/binary, ")"/utf8>>;
{fexpr, Field, Operator, Value} ->
<<<<Field/binary, (opereator_to_sql_string(Operator))/binary>>/binary,
(fexpr_value_to_sql_string(Value))/binary>>;
{and_bin_op, Lhs, Rhs} ->
<<<<(to_sql(Lhs))/binary, " AND "/utf8>>/binary,
(to_sql(Rhs))/binary>>;
{or_bin_op, Lhs@1, Rhs@1} ->
<<<<(to_sql(Lhs@1))/binary, " OR "/utf8>>/binary,
(to_sql(Rhs@1))/binary>>
end.
-file("src/fexpr.gleam", 300).
-spec verify_equality(fexpr_value(), operator(), fexpr_value()) -> boolean().
verify_equality(A, Operator, B) ->
case Operator of
equal ->
(A =:= B) orelse (fexpr_value_compare(A, B) =:= {ok, eq});
not_equal ->
(A /= B) andalso (fexpr_value_compare(A, B) /= {ok, eq});
greater ->
fexpr_value_compare(A, B) =:= {ok, gt};
greater_or_equal ->
case fexpr_value_compare(A, B) of
{ok, gt} ->
true;
{ok, eq} ->
true;
_ ->
false
end;
less ->
fexpr_value_compare(A, B) =:= {ok, lt};
less_or_equal ->
case fexpr_value_compare(A, B) of
{ok, lt} ->
true;
{ok, eq} ->
true;
_ ->
false
end;
contains ->
case {A, B} of
{{array, Arr}, _} ->
gleam@list:any(
Arr,
fun(V) -> verify_equality(V, equal, B) end
);
{{string, A@1}, {string, B@1}} ->
Free_start = gleam_stdlib:string_starts_with(
B@1,
<<"%"/utf8>>
),
Free_end = gleam_stdlib:string_ends_with(B@1, <<"%"/utf8>>),
case {Free_start, Free_end} of
{true, true} ->
gleam_stdlib:contains_string(
A@1,
begin
_pipe = B@1,
_pipe@1 = gleam@string:drop_start(_pipe, 1),
gleam@string:drop_end(_pipe@1, 1)
end
);
{true, false} ->
gleam_stdlib:string_ends_with(
A@1,
begin
_pipe@2 = B@1,
gleam@string:drop_start(_pipe@2, 1)
end
);
{false, true} ->
gleam_stdlib:string_starts_with(
A@1,
begin
_pipe@3 = B@1,
gleam@string:drop_end(_pipe@3, 1)
end
);
{false, false} ->
verify_equality({string, A@1}, equal, {string, B@1})
end;
{_, _} ->
false
end;
not_contains ->
not verify_equality(A, contains, B);
at_least_one_equal ->
case {A, B} of
{A@2, {array, Arr_b}} ->
gleam@list:any(
Arr_b,
fun(V@1) -> verify_equality(V@1, equal, A@2) end
);
{_, _} ->
false
end;
at_least_one_not_equal ->
case {A, B} of
{A@3, {array, Arr_b@1}} ->
gleam@list:any(
Arr_b@1,
fun(V@2) -> verify_equality(V@2, not_equal, A@3) end
);
{_, _} ->
false
end;
at_least_one_greater ->
case {A, B} of
{A@4, {array, Arr_b@2}} ->
gleam@list:any(
Arr_b@2,
fun(V@3) -> verify_equality(V@3, greater, A@4) end
);
{_, _} ->
false
end;
at_least_one_greater_or_equal ->
case {A, B} of
{A@5, {array, Arr_b@3}} ->
gleam@list:any(
Arr_b@3,
fun(V@4) ->
verify_equality(V@4, greater_or_equal, A@5)
end
);
{_, _} ->
false
end;
at_least_one_less ->
case {A, B} of
{A@6, {array, Arr_b@4}} ->
gleam@list:any(
Arr_b@4,
fun(V@5) -> verify_equality(V@5, less, A@6) end
);
{_, _} ->
false
end;
at_least_one_less_or_equal ->
case {A, B} of
{A@7, {array, Arr_b@5}} ->
gleam@list:any(
Arr_b@5,
fun(V@6) -> verify_equality(V@6, less_or_equal, A@7) end
);
{_, _} ->
false
end;
at_least_one_contains ->
case {A, B} of
{A@8, {array, Arr_b@6}} ->
gleam@list:any(
Arr_b@6,
fun(V@7) -> verify_equality(A@8, contains, V@7) end
);
{_, _} ->
false
end;
at_least_one_not_contains ->
case {A, B} of
{A@9, {array, Arr_b@7}} ->
gleam@list:any(
Arr_b@7,
fun(V@8) -> verify_equality(A@9, not_contains, V@8) end
);
{_, _} ->
false
end
end.
-file("src/fexpr.gleam", 387).
-spec verify_fexpr(fexpr_node(), binary()) -> boolean().
verify_fexpr(Fexpr, Data) ->
case Fexpr of
{fexpr, Field, Operator, Value} ->
Parts = gleam@string:split(Field, <<"."/utf8>>),
Decoded = gleam@json:parse(
Data,
begin
gleam@dynamic@decode:subfield(
Parts,
decoder_of_fexpr_value(Value),
fun(Value@1) ->
gleam@dynamic@decode:success(Value@1)
end
)
end
),
case Decoded of
{ok, Decoded@1} ->
verify_equality(Decoded@1, Operator, Value);
{error, {unable_to_decode, Errors}} when Value =:= null ->
gleam@list:any(
Errors,
fun(Error) ->
(erlang:element(2, Error) =:= <<"Field"/utf8>>)
andalso (erlang:element(3, Error) =:= <<"Nothing"/utf8>>)
end
);
{error, _} ->
false
end;
none ->
true;
{and_bin_op, Left, Right} ->
A = verify_fexpr(Left, Data),
B = verify_fexpr(Right, Data),
A andalso B;
{or_bin_op, Left@1, Right@1} ->
A@1 = verify_fexpr(Left@1, Data),
B@1 = verify_fexpr(Right@1, Data),
A@1 orelse B@1;
{wrap, Inner} ->
verify_fexpr(Inner, Data)
end.
-file("src/fexpr.gleam", 423).
-spec verify_fexpr_dyn(fexpr_node(), gleam@dynamic:dynamic_()) -> boolean().
verify_fexpr_dyn(Fexpr, Data) ->
case Fexpr of
{fexpr, Field, Operator, Value} ->
Parts = gleam@string:split(Field, <<"."/utf8>>),
Decoded = gleam@dynamic@decode:run(
Data,
begin
gleam@dynamic@decode:subfield(
Parts,
decoder_of_fexpr_value(Value),
fun(Value@1) ->
gleam@dynamic@decode:success(Value@1)
end
)
end
),
case Decoded of
{ok, Decoded@1} ->
verify_equality(Decoded@1, Operator, Value);
{error, Errors} when Value =:= null ->
gleam@list:any(
Errors,
fun(Error) ->
(erlang:element(2, Error) =:= <<"Field"/utf8>>)
andalso (erlang:element(3, Error) =:= <<"Nothing"/utf8>>)
end
);
{error, _} ->
false
end;
none ->
true;
{and_bin_op, Left, Right} ->
A = verify_fexpr_dyn(Left, Data),
B = verify_fexpr_dyn(Right, Data),
A andalso B;
{or_bin_op, Left@1, Right@1} ->
A@1 = verify_fexpr_dyn(Left@1, Data),
B@1 = verify_fexpr_dyn(Right@1, Data),
A@1 orelse B@1;
{wrap, Inner} ->
verify_fexpr_dyn(Inner, Data)
end.