-module(crossbar). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([int/2, float/2, string/2, bool/2, string_value/1, int_value/1, float_value/1, bool_value/1, field_name/1, rule_to_string/1, rule_to_error_string/1, to_float/1, required/1, min_value/2, max_value/2, min_length/2, max_length/2, eq/3, not_eq/3, with_validator/4, regex/4, extract_errors/1, to_serializable/3, to_serializable_list/2, serializables_to_string/1, has_errors/1, validate/1, validate_many/2]). -export_type([json_mode/0, cross_bar_error/0]). -type json_mode() :: array | key_value. -type cross_bar_error() :: {failed_rule, binary(), binary(), binary()}. -spec int(binary(), integer()) -> crossbar@internal@field:field(integer()). int(Name, Value) -> {int_field, Name, Value, []}. -spec float(binary(), float()) -> crossbar@internal@field:field(float()). float(Name, Value) -> {float_field, Name, Value, []}. -spec string(binary(), binary()) -> crossbar@internal@field:field(binary()). string(Name, Value) -> {string_field, Name, Value, []}. -spec bool(binary(), boolean()) -> crossbar@internal@field:field(boolean()). bool(Name, Value) -> {bool_field, Name, Value, []}. -spec string_value(crossbar@internal@field:field(binary())) -> binary(). string_value(Field) -> case Field of {string_field, _, Value, _} -> Value; _ -> <<""/utf8>> end. -spec int_value(crossbar@internal@field:field(integer())) -> integer(). int_value(Field) -> case Field of {int_field, _, Value, _} -> Value; _ -> 0 end. -spec float_value(crossbar@internal@field:field(float())) -> float(). float_value(Field) -> case Field of {float_field, _, Value, _} -> Value; _ -> +0.0 end. -spec bool_value(crossbar@internal@field:field(boolean())) -> boolean(). bool_value(Field) -> case Field of {bool_field, _, Value, _} -> Value; _ -> false end. -spec field_name(crossbar@internal@field:field(any())) -> binary(). field_name(Field) -> case Field of {int_field, Name, _, _} -> Name; {float_field, Name@1, _, _} -> Name@1; {string_field, Name@2, _, _} -> Name@2; {bool_field, Name@3, _, _} -> Name@3 end. -spec rule_to_string(crossbar@internal@field:rule(any())) -> binary(). rule_to_string(Rule) -> case Rule of required -> <<"required"/utf8>>; {min_size, _} -> <<"min_value"/utf8>>; {max_size, _} -> <<"max_value"/utf8>>; {min_length, _} -> <<"min_length"/utf8>>; {max_length, _} -> <<"max_length"/utf8>>; {eq, _, _} -> <<"eq"/utf8>>; {not_eq, _, _} -> <<"not_eq"/utf8>>; {regex, Name, _, _} -> gleam@bool:guard( Name =:= <<""/utf8>>, <<"regex"/utf8>>, fun() -> Name end ); {validator_function, Name@1, _, _} -> gleam@bool:guard( Name@1 =:= <<""/utf8>>, <<"validator_function"/utf8>>, fun() -> Name@1 end ) end. -spec extract_last_error_part(binary(), any()) -> binary(). extract_last_error_part(Name, Value) -> Name@1 = gleam@string:trim(Name), gleam@bool:guard( Name@1 /= <<""/utf8>>, <<<<"`"/utf8, Name@1/binary>>/binary, "` field"/utf8>>, fun() -> crossbar@internal@cast:to_string(Value) end ). -spec rule_to_error_string(crossbar@internal@field:rule(any())) -> binary(). rule_to_error_string(Rule) -> case Rule of required -> <<"is required"/utf8>>; {min_size, V} -> <<"must be at least "/utf8, (gleam@float:to_string(V))/binary>>; {max_size, V@1} -> <<"must not be greater than "/utf8, (gleam@float:to_string(V@1))/binary>>; {min_length, V@2} -> <<<<"must be at least "/utf8, (gleam@int:to_string(V@2))/binary>>/binary, " characters"/utf8>>; {max_length, V@3} -> <<<<"must not be longer than "/utf8, (gleam@int:to_string(V@3))/binary>>/binary, " characters"/utf8>>; {eq, Name, Value} -> <<"must be equal to "/utf8, (extract_last_error_part(Name, Value))/binary>>; {not_eq, Name@1, Value@1} -> <<"must not be equal to "/utf8, (extract_last_error_part(Name@1, Value@1))/binary>>; {regex, _, _, Error} -> Error; {validator_function, _, _, Error@1} -> Error@1 end. -spec append_rule( crossbar@internal@field:field(FXT), crossbar@internal@field:rule(FXT) ) -> crossbar@internal@field:field(FXT). append_rule(Field, Rule) -> case Field of {int_field, Name, Value, Rules} -> {int_field, Name, Value, gleam@list:append(Rules, [Rule])}; {float_field, Name@1, Value@1, Rules@1} -> {float_field, Name@1, Value@1, gleam@list:append(Rules@1, [Rule])}; {string_field, Name@2, Value@2, Rules@2} -> {string_field, Name@2, Value@2, gleam@list:append(Rules@2, [Rule])}; {bool_field, Name@3, Value@3, Rules@3} -> {bool_field, Name@3, Value@3, gleam@list:append(Rules@3, [Rule])} end. -spec int_rules_to_float_rules(list(crossbar@internal@field:rule(integer()))) -> list(crossbar@internal@field:rule(float())). int_rules_to_float_rules(Rules) -> _pipe = Rules, gleam@list:map(_pipe, fun(Rule) -> case Rule of required -> required; {min_size, V} -> {min_size, V}; {max_size, V@1} -> {max_size, V@1}; {min_length, V@2} -> {min_length, V@2}; {max_length, V@3} -> {max_length, V@3}; {eq, Name, Value} -> {eq, Name, gleam@int:to_float(Value)}; {not_eq, Name@1, Value@1} -> {not_eq, Name@1, gleam@int:to_float(Value@1)}; {regex, Name@2, Regex, Error} -> {regex, Name@2, Regex, Error}; {validator_function, Name@3, Original_validator, Error@1} -> {validator_function, Name@3, fun(V@4) -> _pipe@1 = V@4, _pipe@2 = gleam@float:round(_pipe@1), Original_validator(_pipe@2) end, Error@1} end end). -spec to_float(crossbar@internal@field:field(integer())) -> crossbar@internal@field:field(float()). to_float(Field) -> {int_field, Name, Value, Rules} = case Field of {int_field, _, _, _} -> Field; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Assertion pattern match failed"/utf8>>, value => _assert_fail, module => <<"crossbar"/utf8>>, function => <<"to_float"/utf8>>, line => 198}) end, _pipe = Value, _pipe@1 = gleam@int:to_float(_pipe), {float_field, Name, _pipe@1, int_rules_to_float_rules(Rules)}. -spec required(crossbar@internal@field:field(GDV)) -> crossbar@internal@field:field(GDV). required(Field) -> append_rule(Field, required). -spec min_value(crossbar@internal@field:field(float()), float()) -> crossbar@internal@field:field(float()). min_value(Field, Size) -> append_rule(Field, {min_size, Size}). -spec max_value(crossbar@internal@field:field(float()), float()) -> crossbar@internal@field:field(float()). max_value(Field, Size) -> append_rule(Field, {max_size, Size}). -spec min_length(crossbar@internal@field:field(binary()), integer()) -> crossbar@internal@field:field(binary()). min_length(Field, Length) -> append_rule(Field, {min_length, Length}). -spec max_length(crossbar@internal@field:field(binary()), integer()) -> crossbar@internal@field:field(binary()). max_length(Field, Length) -> append_rule(Field, {max_length, Length}). -spec eq(crossbar@internal@field:field(FYT), binary(), FYT) -> crossbar@internal@field:field(FYT). eq(Field, Name, Value) -> append_rule(Field, {eq, Name, Value}). -spec not_eq(crossbar@internal@field:field(FYW), binary(), FYW) -> crossbar@internal@field:field(FYW). not_eq(Field, Name, Value) -> append_rule(Field, {not_eq, Name, Value}). -spec with_validator( crossbar@internal@field:field(FYZ), binary(), fun((FYZ) -> boolean()), binary() ) -> crossbar@internal@field:field(FYZ). with_validator(Field, Name, Func, Error) -> append_rule(Field, {validator_function, Name, Func, Error}). -spec regex( crossbar@internal@field:field(FZC), binary(), gleam@regex:regex(), binary() ) -> crossbar@internal@field:field(FZC). regex(Field, Name, Regex, Error) -> append_rule(Field, {regex, Name, Regex, Error}). -spec extract_errors( {ok, crossbar@internal@field:field(any())} | {error, list(cross_bar_error())} ) -> list({binary(), binary()}). extract_errors(Result) -> Errors@1 = case Result of {ok, _} -> []; {error, Errors} -> Errors end, gleam@list:map(Errors@1, fun(Error) -> case Error of {failed_rule, _, Rule, Error@1} -> {Rule, Error@1} end end). -spec extract_field_name( {ok, crossbar@internal@field:field(any())} | {error, list(cross_bar_error())} ) -> binary(). extract_field_name(Result) -> case Result of {ok, Field} -> erlang:element(2, Field); {error, Errors} -> case Errors of [{failed_rule, Name, _, _} | _] -> Name; [] -> <<""/utf8>> end end. -spec to_serializable( {ok, crossbar@internal@field:field(any())} | {error, list(cross_bar_error())}, binary(), json_mode() ) -> {binary(), gleam@json:json()}. to_serializable(Validation_result, Field_name, Mode) -> Name = case Field_name of <<""/utf8>> -> extract_field_name(Validation_result); _ -> Field_name end, Errors = begin _pipe = Validation_result, extract_errors(_pipe) end, gleam@bool:guard( Errors =:= [], {Name, gleam@json:null()}, fun() -> Json_value = case Mode of array -> Errors_list = gleam@list:map( Errors, fun(E) -> erlang:element(2, E) end ), gleam@json:array(Errors_list, fun gleam@json:string/1); key_value -> gleam@json:object( gleam@list:map( Errors, fun(E@1) -> {erlang:element(1, E@1), gleam@json:string(erlang:element(2, E@1))} end ) ) end, {Name, Json_value} end ). -spec to_serializable_list( list({binary(), list(cross_bar_error())}), json_mode() ) -> list({binary(), gleam@json:json()}). to_serializable_list(Results, Mode) -> _pipe = Results, gleam@list:map( _pipe, fun(R) -> to_serializable( {error, erlang:element(2, R)}, erlang:element(1, R), Mode ) end ). -spec serializables_to_string(list({binary(), gleam@json:json()})) -> binary(). serializables_to_string(Serializables) -> _pipe = Serializables, _pipe@1 = gleam@json:object(_pipe), gleam@json:to_string(_pipe@1). -spec has_errors(list({binary(), gleam@json:json()})) -> boolean(). has_errors(Json_errors) -> _pipe = Json_errors, _pipe@1 = gleam@list:map(_pipe, fun(E) -> erlang:element(2, E) end), gleam@list:any(_pipe@1, fun(E@1) -> E@1 /= gleam@json:null() end). -spec validate_field( crossbar@internal@field:field(FZR), list(crossbar@internal@field:rule(FZR)), list(cross_bar_error()) ) -> list(cross_bar_error()). validate_field(Field, Rules, Errors) -> case Rules of [Rule | Other_rules] -> Validation_result = case Rule of required -> crossbar@internal@field:validate_required(Field); {min_size, Size} -> crossbar@internal@field:validate_min_value(Field, Size); {max_size, Size@1} -> crossbar@internal@field:validate_max_value(Field, Size@1); {min_length, Length} -> crossbar@internal@field:validate_min_length(Field, Length); {max_length, Length@1} -> crossbar@internal@field:validate_max_length(Field, Length@1); {eq, _, Value} -> crossbar@internal@field:validate_eq(Field, Value); {not_eq, _, Value@1} -> crossbar@internal@field:validate_not_eq(Field, Value@1); {regex, _, Regex, _} -> crossbar@internal@field:validate_regex(Field, Regex); {validator_function, _, Func, _} -> crossbar@internal@field:use_validator_function(Field, Func) end, New_errors = (gleam@bool:guard( Validation_result, Errors, fun() -> _pipe = Errors, gleam@list:append( _pipe, [{failed_rule, erlang:element(2, Field), rule_to_string(Rule), rule_to_error_string(Rule)}] ) end )), validate_field(Field, Other_rules, New_errors); [] -> Errors end. -spec validate(crossbar@internal@field:field(FZX)) -> {ok, crossbar@internal@field:field(FZX)} | {error, list(cross_bar_error())}. validate(Field) -> Validation_result = validate_field(Field, erlang:element(4, Field), []), case Validation_result of [] -> {ok, Field}; _ -> {error, Validation_result} end. -spec validate_many(list(crossbar@internal@field:field(any())), boolean()) -> list({binary(), list(cross_bar_error())}). validate_many(Fields, Failed_only) -> _pipe = Fields, _pipe@1 = gleam@list:map( _pipe, fun(Field) -> Errors@1 = case validate(Field) of {ok, _} -> []; {error, Errors} -> Errors end, {erlang:element(2, Field), Errors@1} end ), (fun(Results) -> case Failed_only of true -> gleam@list:filter( Results, fun(Res) -> gleam@list:length(erlang:element(2, Res)) > 0 end ); false -> Results end end)(_pipe@1).