Current section

Files

Jump to
permit lib permit permissions parsed_condition.ex
Raw

lib/permit/permissions/parsed_condition.ex

defmodule Permit.Permissions.ParsedCondition do
@moduledoc """
Represents the product of parsing a condition by a function implementing
the `c:Permit.Permissions.can/1` callback.
A condition parsed by Permit's rule syntax parser contains:
* condition semantics, that is: a function that allows for checking
whether the condition is satisfied
* an indication of whether it is negated (i.e. a condition defined as
`{:not, ...}`)
* metadata (`:private`), which can be used by alternative parsers (e.g.
`Permit.Ecto.Permissions` puts dynamic query constructors there)
Part of the private API, subject to changes and not to be used on the
application level.
"""
@enforce_keys [:condition, :condition_type]
defstruct [:condition, :condition_type, :semantics, not: false, private: %{}]
alias __MODULE__
alias Permit.Types
alias Permit.Types.ConditionTypes
require Permit.Operators
@type condition_type :: :const | :function_1 | :function_2 | {:operator, module()}
@type t :: %ParsedCondition{
condition:
ConditionTypes.boolean_condition()
| {Types.struct_field(), (Types.subject(), Types.object() -> any())}
| ConditionTypes.fn1_condition()
| ConditionTypes.fn2_condition(),
condition_type: condition_type(),
semantics: (Types.struct_field(), Types.subject(), Types.object() -> boolean()),
private: map(),
not: boolean()
}
@doc false
def satisfied?(
%ParsedCondition{condition: condition, condition_type: :const},
_record,
_subject
),
do: condition
def satisfied?(
%ParsedCondition{
condition: {key, _},
condition_type: {:operator, _},
semantics: semantics
},
record,
subject
)
when is_struct(record) do
record
|> Map.get(key)
|> semantics.(subject, record)
end
def satisfied?(
%ParsedCondition{
condition: {key, val_fn},
condition_type: {:association, _},
semantics: _semantics
},
record,
subject
)
when is_struct(record) do
conditions = val_fn.(subject, record)
check_conditions(record, [{key, conditions}])
end
def satisfied?(%ParsedCondition{condition: condition}, module, _subject)
when is_atom(module),
do: !!condition
def satisfied?(
%ParsedCondition{condition: function, condition_type: :function_1},
record,
_subject
),
do: !!function.(record)
def satisfied?(
%ParsedCondition{condition: _fun, condition_type: :function_2},
_record,
subject
)
when is_nil(subject),
do:
raise(
"Unable to use function/2 condition feature without subject. First argument of this function is nonexisting subject"
)
def satisfied?(
%ParsedCondition{condition: function, condition_type: :function_2},
record,
subject
),
do: !!function.(subject, record)
defp check_conditions(record, conditions) when is_map(record) and is_list(conditions) do
Enum.all?(conditions, fn {assoc, assoc_conditions} ->
check_association(record, assoc, assoc_conditions)
end)
end
defp check_association(record, assoc, assoc_conditions) do
case Map.get(record, assoc) do
sub_assoc when is_map(sub_assoc) ->
check_conditions(sub_assoc, assoc_conditions)
sub_assoc when is_list(sub_assoc) ->
Enum.all?(sub_assoc, &check_conditions(&1, assoc_conditions))
sub_assoc ->
sub_assoc == assoc_conditions
end
end
end