Skip to content
Laravel Warrant is in beta and still being tested — expect API changes between releases. Report an issue.

Errors & exceptions

Warrant fails loudly. A typo in a stored rule, a missing context key, or a misconfigured schema throws rather than silently granting or denying. This is the catalogue.

Stage What’s checked
Parse time Rule syntax, binding consistency (WarrantSyntaxException)
Compile / validate time Ability / condition names exist on the schema
Check time Requested ability exists; required context present; user available
Boot / reflection Schema registry uniqueness; condition method signatures

Thrown eagerly from the lexer/parser. Extends RuntimeException and carries $source, $offset, $sourceLine, and $sourceColumn. The message includes the line, column, and a caret:

Reserved word 'can' cannot be used as a name; expected an ability name. (line 1, column 21)
if is_self they can can
^

Representative messages:

  • Unexpected character %s.
  • Unterminated string literal.
  • Invalid escape sequence "\%s"; only \', \", and \\ are allowed.
  • Expected 'context' after '@'. / Expected a context key after '@context'.
  • Expected 'can' or 'cannot' after 'they'.
  • Expected at least one 'they can ...' or 'they cannot ...' clause.
  • Expected ')' to close the group. / Expected ')' to close the condition arguments.
  • Reserved word '%s' cannot be used as a name; expected %s.
  • Expected a rule. / Expected a single rule but found multiple.

Binding errors (also WarrantSyntaxException)

Section titled “Binding errors (also WarrantSyntaxException)”
  • Cannot mix named and positional bindings.
  • No binding provided for ":%s".
  • More positional placeholders (?) than bindings provided.
  • %d positional binding(s) were provided but never used.
  • Binding(s) provided but never used: %s.

Validation errors → InvalidArgumentException

Section titled “Validation errors → InvalidArgumentException”

Thrown when a rule set is validated/compiled against a schema:

  • Ability [%s] is not declared by the schema.
  • Condition [%s] is not declared by the schema.
  • Condition [%s] requires at least %d argument(s), but the rule supplied %d.

Context keys need no declaration to be referenced in a rule (@context <key>) or read in a condition, so there is no “unknown context key” validation error.

Attaching a denial message to a rule that has no theyCannot clause is also rejected here — only a cannot rule may carry one, whether it was set with withDenialMessage() or written in the DSL with because. See Denial messages.

fromRules / validateAll type-guard their inputs:

  • fromRules expects WarrantRule or WarrantRuleBuilder instances, got %s.
  • validateAll expects WarrantRuleSet instances, got %s.

Condition / reflection errors → InvalidArgumentException

Section titled “Condition / reflection errors → InvalidArgumentException”

Thrown lazily the first time a schema’s conditions are reflected:

  • Condition method [%s::%s] must not declare duplicate condition attributes.
  • Condition method [%s::%s] cannot declare both #[RowCondition] and #[GlobalCondition].
  • Condition method [%s::%s] must resolve to a non-empty condition key.
  • Condition method [%s::%s] must accept a [%s] as its first parameter. — a missing or wrong-typed context parameter.
  • Schema [%s] has no rows and does not support targeted checks; use a no-target check instead.
  • Schema [%s] has rows but no way to name one, so it does not support targeted checks; declare const key for the column its rows are identified by, or a matchKey() of its own. Filtering a query and selecting per-row abilities need neither.

From the condition resolver:

  • BadMethodCallException — Condition [%s] is not defined on schema [%s].
  • InvalidArgumentException — Condition [%s] on schema [%s] requires a target row. (a row condition run with no target)
  • InvalidArgumentException — Condition [%s] on schema [%s] requires at least %d argument(s), but the rule supplied %d. (fewer arguments than the condition’s required parameters)

From the compiler, on what a condition emitted:

  • InvalidArgumentException — Condition [%s] on schema [%s] may only add where clauses, but it emitted a [%s]; ... (a join, groupBy, having, aggregate, or union — none of which can be spliced into an OR or negated in place)
  • InvalidArgumentException — Condition [%s] on schema [%s] added no where clause; a condition must add at least one where clause, return true/false to decide the outcome outright, or return null to answer unknown. (a condition that returned its query untouched — see How it compiles)
  • InvalidArgumentException — Condition [%s] on schema [%s] returned null, answering unknown, but also added a where clause; return the builder it constrained, or answer unknown without constraining it. (almost always a missing return — see Answering unknown)

From validation, on a handle against the target schema’s row key — and from the compiler, which makes the same checks for handles that never pass through the parser:

  • A %s(...) reference targets a specific row of schema [%s], but [%s] has no way to name one; declare \const key` for the column its rows are identified by, or a matchKey() of its own, or drop the row selector.`
  • A %s(...) reference to schema [%s] supplies %d row-key argument(s), but that schema's row key requires at least %d.

From the key’s own dispatch:

  • The row key for schema [%s] requires at least %d argument(s), but %d were supplied.
  • The row key for schema [%s] must return the query it constrained, or null to answer unknown; it returned a [%s].

From the schema’s own declaration:

  • Schema [%s] declares a condition attribute on matchKey(), which is the schema's row key and not part of its rule vocabulary; remove the attribute, or move the logic to a condition method of its own.
  • Schema [%s] must accept a [%s] as the first parameter of matchKey().

