# `AshGrant.Info`
[🔗](https://github.com/jhlee111/ash_grant/blob/v0.14.1/lib/ash_grant/info.ex#L1)

Introspection helpers for AshGrant DSL configuration.

This module provides functions to query AshGrant configuration at runtime,
including resolvers, scopes, field groups, and policy settings.

## Common Functions

| Function | Description |
|----------|-------------|
| `resolver/1` | Get the permission resolver for a resource |
| `default_policies/1` | Get the default_policies setting |
| `default_field_policies/1` | Get the default_field_policies setting |
| `resource_name/1` | Get the resource name for permission matching |
| `scopes/1` | Get all scope definitions |
| `get_scope/2` | Get a specific scope by name |
| `resolve_scope_filter/3` | Resolve a scope to its read filter expression |
| `resolve_write_scope_filter/3` | Resolve a scope to its write filter expression |
| `field_groups/1` | Get all field group definitions |
| `get_field_group/2` | Get a specific field group by name |
| `resolve_field_group/2` | Resolve a field group with inheritance |
| `owner_field/1` | **Deprecated.** Use explicit scope expressions instead |

## Example

    iex> AshGrant.Info.default_policies(MyApp.Blog.Post)
    true

    iex> AshGrant.Info.resolver(MyApp.Blog.Post)
    MyApp.PermissionResolver

    iex> AshGrant.Info.scopes(MyApp.Blog.Post) |> Enum.map(& &1.name)
    [:all, :own, :published]

# `ash_grant`

```elixir
@spec ash_grant(dsl_or_extended :: module() | map()) :: [struct()]
```

ash_grant DSL entities

# `ash_grant_can_perform_actions`

```elixir
@spec ash_grant_can_perform_actions(dsl_or_extended :: module() | map()) ::
  {:ok, [atom()]} | :error
```

List of action names to generate CanPerform calculations for.
Each generates a `:can_<action>?` boolean calculation (public by default).

## Example

    can_perform_actions [:update, :destroy]

Generates `:can_update?` and `:can_destroy?` calculations.

# `ash_grant_can_perform_actions!`

```elixir
@spec ash_grant_can_perform_actions!(dsl_or_extended :: module() | map()) ::
  [atom()] | no_return()
```

List of action names to generate CanPerform calculations for.
Each generates a `:can_<action>?` boolean calculation (public by default).

## Example

    can_perform_actions [:update, :destroy]

Generates `:can_update?` and `:can_destroy?` calculations.

# `ash_grant_default_field_policies`

```elixir
@spec ash_grant_default_field_policies(dsl_or_extended :: module() | map()) ::
  {:ok, boolean()} | :error
```

Automatically generate Ash `field_policies` from `field_group` definitions.

When `true`, AshGrant generates field policies that use `AshGrant.FieldCheck`
to authorize field access based on the 5th part of permission strings.

When `false` (default), you can manually write `field_policies` using
`AshGrant.field_check/1` (Mode A).

# `ash_grant_default_field_policies!`

```elixir
@spec ash_grant_default_field_policies!(dsl_or_extended :: module() | map()) ::
  boolean() | no_return()
```

Automatically generate Ash `field_policies` from `field_group` definitions.

When `true`, AshGrant generates field policies that use `AshGrant.FieldCheck`
to authorize field access based on the 5th part of permission strings.

When `false` (default), you can manually write `field_policies` using
`AshGrant.field_check/1` (Mode A).

# `ash_grant_default_policies`

```elixir
@spec ash_grant_default_policies(dsl_or_extended :: module() | map()) ::
  {:ok, boolean() | :all | :write | :read} | :error
```

Automatically generate standard AshGrant policies.

When enabled, AshGrant will automatically add policies to your resource,
eliminating the need to manually define the `policies` block.

Options:
- `false` - No policies are generated (default, explicit policies required)
- `true` or `:all` - Generate policies for both read and write actions
- `:read` - Only generate policy for read actions (filter_check)
- `:write` - Only generate policy for write actions (check)

Generated policies:
```elixir
policies do
  policy action_type(:read) do
    authorize_if AshGrant.filter_check()
  end

  policy action_type([:create, :update, :destroy]) do
    authorize_if AshGrant.check()
  end
end
```

Note: When using `default_policies`, you should still add
`authorizers: [Ash.Policy.Authorizer]` to your resource options.

# `ash_grant_default_policies!`

```elixir
@spec ash_grant_default_policies!(dsl_or_extended :: module() | map()) ::
  (boolean() | :all | :write | :read) | no_return()
```

Automatically generate standard AshGrant policies.

When enabled, AshGrant will automatically add policies to your resource,
eliminating the need to manually define the `policies` block.

Options:
- `false` - No policies are generated (default, explicit policies required)
- `true` or `:all` - Generate policies for both read and write actions
- `:read` - Only generate policy for read actions (filter_check)
- `:write` - Only generate policy for write actions (check)

Generated policies:
```elixir
policies do
  policy action_type(:read) do
    authorize_if AshGrant.filter_check()
  end

  policy action_type([:create, :update, :destroy]) do
    authorize_if AshGrant.check()
  end
end
```

Note: When using `default_policies`, you should still add
`authorizers: [Ash.Policy.Authorizer]` to your resource options.

# `ash_grant_instance_key`

```elixir
@spec ash_grant_instance_key(dsl_or_extended :: module() | map()) ::
  {:ok, atom()} | :error
```

Field to match instance permission IDs against. Defaults to `:id` (primary key).

When set, instance permissions like `"feed:feed_abc:read:"` will generate a
filter matching the specified field instead of the primary key.

## Example

    ash_grant do
      instance_key :feed_id
    end

With this, `"feed:feed_abc:read:"` generates `WHERE feed_id IN ('feed_abc')`
instead of `WHERE id IN ('feed_abc')`.

# `ash_grant_instance_key!`

```elixir
@spec ash_grant_instance_key!(dsl_or_extended :: module() | map()) ::
  atom() | no_return()
```

Field to match instance permission IDs against. Defaults to `:id` (primary key).

When set, instance permissions like `"feed:feed_abc:read:"` will generate a
filter matching the specified field instead of the primary key.

## Example

    ash_grant do
      instance_key :feed_id
    end

With this, `"feed:feed_abc:read:"` generates `WHERE feed_id IN ('feed_abc')`
instead of `WHERE id IN ('feed_abc')`.

# `ash_grant_options`

```elixir
@spec ash_grant_options(dsl_or_extended :: module() | map()) :: %{
  required(atom()) =&gt; any()
}
```

ash_grant DSL options

Returns a map containing the and any configured or default values.

# `ash_grant_owner_field`

```elixir
@spec ash_grant_owner_field(dsl_or_extended :: module() | map()) ::
  {:ok, atom()} | :error
```

DEPRECATED: Use explicit `scope :own, expr(field == ^actor(:id))` instead.

The field that identifies the owner of a record. This option is
deprecated and will be removed in v1.0.0.

# `ash_grant_owner_field!`

```elixir
@spec ash_grant_owner_field!(dsl_or_extended :: module() | map()) ::
  atom() | no_return()
```

DEPRECATED: Use explicit `scope :own, expr(field == ^actor(:id))` instead.

The field that identifies the owner of a record. This option is
deprecated and will be removed in v1.0.0.

# `ash_grant_resolver`

```elixir
@spec ash_grant_resolver(dsl_or_extended :: module() | map()) ::
  {:ok, module() | (any(), any() -&gt; any())} | :error
```

Module implementing `AshGrant.PermissionResolver` behaviour,
or a 2-arity function `(actor, context) -> permissions`.

This resolves permissions for the current actor.
Can be inherited from the domain if the domain uses `AshGrant.Domain`.

# `ash_grant_resolver!`

```elixir
@spec ash_grant_resolver!(dsl_or_extended :: module() | map()) ::
  (module() | (any(), any() -&gt; any())) | no_return()
```

Module implementing `AshGrant.PermissionResolver` behaviour,
or a 2-arity function `(actor, context) -> permissions`.

This resolves permissions for the current actor.
Can be inherited from the domain if the domain uses `AshGrant.Domain`.

# `ash_grant_resource_name`

```elixir
@spec ash_grant_resource_name(dsl_or_extended :: module() | map()) ::
  {:ok, String.t()} | :error
```

The resource name used in permission matching.

Defaults to the last part of the module name, lowercased.
For example, `MyApp.Blog.Post` becomes `"post"`.

# `ash_grant_resource_name!`

```elixir
@spec ash_grant_resource_name!(dsl_or_extended :: module() | map()) ::
  String.t() | no_return()
```

The resource name used in permission matching.

Defaults to the last part of the module name, lowercased.
For example, `MyApp.Blog.Post` becomes `"post"`.

# `ash_grant_scope_resolver`

```elixir
@spec ash_grant_scope_resolver(dsl_or_extended :: module() | map()) ::
  {:ok, module() | (any(), any() -&gt; any())} | :error
```

DEPRECATED: Use inline `scope` entities instead.

Module implementing `AshGrant.ScopeResolver` behaviour,
or a 2-arity function `(scope, context) -> filter`.

This resolves scope strings to Ash filter expressions.
If not provided, scopes are resolved from inline `scope` entities.

# `ash_grant_scope_resolver!`

```elixir
@spec ash_grant_scope_resolver!(dsl_or_extended :: module() | map()) ::
  (module() | (any(), any() -&gt; any())) | no_return()
```

DEPRECATED: Use inline `scope` entities instead.

Module implementing `AshGrant.ScopeResolver` behaviour,
or a 2-arity function `(scope, context) -> filter`.

This resolves scope strings to Ash filter expressions.
If not provided, scopes are resolved from inline `scope` entities.

# `can_perform_actions`

```elixir
@spec can_perform_actions(Ash.Resource.t()) :: [atom()]
```

Gets the list of action names configured via `can_perform_actions`.

Returns an empty list if not configured.

# `configured?`

```elixir
@spec configured?(Ash.Resource.t()) :: boolean()
```

Checks if AshGrant is configured for a resource.

# `default_field_policies`

```elixir
@spec default_field_policies(Ash.Resource.t()) :: boolean()
```

Gets the default_field_policies setting.

Returns `true` if field policies should be auto-generated from field_group definitions,
or `false` (default) if field policies should be manually defined.

# `default_policies`

```elixir
@spec default_policies(Ash.Resource.t()) :: boolean() | :read | :write | :all
```

Gets the default_policies setting.

Returns `false` if not configured, or one of:
- `true` or `:all` - Generate policies for both read and write
- `:read` - Only generate read policy
- `:write` - Only generate write policy

# `field_groups`

```elixir
@spec field_groups(Ash.Resource.t()) :: [AshGrant.Dsl.FieldGroup.t()]
```

Gets all field group definitions for a resource.

# `get_field_group`

```elixir
@spec get_field_group(Ash.Resource.t(), atom()) :: AshGrant.Dsl.FieldGroup.t() | nil
```

Gets a specific field group by name.

# `get_scope`

```elixir
@spec get_scope(Ash.Resource.t(), atom()) :: AshGrant.Dsl.Scope.t() | nil
```

Gets a specific scope by name.

# `instance_key`

```elixir
@spec instance_key(Ash.Resource.t()) :: atom()
```

Gets the field to match instance permission IDs against.

Defaults to `:id` (primary key) when not configured.

# `owner_field`

> This function is deprecated. Use explicit scope expressions instead of owner_field.

```elixir
@spec owner_field(Ash.Resource.t()) :: atom() | nil
```

Gets the owner field for "own" scope resolution.

DEPRECATED: Use explicit `scope :own, expr(field == ^actor(:id))` instead.
This option will be removed in v1.0.0.

# `resolve_arguments`

```elixir
@spec resolve_arguments(Ash.Resource.t()) :: [AshGrant.Dsl.ResolveArgument.t()]
```

Gets all `resolve_argument` declarations for a resource.

# `resolve_field_group`

```elixir
@spec resolve_field_group(Ash.Resource.t(), atom()) ::
  %{fields: [atom()], masked_fields: map()} | nil
```

Resolves a field group to its complete field set including inherited fields.

Returns a map with:
- `:fields` - Complete list of accessible field atoms (union of own + inherited)
- `:masked_fields` - Map of field_name => mask_function for fields masked at THIS level only

Masking is NOT inherited. A higher-level field group sees original values
unless it explicitly declares its own masking.

Returns nil if the field group does not exist.

# `resolve_scope_filter`

```elixir
@spec resolve_scope_filter(Ash.Resource.t(), atom(), map()) ::
  boolean() | Ash.Expr.t()
```

Resolves a scope to its read filter expression.

Uses the scope's `filter` field (ignoring any `write:` option).
If the scope has inheritance, the parent scopes are combined with AND.
Returns `false` for unknown scopes.

For write action scope resolution, use `resolve_write_scope_filter/3` instead.

# `resolve_write_scope_filter`

```elixir
@spec resolve_write_scope_filter(Ash.Resource.t(), atom(), map()) ::
  boolean() | Ash.Expr.t()
```

Resolves a scope's write filter expression.

If the scope has a `write` field set, uses that value. Otherwise falls back
to the regular `filter`. This ensures write actions use a direct-field expression
when relationship traversal (exists/dot-paths) cannot be evaluated in-memory.

Returns `false` for unknown scopes, or when `write: false` is explicitly set.

## Resolution Order

1. If scope has `write:` set → use `write` value (`false`, `true`, or expression)
2. If scope has no `write:` → fall back to `scope.filter` (backward compatible)
3. Inheritance: parent `write:` values are resolved recursively and combined with AND
4. If any parent returns `false` → short-circuit to `false` (deny propagation)

## Examples

    # Scope with write: expr(...) → returns the write expression
    resolve_write_scope_filter(Resource, :same_org, context)
    # => expr(org_id == ^actor(:org_id))

    # Scope with write: false → returns false
    resolve_write_scope_filter(Resource, :readonly, context)
    # => false

    # Scope without write: → falls back to filter
    resolve_write_scope_filter(Resource, :own, context)
    # => expr(author_id == ^actor(:id))

# `resolver`

```elixir
@spec resolver(Ash.Resource.t()) :: module() | function() | nil
```

Gets the permission resolver for a resource.

# `resource_name`

```elixir
@spec resource_name(Ash.Resource.t()) :: String.t()
```

Gets the resource name for permission matching.

Falls back to deriving from the module name if not configured.

# `scope_description`

```elixir
@spec scope_description(Ash.Resource.t(), atom()) :: String.t() | nil
```

Gets the description for a specific scope.

Returns `nil` if the scope doesn't exist or has no description.

## Examples

    iex> AshGrant.Info.scope_description(MyApp.Blog.Post, :own)
    "Records owned by the current user"

    iex> AshGrant.Info.scope_description(MyApp.Blog.Post, :all)
    nil

# `scope_resolver`

```elixir
@spec scope_resolver(Ash.Resource.t()) :: module() | function() | nil
```

Gets the scope resolver for a resource.

DEPRECATED: Use inline `scope` entities instead.

# `scope_throughs`

```elixir
@spec scope_throughs(Ash.Resource.t()) :: [AshGrant.Dsl.ScopeThrough.t()]
```

Gets all scope_through entities for a resource.

Returns an empty list if none are configured.

# `scopes`

```elixir
@spec scopes(Ash.Resource.t()) :: [AshGrant.Dsl.Scope.t()]
```

Gets all scope definitions for a resource.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
