Current section
Files
Jump to
Current section
Files
src/cquill@changeset.erl
-module(cquill@changeset).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/cquill/changeset.gleam").
-export([new/1, change/2, empty/0, put_change/3, delete_change/2, get_change/2, add_error/4, clear_errors/2, force_change/3, clear_all_errors/1, get_errors/2, all_errors/1, has_error/2, has_errors/1, error_count/1, apply/1, is_valid/1, get_changes/1, get_data/1, merge_changes/1, validate_if_present/3, validate_required/2, validate_format/3, validate_length_min/3, validate_length_max/3, validate_length_exact/3, validate_length/4, validate_inclusion/3, validate_exclusion/3, validate_number_range/4, validate_confirmation/3, validate_acceptance/2, validate_custom/4, format_errors/1, format_field_errors/2]).
-export_type([changeset/0, validation_error/0, validation_type/0, length_constraint/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 changeset() :: {changeset,
gleam@option:option(gleam@dict:dict(binary(), gleam@dynamic:dynamic_())),
gleam@dict:dict(binary(), gleam@dynamic:dynamic_()),
gleam@dict:dict(binary(), list(validation_error())),
boolean(),
list(binary())}.
-type validation_error() :: {validation_error, binary(), validation_type()}.
-type validation_type() :: required |
format |
{length, length_constraint()} |
inclusion |
exclusion |
range |
{custom, binary()} |
uniqueness |
confirmation |
acceptance.
-type length_constraint() :: {min_length, integer()} |
{max_length, integer()} |
{exact_length, integer()} |
{range_length, integer(), integer()}.
-file("src/cquill/changeset.gleam", 108).
?DOC(" Create a new changeset from input data (for inserts)\n").
-spec new(gleam@dict:dict(binary(), gleam@dynamic:dynamic_())) -> changeset().
new(Changes) ->
{changeset, none, Changes, maps:new(), true, []}.
-file("src/cquill/changeset.gleam", 119).
?DOC(" Create a changeset for updating existing data\n").
-spec change(
gleam@dict:dict(binary(), gleam@dynamic:dynamic_()),
gleam@dict:dict(binary(), gleam@dynamic:dynamic_())
) -> changeset().
change(Existing, Changes) ->
{changeset, {some, Existing}, Changes, maps:new(), true, []}.
-file("src/cquill/changeset.gleam", 133).
?DOC(" Create an empty changeset (useful for forms)\n").
-spec empty() -> changeset().
empty() ->
{changeset, none, maps:new(), maps:new(), true, []}.
-file("src/cquill/changeset.gleam", 408).
?DOC(" Put a change into the changeset\n").
-spec put_change(changeset(), binary(), gleam@dynamic:dynamic_()) -> changeset().
put_change(Changeset, Field, Value) ->
{changeset,
erlang:element(2, Changeset),
gleam@dict:insert(erlang:element(3, Changeset), Field, Value),
erlang:element(4, Changeset),
erlang:element(5, Changeset),
erlang:element(6, Changeset)}.
-file("src/cquill/changeset.gleam", 417).
?DOC(" Delete a change from the changeset\n").
-spec delete_change(changeset(), binary()) -> changeset().
delete_change(Changeset, Field) ->
{changeset,
erlang:element(2, Changeset),
gleam@dict:delete(erlang:element(3, Changeset), Field),
erlang:element(4, Changeset),
erlang:element(5, Changeset),
erlang:element(6, Changeset)}.
-file("src/cquill/changeset.gleam", 422).
?DOC(" Get a change value from the changeset\n").
-spec get_change(changeset(), binary()) -> gleam@option:option(gleam@dynamic:dynamic_()).
get_change(Changeset, Field) ->
_pipe = gleam_stdlib:map_get(erlang:element(3, Changeset), Field),
gleam@option:from_result(_pipe).
-file("src/cquill/changeset.gleam", 444).
?DOC(" Add an error to the changeset\n").
-spec add_error(changeset(), binary(), binary(), validation_type()) -> changeset().
add_error(Changeset, Field, Message, Validation) ->
Error = {validation_error, Message, Validation},
Existing_errors = begin
_pipe = gleam_stdlib:map_get(erlang:element(4, Changeset), Field),
gleam@result:unwrap(_pipe, [])
end,
Updated_errors = gleam@dict:insert(
erlang:element(4, Changeset),
Field,
[Error | Existing_errors]
),
{changeset,
erlang:element(2, Changeset),
erlang:element(3, Changeset),
Updated_errors,
false,
erlang:element(6, Changeset)}.
-file("src/cquill/changeset.gleam", 460).
?DOC(" Clear all errors for a specific field\n").
-spec clear_errors(changeset(), binary()) -> changeset().
clear_errors(Changeset, Field) ->
Updated_errors = gleam@dict:delete(erlang:element(4, Changeset), Field),
Is_valid = gleam@dict:is_empty(Updated_errors),
{changeset,
erlang:element(2, Changeset),
erlang:element(3, Changeset),
Updated_errors,
Is_valid,
erlang:element(6, Changeset)}.
-file("src/cquill/changeset.gleam", 429).
?DOC(
" Force a change even if validation would fail\n"
" Use with caution — this bypasses validation for the field\n"
).
-spec force_change(changeset(), binary(), gleam@dynamic:dynamic_()) -> changeset().
force_change(Changeset, Field, Value) ->
_pipe = Changeset,
_pipe@1 = put_change(_pipe, Field, Value),
clear_errors(_pipe@1, Field).
-file("src/cquill/changeset.gleam", 467).
?DOC(" Clear all errors from the changeset\n").
-spec clear_all_errors(changeset()) -> changeset().
clear_all_errors(Changeset) ->
{changeset,
erlang:element(2, Changeset),
erlang:element(3, Changeset),
maps:new(),
true,
erlang:element(6, Changeset)}.
-file("src/cquill/changeset.gleam", 472).
?DOC(" Get errors for a specific field\n").
-spec get_errors(changeset(), binary()) -> list(validation_error()).
get_errors(Changeset, Field) ->
_pipe = gleam_stdlib:map_get(erlang:element(4, Changeset), Field),
gleam@result:unwrap(_pipe, []).
-file("src/cquill/changeset.gleam", 478).
?DOC(" Get all errors as a list of (field, error) tuples\n").
-spec all_errors(changeset()) -> list({binary(), validation_error()}).
all_errors(Changeset) ->
_pipe = maps:to_list(erlang:element(4, Changeset)),
gleam@list:flat_map(
_pipe,
fun(Pair) ->
{Field, Errors} = Pair,
gleam@list:map(Errors, fun(Err) -> {Field, Err} end)
end
).
-file("src/cquill/changeset.gleam", 487).
?DOC(" Check if a specific field has errors\n").
-spec has_error(changeset(), binary()) -> boolean().
has_error(Changeset, Field) ->
case gleam_stdlib:map_get(erlang:element(4, Changeset), Field) of
{ok, Errors} ->
not gleam@list:is_empty(Errors);
{error, _} ->
false
end.
-file("src/cquill/changeset.gleam", 496).
?DOC(
" Check if the changeset has any validation errors\n"
" This is a convenience function to check if any field has errors\n"
).
-spec has_errors(changeset()) -> boolean().
has_errors(Changeset) ->
not gleam@dict:is_empty(erlang:element(4, Changeset)).
-file("src/cquill/changeset.gleam", 502).
?DOC(
" Count the total number of validation errors across all fields\n"
" Returns the sum of all errors on all fields\n"
).
-spec error_count(changeset()) -> integer().
error_count(Changeset) ->
_pipe = erlang:element(4, Changeset),
_pipe@1 = maps:values(_pipe),
_pipe@2 = gleam@list:map(_pipe@1, fun erlang:length/1),
gleam@int:sum(_pipe@2).
-file("src/cquill/changeset.gleam", 515).
?DOC(
" Apply the changeset and return the result\n"
" Returns Ok(changes) if valid, Error(errors) if invalid\n"
).
-spec apply(changeset()) -> {ok,
gleam@dict:dict(binary(), gleam@dynamic:dynamic_())} |
{error, gleam@dict:dict(binary(), list(validation_error()))}.
apply(Changeset) ->
case erlang:element(5, Changeset) of
true ->
{ok, erlang:element(3, Changeset)};
false ->
{error, erlang:element(4, Changeset)}
end.
-file("src/cquill/changeset.gleam", 525).
?DOC(" Check if the changeset is valid\n").
-spec is_valid(changeset()) -> boolean().
is_valid(Changeset) ->
erlang:element(5, Changeset).
-file("src/cquill/changeset.gleam", 530).
?DOC(" Get the changes from the changeset (regardless of validity)\n").
-spec get_changes(changeset()) -> gleam@dict:dict(binary(), gleam@dynamic:dynamic_()).
get_changes(Changeset) ->
erlang:element(3, Changeset).
-file("src/cquill/changeset.gleam", 535).
?DOC(" Get the original data (if this is an update changeset)\n").
-spec get_data(changeset()) -> gleam@option:option(gleam@dict:dict(binary(), gleam@dynamic:dynamic_())).
get_data(Changeset) ->
erlang:element(2, Changeset).
-file("src/cquill/changeset.gleam", 541).
?DOC(
" Merge changes from the changeset with the original data\n"
" For updates, this combines old data with new changes\n"
).
-spec merge_changes(changeset()) -> gleam@dict:dict(binary(), gleam@dynamic:dynamic_()).
merge_changes(Changeset) ->
case erlang:element(2, Changeset) of
none ->
erlang:element(3, Changeset);
{some, Data} ->
maps:merge(Data, erlang:element(3, Changeset))
end.
-file("src/cquill/changeset.gleam", 553).
?DOC(" Get a field value from changes or original data\n").
-spec get_field(changeset(), binary()) -> gleam@option:option(gleam@dynamic:dynamic_()).
get_field(Changeset, Field) ->
case gleam_stdlib:map_get(erlang:element(3, Changeset), Field) of
{ok, Value} ->
{some, Value};
{error, _} ->
case erlang:element(2, Changeset) of
none ->
none;
{some, Data} ->
_pipe = gleam_stdlib:map_get(Data, Field),
gleam@option:from_result(_pipe)
end
end.
-file("src/cquill/changeset.gleam", 392).
?DOC(
" Validate a field only if it is present (non-nil)\n"
" Useful for optional fields that have constraints when provided\n"
).
-spec validate_if_present(
changeset(),
binary(),
fun((changeset()) -> changeset())
) -> changeset().
validate_if_present(Changeset, Field, Validator) ->
case get_field(Changeset, Field) of
none ->
Changeset;
{some, _} ->
Validator(Changeset)
end.
-file("src/cquill/changeset.gleam", 568).
?DOC(" Get a field value as a string\n").
-spec get_field_string(changeset(), binary()) -> gleam@option:option(binary()).
get_field_string(Changeset, Field) ->
case get_field(Changeset, Field) of
none ->
none;
{some, Value} ->
case gleam@dynamic@decode:run(
Value,
{decoder, fun gleam@dynamic@decode:decode_string/1}
) of
{ok, S} ->
{some, S};
{error, _} ->
none
end
end.
-file("src/cquill/changeset.gleam", 581).
?DOC(" Get a field value as an int\n").
-spec get_field_int(changeset(), binary()) -> gleam@option:option(integer()).
get_field_int(Changeset, Field) ->
case get_field(Changeset, Field) of
none ->
none;
{some, Value} ->
case gleam@dynamic@decode:run(
Value,
{decoder, fun gleam@dynamic@decode:decode_int/1}
) of
{ok, I} ->
{some, I};
{error, _} ->
none
end
end.
-file("src/cquill/changeset.gleam", 594).
?DOC(" Get a field value as a bool\n").
-spec get_field_bool(changeset(), binary()) -> gleam@option:option(boolean()).
get_field_bool(Changeset, Field) ->
case get_field(Changeset, Field) of
none ->
none;
{some, Value} ->
case gleam@dynamic@decode:run(
Value,
{decoder, fun gleam@dynamic@decode:decode_bool/1}
) of
{ok, B} ->
{some, B};
{error, _} ->
none
end
end.
-file("src/cquill/changeset.gleam", 607).
?DOC(" Check if a value is present (not None and not empty string)\n").
-spec is_present(gleam@option:option(gleam@dynamic:dynamic_())) -> boolean().
is_present(Value) ->
case Value of
none ->
false;
{some, V} ->
case gleam@dynamic@decode:run(
V,
{decoder, fun gleam@dynamic@decode:decode_string/1}
) of
{ok, S} ->
not gleam@string:is_empty(S);
{error, _} ->
true
end
end.
-file("src/cquill/changeset.gleam", 620).
?DOC(" Mark a field as validated\n").
-spec mark_validated(changeset(), binary()) -> changeset().
mark_validated(Changeset, Field) ->
case gleam@list:contains(erlang:element(6, Changeset), Field) of
true ->
Changeset;
false ->
{changeset,
erlang:element(2, Changeset),
erlang:element(3, Changeset),
erlang:element(4, Changeset),
erlang:element(5, Changeset),
[Field | erlang:element(6, Changeset)]}
end.
-file("src/cquill/changeset.gleam", 148).
?DOC(" Validate that the specified fields are present and non-empty\n").
-spec validate_required(changeset(), list(binary())) -> changeset().
validate_required(Changeset, Fields) ->
gleam@list:fold(
Fields,
Changeset,
fun(Cs, Field) ->
Value = get_field(Cs, Field),
case is_present(Value) of
true ->
mark_validated(Cs, Field);
false ->
add_error(Cs, Field, <<"is required"/utf8>>, required)
end
end
).
-file("src/cquill/changeset.gleam", 162).
?DOC(" Validate that a string field matches a regex pattern\n").
-spec validate_format(changeset(), binary(), binary()) -> changeset().
validate_format(Changeset, Field, Pattern) ->
case get_field_string(Changeset, Field) of
none ->
mark_validated(Changeset, Field);
{some, Value} ->
case gleam@regexp:from_string(Pattern) of
{error, _} ->
add_error(
Changeset,
Field,
<<"has invalid format"/utf8>>,
format
);
{ok, Re} ->
case gleam@regexp:check(Re, Value) of
true ->
mark_validated(Changeset, Field);
false ->
add_error(
Changeset,
Field,
<<"has invalid format"/utf8>>,
format
)
end
end
end.
-file("src/cquill/changeset.gleam", 220).
-spec validate_length_impl(
changeset(),
binary(),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(integer())
) -> changeset().
validate_length_impl(Changeset, Field, Min, Max, Exact) ->
case get_field_string(Changeset, Field) of
none ->
mark_validated(Changeset, Field);
{some, Value} ->
Len = string:length(Value),
case Exact of
{some, Exact_len} when Len =/= Exact_len ->
add_error(
Changeset,
Field,
<<<<"must be exactly "/utf8,
(erlang:integer_to_binary(Exact_len))/binary>>/binary,
" characters"/utf8>>,
{length, {exact_length, Exact_len}}
);
{some, _} ->
mark_validated(Changeset, Field);
none ->
case {Min, Max} of
{{some, Min_len}, {some, Max_len}} when (Len < Min_len) orelse (Len > Max_len) ->
add_error(
Changeset,
Field,
<<<<<<<<"must be between "/utf8,
(erlang:integer_to_binary(
Min_len
))/binary>>/binary,
" and "/utf8>>/binary,
(erlang:integer_to_binary(Max_len))/binary>>/binary,
" characters"/utf8>>,
{length, {range_length, Min_len, Max_len}}
);
{{some, Min_len@1}, none} when Len < Min_len@1 ->
add_error(
Changeset,
Field,
<<<<"must be at least "/utf8,
(erlang:integer_to_binary(Min_len@1))/binary>>/binary,
" characters"/utf8>>,
{length, {min_length, Min_len@1}}
);
{none, {some, Max_len@1}} when Len > Max_len@1 ->
add_error(
Changeset,
Field,
<<<<"must be at most "/utf8,
(erlang:integer_to_binary(Max_len@1))/binary>>/binary,
" characters"/utf8>>,
{length, {max_length, Max_len@1}}
);
{_, _} ->
mark_validated(Changeset, Field)
end
end
end.
-file("src/cquill/changeset.gleam", 184).
?DOC(" Validate string length with minimum constraint\n").
-spec validate_length_min(changeset(), binary(), integer()) -> changeset().
validate_length_min(Changeset, Field, Min) ->
validate_length_impl(Changeset, Field, {some, Min}, none, none).
-file("src/cquill/changeset.gleam", 193).
?DOC(" Validate string length with maximum constraint\n").
-spec validate_length_max(changeset(), binary(), integer()) -> changeset().
validate_length_max(Changeset, Field, Max) ->
validate_length_impl(Changeset, Field, none, {some, Max}, none).
-file("src/cquill/changeset.gleam", 202).
?DOC(" Validate string length with exact length constraint\n").
-spec validate_length_exact(changeset(), binary(), integer()) -> changeset().
validate_length_exact(Changeset, Field, Length) ->
validate_length_impl(Changeset, Field, none, none, {some, Length}).
-file("src/cquill/changeset.gleam", 211).
?DOC(" Validate string length with min and max constraints\n").
-spec validate_length(changeset(), binary(), integer(), integer()) -> changeset().
validate_length(Changeset, Field, Min, Max) ->
validate_length_impl(Changeset, Field, {some, Min}, {some, Max}, none).
-file("src/cquill/changeset.gleam", 276).
?DOC(" Validate that a field value is in a list of allowed values\n").
-spec validate_inclusion(changeset(), binary(), list(binary())) -> changeset().
validate_inclusion(Changeset, Field, Allowed) ->
case get_field_string(Changeset, Field) of
none ->
mark_validated(Changeset, Field);
{some, Value} ->
case gleam@list:contains(Allowed, Value) of
true ->
mark_validated(Changeset, Field);
false ->
add_error(
Changeset,
Field,
<<"is not a valid option"/utf8>>,
inclusion
)
end
end.
-file("src/cquill/changeset.gleam", 293).
?DOC(" Validate that a field value is NOT in a list of excluded values\n").
-spec validate_exclusion(changeset(), binary(), list(binary())) -> changeset().
validate_exclusion(Changeset, Field, Excluded) ->
case get_field_string(Changeset, Field) of
none ->
mark_validated(Changeset, Field);
{some, Value} ->
case gleam@list:contains(Excluded, Value) of
true ->
add_error(
Changeset,
Field,
<<"is not allowed"/utf8>>,
exclusion
);
false ->
mark_validated(Changeset, Field)
end
end.
-file("src/cquill/changeset.gleam", 310).
?DOC(" Validate that a numeric field is within a range\n").
-spec validate_number_range(
changeset(),
binary(),
gleam@option:option(integer()),
gleam@option:option(integer())
) -> changeset().
validate_number_range(Changeset, Field, Min, Max) ->
case get_field_int(Changeset, Field) of
none ->
mark_validated(Changeset, Field);
{some, Value} ->
case {Min, Max} of
{{some, Min_val}, {some, Max_val}} when (Value < Min_val) orelse (Value > Max_val) ->
add_error(
Changeset,
Field,
<<<<<<"must be between "/utf8,
(erlang:integer_to_binary(Min_val))/binary>>/binary,
" and "/utf8>>/binary,
(erlang:integer_to_binary(Max_val))/binary>>,
range
);
{{some, Min_val@1}, none} when Value < Min_val@1 ->
add_error(
Changeset,
Field,
<<"must be at least "/utf8,
(erlang:integer_to_binary(Min_val@1))/binary>>,
range
);
{none, {some, Max_val@1}} when Value > Max_val@1 ->
add_error(
Changeset,
Field,
<<"must be at most "/utf8,
(erlang:integer_to_binary(Max_val@1))/binary>>,
range
);
{_, _} ->
mark_validated(Changeset, Field)
end
end.
-file("src/cquill/changeset.gleam", 352).
?DOC(
" Validate that a confirmation field matches the original field\n"
" e.g., password and password_confirmation\n"
).
-spec validate_confirmation(changeset(), binary(), binary()) -> changeset().
validate_confirmation(Changeset, Field, Confirmation_field) ->
Original = get_field_string(Changeset, Field),
Confirmation = get_field_string(Changeset, Confirmation_field),
case {Original, Confirmation} of
{{some, Orig}, {some, Conf}} when Orig =/= Conf ->
add_error(
Changeset,
Confirmation_field,
<<"does not match"/utf8>>,
confirmation
);
{_, _} ->
mark_validated(Changeset, Confirmation_field)
end.
-file("src/cquill/changeset.gleam", 368).
?DOC(" Validate that a boolean field is true (for accepting terms, etc.)\n").
-spec validate_acceptance(changeset(), binary()) -> changeset().
validate_acceptance(Changeset, Field) ->
case get_field_bool(Changeset, Field) of
{some, true} ->
mark_validated(Changeset, Field);
{some, false} ->
add_error(Changeset, Field, <<"must be accepted"/utf8>>, acceptance);
none ->
add_error(Changeset, Field, <<"must be accepted"/utf8>>, acceptance)
end.
-file("src/cquill/changeset.gleam", 378).
?DOC(
" Apply a custom validation function\n"
" The function should return Ok(Nil) if valid, or Error(message) if invalid\n"
).
-spec validate_custom(
changeset(),
binary(),
binary(),
fun((changeset()) -> {ok, nil} | {error, binary()})
) -> changeset().
validate_custom(Changeset, Field, Name, Validator) ->
case Validator(Changeset) of
{ok, _} ->
mark_validated(Changeset, Field);
{error, Message} ->
add_error(Changeset, Field, Message, {custom, Name})
end.
-file("src/cquill/changeset.gleam", 636).
?DOC(" Format all errors as a human-readable string\n").
-spec format_errors(changeset()) -> binary().
format_errors(Changeset) ->
_pipe = all_errors(Changeset),
_pipe@1 = gleam@list:map(
_pipe,
fun(Pair) ->
{Field, Error} = Pair,
<<<<Field/binary, " "/utf8>>/binary,
(erlang:element(2, Error))/binary>>
end
),
gleam@string:join(_pipe@1, <<", "/utf8>>).
-file("src/cquill/changeset.gleam", 646).
?DOC(" Format errors for a specific field\n").
-spec format_field_errors(changeset(), binary()) -> binary().
format_field_errors(Changeset, Field) ->
_pipe = get_errors(Changeset, Field),
_pipe@1 = gleam@list:map(_pipe, fun(Error) -> erlang:element(2, Error) end),
gleam@string:join(_pipe@1, <<", "/utf8>>).