From a schema declaring both row sources, on first resolution:

  • Schema [%s] names model [%s] and also defines a virtualTable(); a schema draws its rows from one or the other. Drop the model to make it a virtual table, or drop virtualTable() to keep the model's own table.
  • Schema [%s] names model [%s] and also declares a key [%s]; a model answers for its own key, so drop the constant. It is for a virtual table, whose rows have no key of their own.

From a condition or key over rows with no key column of their own:

  • These rows have no key column of their own, so row() must be given a column name; rows drawn from a virtualTable() need a matchKey() that names its own columns. (BadMethodCallException)

An empty argument list is not an error in itself: it addresses a row by a key that requires no arguments, exactly as schema() does in rule text. It is the arity message above that rejects it against a key which does require some. Only null is a no-target check.

Cross-schema row selectors → InvalidArgumentException

Section titled “Cross-schema row selectors → InvalidArgumentException”

From the compiler, on the value inside a can(... for schema(<row>)) or check(... for schema(<row>)) handle. Both replace what used to be a query that silently matched no row:

  • The row selector for schema [%s] is a [%s], which is not that schema's model [%s]; pass that schema's own model or a row key.
  • The row selector for schema [%s] is a [%s], which cannot identify a row; pass a key, that schema's model, or a @column/@sql reference.

See What a row selector may be for the accepted values.

Context errors → InvalidArgumentException

Section titled “Context errors → InvalidArgumentException”
Schema [%s] requires context key(s) [%s]; supply them at the check or via defaultContext().

See Check-time context. Note that an optional key that’s absent doesn’t throw — it’s passed to its condition as null (standard SQL logic then applies, which is fail-closed).

  • InvalidArgumentException — Schema [...] is registered under more than one schema key [...] (when the index is first built from the container)
  • OutOfBoundsException — No Warrant %s registered for reference [%s]. — where %s is the coordinate being resolved (schema or model) and the reference is the class name or key that failed to resolve (e.g. No Warrant schema registered for reference [documents].)

These fire the first time a schema is resolved, not at boot — checking any of them requires loading the schema class, which is exactly what the index defers:

  • LogicException — Schema key [...] is registered to [...], which is not a Warrant\Schema\WarrantSchema.
  • LogicException — Schema [...] names model [...], which is not an Eloquent model.
  • LogicException — Schema [...] names model [...], but that model does not use the Warrant\HasWarrantSchema trait, ...
  • LogicException — Model [...] must declare warrantSchema() as \public static`.`
  • LogicException — Schema [...] names model [...], but that model names schema [...]; a schema and its model must name each other.

The same pair is checked from the model end when the reference is a model — a row check, a query scope, or loadUserAbilities(). That direction is the one that catches a subclass inheriting warrantSchema() from its parent:

  • LogicException — Model [...] must name a Warrant\Schema\WarrantSchema, but names [...].
  • LogicException — Model [...] names schema [...], but that schema names model [...]; a schema and its model must name each other.

Authorization failures → WarrantAuthorizationException

Section titled “Authorization failures → WarrantAuthorizationException”

Thrown by authorize() / authorizeAny() when a check is denied. It extends Illuminate\Auth\Access\AuthorizationException, so Laravel renders it as HTTP 403 automatically.

public function __construct(
string $message = 'This action is unauthorized.',
?WarrantDenialContext $denial = null,
);
public readonly ?WarrantDenialContext $denial; // the diagnosed denial, or null for a generic denial

The $denial property carries a diagnosed denial-context data object (a plain final readonly object under Warrant\, not an exception) describing why the check failed:

  • WarrantGate — the requested array $abilities (normalized, wildcards resolved) and AbilityMatchMode $matchMode.
  • WarrantDenialContext — $user, ?Model $target, string $schema, array $context, WarrantGate $gate, the responsible WarrantRule $rule (the matching cannot), and array $deniedAbilities.
  • WarrantUngrantedContext — same fields minus $rule, with array $ungrantedAbilities in place of deniedAbilities (the whole gate under ANY; the missing subset under ALL).

See Denial messages for attaching messages and the schema fallback hooks.

See Middleware API. Unauthenticated or unauthorized requests throw WarrantAuthorizationException (rendered as 403); misconfiguration throws InvalidArgumentException. The warrant: gate and the reachability guards use distinct message strings — see the Middleware API errors table.

“No authenticated user” → depends on the entry point

Section titled ““No authenticated user” → depends on the entry point”

When no user is passed and none is authenticated, the failure surfaces two ways:

  • InvalidArgumentException — Warrant requires an authenticated user or an explicit user instance. — from the engine entry points (Warrant::guard(), Warrant::forSchema(), and every facade check / reachability helper that resolves the current user).
  • LogicException — from the query scopes / instance helpers that need a user but weren’t given one (scopeUserHasAbility, scopeSelectUserAbilities, loadUserAbilities).

Thrown by toSyntax() when a rule can’t be rendered as inline DSL — use toBoundSyntax() instead:

  • A constant boolean expression has no rule-language representation.
  • Condition parameter of type %s cannot be written inline; use toBoundSyntax().
  • NAN/INF cannot be written inline; use toBoundSyntax().
  • Float %s requires exponent notation, unsupported inline; use toBoundSyntax().

Configuration & driver errors → RuntimeException

Section titled “Configuration & driver errors → RuntimeException”
  • No Warrant rule resolver configured. Set warrant.rule_resolver to a class implementing Warrant\Rules\RuleResolver.
  • Warrant ability selection does not support the [%s] database driver. (a driver other than PostgreSQL, MySQL/MariaDB, or SQLite for the per-row abilities column)