AbsinthePermission.Condition (AbsinthePermission v1.0.0)

Copy Markdown View Source

Compiled condition AST and helpers.

A condition is a small data tree, not a closure. This makes rules introspectable, testable, and serialisable.

All variants use only 2-tuples (and lists) so they survive Absinthe's schema metadata pipeline intact.

Grammar

condition ::=
  | :always                              # vacuously true
  | {:literal, term()}
  | {:arg, atom()}                       # GraphQL argument
  | {:loaded, [atom()]}                  # [load_name, field, ...]
  | {:current_user, [atom()]}            # path under context.current_user
  | {:context, [atom()]}                 # arbitrary context path
  | {:cmp, [op() | expr()]}              # [op, lhs, rhs]
  | {:and, [condition()]}
  | {:or, [condition()]}
  | {:not, condition()}
  | {:fun, mfa_or_capture()}             # escape hatch

Comparison operators: :eq, :neq, :gt, :gte, :lt, :lte, :in, :not_in.

Why 2-tuples only

Absinthe stores schema metadata through an AST-aware pipeline that treats any 3-element tuple as a macro call. Sticking to 2-tuples means rules survive the round-trip and remain plain data — readable for tests, logs, the mix absinthe_permission.audit task, and AI agents inspecting the schema.

Summary

Functions

Pretty-print a condition as a one-line string. Used by audit output and error messages.

Returns true if op is a supported comparison operator.

Returns the list of supported comparison operators.

Types

expr()

@type expr() ::
  {:literal, term()}
  | {:arg, atom()}
  | {:loaded, [atom(), ...]}
  | {:current_user, [atom()]}
  | {:context, [atom()]}

op()

@type op() :: :eq | :neq | :gt | :gte | :lt | :lte | :in | :not_in

t()

@type t() ::
  :always
  | expr()
  | {:cmp, [op() | expr()]}
  | {:and, [t()]}
  | {:or, [t()]}
  | {:not, t()}
  | {:fun, {module(), atom()} | (map() -> boolean())}

Functions

format(arg1)

@spec format(t()) :: String.t()

Pretty-print a condition as a one-line string. Used by audit output and error messages.

iex> AbsinthePermission.Condition.format(:always)
"always"

iex> AbsinthePermission.Condition.format({:cmp, [:eq, {:arg, :state}, {:literal, "CLOSED"}]})
"arg(:state) == \"CLOSED\""

valid_op?(op)

@spec valid_op?(atom()) :: boolean()

Returns true if op is a supported comparison operator.

valid_ops()

@spec valid_ops() :: [op()]

Returns the list of supported comparison operators.