Current section
Files
Jump to
Current section
Files
lib/regolix.ex
defmodule Regolix do
@moduledoc """
Elixir wrapper for the Regorus Rego policy engine.
"""
alias Regolix.{Error, Native}
@type engine :: reference()
@type json_encodable :: map() | list() | String.t() | number() | boolean() | nil
@type eval_result :: json_encodable() | :undefined
@doc """
Creates a new Rego policy engine.
## Examples
{:ok, engine} = Regolix.new()
"""
@spec new() :: {:ok, engine()}
def new do
{:ok, Native.native_new()}
end
@doc """
Creates a new Rego policy engine. Raises on error.
"""
@spec new!() :: engine()
def new! do
Native.native_new()
end
@doc """
Adds a Rego policy to the engine.
## Examples
{:ok, engine} = Regolix.add_policy(engine, "authz.rego", "package authz")
"""
@spec add_policy(engine(), String.t(), String.t()) :: {:ok, engine()} | {:error, Error.t()}
def add_policy(engine, name, source) do
case Native.native_add_policy(engine, name, source) do
{:ok, {}} -> {:ok, engine}
{:error, {type, message}} -> {:error, %Error{type: type, message: message}}
end
end
@doc """
Adds a Rego policy to the engine. Raises on error.
"""
@spec add_policy!(engine(), String.t(), String.t()) :: engine()
def add_policy!(engine, name, source) do
case add_policy(engine, name, source) do
{:ok, engine} -> engine
{:error, error} -> raise error
end
end
@doc """
Returns the list of package names loaded in the engine.
## Examples
packages = Regolix.get_packages(engine)
# => ["data.authz", "data.rbac"]
"""
@spec get_packages(engine()) :: [String.t()]
def get_packages(engine) do
case Native.native_get_packages(engine) do
{:ok, packages} -> packages
packages when is_list(packages) -> packages
end
end
@doc """
Sets the input document for policy evaluation.
Accepts Elixir terms (maps, lists, etc.) which are automatically JSON-encoded.
## Examples
{:ok, engine} = Regolix.set_input(engine, %{"user" => "alice"})
"""
@spec set_input(engine(), json_encodable()) :: {:ok, engine()} | {:error, Error.t()}
def set_input(engine, input) do
with {:ok, json} <- encode_json(input),
{:ok, {}} <- Native.native_set_input(engine, json) do
{:ok, engine}
else
{:error, {type, message}} ->
{:error, %Error{type: type, message: message}}
{:error, %Jason.EncodeError{} = e} ->
{:error, %Error{type: :json_error, message: Exception.message(e)}}
{:error, %Protocol.UndefinedError{} = e} ->
{:error, %Error{type: :json_error, message: Exception.message(e)}}
end
end
@doc """
Sets the input document. Raises on error.
"""
@spec set_input!(engine(), json_encodable()) :: engine()
def set_input!(engine, input) do
case set_input(engine, input) do
{:ok, engine} -> engine
{:error, error} -> raise error
end
end
@doc """
Adds data to the engine's data document.
Can be called multiple times to merge data.
## Examples
{:ok, engine} = Regolix.add_data(engine, %{"users" => %{"alice" => %{"role" => "admin"}}})
"""
@spec add_data(engine(), json_encodable()) :: {:ok, engine()} | {:error, Error.t()}
def add_data(engine, data) do
with {:ok, json} <- encode_json(data),
{:ok, {}} <- Native.native_add_data(engine, json) do
{:ok, engine}
else
{:error, {type, message}} ->
{:error, %Error{type: type, message: message}}
{:error, %Jason.EncodeError{} = e} ->
{:error, %Error{type: :json_error, message: Exception.message(e)}}
{:error, %Protocol.UndefinedError{} = e} ->
{:error, %Error{type: :json_error, message: Exception.message(e)}}
end
end
@doc """
Adds data to the engine. Raises on error.
"""
@spec add_data!(engine(), json_encodable()) :: engine()
def add_data!(engine, data) do
case add_data(engine, data) do
{:ok, engine} -> engine
{:error, error} -> raise error
end
end
@doc """
Clears all data from the engine, keeping policies intact.
## Examples
{:ok, engine} = Regolix.clear_data(engine)
"""
@spec clear_data(engine()) :: {:ok, engine()} | {:error, Error.t()}
def clear_data(engine) do
case Native.native_clear_data(engine) do
{:ok, {}} -> {:ok, engine}
{:error, {type, message}} -> {:error, %Error{type: type, message: message}}
end
end
@doc """
Clears all data from the engine. Raises on error.
"""
@spec clear_data!(engine()) :: engine()
def clear_data!(engine) do
case clear_data(engine) do
{:ok, engine} -> engine
{:error, error} -> raise error
end
end
@doc """
Evaluates a Rego query against the engine.
Returns the result as Elixir terms, or `:undefined` if the query has no result.
## Examples
{:ok, true} = Regolix.eval_query(engine, "data.authz.allow")
{:ok, :undefined} = Regolix.eval_query(engine, "data.authz.nonexistent")
"""
@spec eval_query(engine(), String.t()) :: {:ok, eval_result()} | {:error, Error.t()}
def eval_query(engine, query) do
case Native.native_eval_query(engine, query) do
{:ok, result} -> {:ok, result}
{:error, {type, message}} -> {:error, %Error{type: type, message: message}}
end
end
@doc """
Evaluates a Rego query. Raises on error.
"""
@spec eval_query!(engine(), String.t()) :: eval_result()
def eval_query!(engine, query) do
case eval_query(engine, query) do
{:ok, result} -> result
{:error, error} -> raise error
end
end
defp encode_json(term) do
Jason.encode(term)
end
end