Macros that compose the AbsinthePermission DSL.
These are imported automatically by use AbsinthePermission.
Don't import this module directly.
Available macros
authorize/1,authorize/2— the primary rule-attaching macroauthorize_owner/2— sugar for the "owner-vs-others" patternload/1,load/2— declare data to fetch before rules evaluateloaders/1— block delimiter for loader registrationloader/2— register a single loader
Condition helpers (used inside when: / unless:)
These are recognised by AbsinthePermission.Compiler at macro time
and never need to be imported at runtime:
arg(:name)— a GraphQL argumentloaded(:name).field.path— a field on a loaded recordcurrent_user.field.pathorcurrent_user(:field)— context'scurrent_usercontext.field— arbitrary context path
Summary
Functions
Attach an authorization rule to the enclosing field.
Sugar for the "owner-or-admin" pattern
Declare a piece of data to fetch before rules on this field are
evaluated. Subsequent rules can reference it as loaded(:name).
Register a single loader function. Must be called inside a loaders
block (or directly at module top level).
Block delimiter for loader registrations. Use at the top level of your schema module
Functions
Attach an authorization rule to the enclosing field.
When the rule fires (its when: condition holds, or always if none),
the caller must hold permission or the operation is denied.
Permission shapes
authorize "edit_todos" # single permission
authorize ["admin", "support"] # any-of
authorize all: ["admin", "verified_2fa"] # all-ofConditional firing
authorize "close_tickets", when: arg(:state) == "CLOSED"
authorize "edit_others", unless: loaded(:todo).owner_id == current_user.id
authorize "high_priority", when: arg(:priority) > 5Behaviour on deny
authorize "view_emails", on_deny: :null # redact the field, return null
authorize "view_emails", on_deny: :error # default — return GraphQL errorCustom error message
authorize "edit_todos", error_message: "Only admins may edit todos."
See authorize/1.
Sugar for the "owner-or-admin" pattern:
authorize_owner :todo, by: arg(:id),
owner_field: :owner_id,
if_owner: "edit_own_todo",
if_other: "edit_others_todo"Equivalent to:
load :todo, by: arg(:id)
authorize "edit_own_todo",
when: loaded(:todo).owner_id == current_user.id
authorize "edit_others_todo",
when: loaded(:todo).owner_id != current_user.idOptions
:by— what to look up (default:arg(:id)):owner_field— record field to compare againstcurrent_user.id(default::owner_id):if_owner— permission required when caller IS the owner (required):if_other— permission required when caller is NOT the owner (required):user_field—current_userfield to compare (default::id)
Declare a piece of data to fetch before rules on this field are
evaluated. Subsequent rules can reference it as loaded(:name).
load :todo, by: arg(:id)
load :user, by: arg(:user_id), using: :user_loader
load :todo # short for `by: arg(:id), using: :todo`The loader function is registered via loader/2 inside a loaders
block and called as loader.(key, ctx), returning either the record
or nil.
Register a single loader function. Must be called inside a loaders
block (or directly at module top level).
The function receives (key, context) and should return either the
loaded record (any term) or nil if not found.
loader :todo, fn id, _ctx -> MyApp.Todos.get(id) end
loader :user, &MyApp.Users.fetch/2
Block delimiter for loader registrations. Use at the top level of your schema module:
loaders do
loader :todo, fn id, _ctx -> MyApp.Todos.get(id) end
loader :user, &MyApp.Users.fetch/2
endThe block is otherwise opaque — loader/2 does the real work.