AshGrant.Explanation (AshGrant v0.14.1)

Copy Markdown View Source

Represents a detailed explanation of an authorization decision.

This struct is returned by AshGrant.explain/4 and provides comprehensive information about why an authorization check succeeded or failed.

Fields

  • :resource - The Ash resource being checked
  • :action - The action being checked (e.g., :read, :update)
  • :actor - The actor performing the action
  • :context - Optional context passed to the check
  • :decision - The final decision (:allow or :deny)
  • :reason - Reason for denial (:no_matching_permissions, :denied_by_rule, etc.)
  • :matching_permissions - List of permissions that matched and granted access
  • :evaluated_permissions - All permissions that were evaluated with match status
  • :scope_filter - The resolved scope filter expression (for reads)
  • :field_groups - List of field group names the actor has access to (from 5-part permissions)
  • :field_group_defs - Field group definitions from the resource DSL
  • :resolve_arguments - resolve_argument declarations active for this action, each annotated with the scope atoms that trigger the resolver at runtime (see guides/argument-based-scope.md)

Example

iex> result = AshGrant.explain(MyApp.Post, :read, actor)
%AshGrant.Explanation{
  resource: MyApp.Post,
  action: :read,
  decision: :allow,
  matching_permissions: [
    %{
      permission: "post:*:read:always",
      description: "Read all posts",
      source: "editor_role",
      scope_name: :all,
      scope_description: "All records without restriction",
      field_group: nil
    }
  ],
  field_groups: [],
  field_group_defs: [],
  ...
}

iex> AshGrant.Explanation.to_string(result)
"""
═══════════════════════════════════════════════════════════════════
Authorization Explanation for MyApp.Post
═══════════════════════════════════════════════════════════════════
Action:   read
Decision: ✓ ALLOW
...
"""

Summary

Functions

Converts an Explanation struct to a human-readable string.

Types

evaluated_permission()

@type evaluated_permission() :: %{
  permission: String.t(),
  matched: boolean(),
  reason: String.t() | nil,
  description: String.t() | nil,
  source: String.t() | nil,
  scope_name: atom() | nil,
  scope_description: String.t() | nil,
  field_group: String.t() | nil
}

resolve_argument_entry()

@type resolve_argument_entry() :: %{
  name: atom(),
  from_path: [atom()],
  for_actions: [atom()] | nil,
  scopes_needing: [atom()]
}

t()

@type t() :: %AshGrant.Explanation{
  action: atom(),
  actor: term(),
  context: map() | nil,
  decision: :allow | :deny,
  evaluated_permissions: [evaluated_permission()],
  field_group_defs: [AshGrant.Dsl.FieldGroup.t()],
  field_groups: [String.t()],
  matching_permissions: [evaluated_permission()],
  reason: atom() | nil,
  resolve_arguments: [resolve_argument_entry()],
  resource: module(),
  scope_filter: term() | nil
}

Functions

to_string(explanation, opts \\ [])

@spec to_string(
  t(),
  keyword()
) :: String.t()

Converts an Explanation struct to a human-readable string.

Options

  • :color - Whether to include ANSI color codes (default: true)
  • :verbose - Whether to include all evaluated permissions (default: false)

Example

iex> result = AshGrant.explain(MyApp.Post, :read, actor)
iex> IO.puts(AshGrant.Explanation.to_string(result))

Authorization Explanation for MyApp.Post

Action:   read
Decision:  ALLOW
...