Errors — canonical shape and Splode wrapping

Copy Markdown View Source

Every error return from this library is {:error, list_of_error_maps}. Each map has one of two shapes — never anything else.

Canonical error map

%{
  field: atom(),                # always present; :__root__ for non-field errors
  action: atom(),               # which check produced the error
  message: String.t(),          # human-readable
  errors: [error_map()] | nil   # only on :conditionals aggregators
  # __hint__: String.t()        # present when the entity carried a `hint:` option
}

Examples:

%{field: :email,    action: :email_r,         message: "..."}
%{field: :username, action: :required_fields, message: "Please submit required fields."}
%{field: :role_id,  action: :authorized_fields, message: "Unauthorized keys are present in the sent data."}
%{field: :__root__, action: :bad_parameters,  message: "..."}
%{field: :actor,    action: :conditionals,    errors: [...]}      # nested

Multi-field errors

:required_fields and :authorized_fields emit one entry per affected field (not one map with a fields: list). Filter by action to collect:

errs
|> Enum.filter(&match?(%{action: :required_fields}, &1))
|> Enum.map(& &1.field)

Splode wrapping (opt-in)

GuardedStruct.Errors.from_tuple/1 converts the error list into a Splode Ash.Error-style class for systems that want exception objects rather than tuples.

case MyApp.User.builder(input) do
  {:ok, user} -> user
  {:error, errs} ->
    GuardedStruct.Errors.from_tuple({:error, errs})  # Splode.Error class
end

Implementations (internal — accessed only through GuardedStruct.Errors.from_tuple/1):

  • Validation — wraps per-field errors.
  • Unknown — fallback for anything else.
  • Invalid — class container.

Each child error becomes a %GuardedStruct.Errors.Validation{} struct with field, action, message, hint, and vars fields. :conditionals aggregators preserve their child_errors recursively.

Section error: true

Setting error: true on the section (or on a sub_field) generates a Module.Error exception:

guardedstruct error: true do
  field :email, :string, enforce: true
end

MyApp.User.builder(%{}, true)
# ** (MyApp.User.Error) ...

builder/2's second argument toggles raise-mode. Without error: true, builder/2 ignores the flag and still returns {:error, list}.

Compile-time errors

The derive parser surfaces as Spark.Error.DslError with path: (DSL location) and module: (caller) — these never reach runtime. Triggers:

  • Malformed derives: string, or an operand that can't be parsed.
  • A bad parameter (max_len=foo, min_len=-2, equal=Integer::abc).
  • Unknown sanitize/validate op inside each=[...] / optional=[...] (and not declared in any registered extension).

Spark verifiers report through the same error type but only warn: the module still compiles. Triggers:

  • validator: {Mod, :fn} not exported.
  • auto: {Mod, :fn} not exported.
  • struct: AnotherMod cycle.