Behaviour defining the contract for all storage backends.
Implementations must handle CRUD operations for both Users and Groups.
Each callback receives an optional scope for multi-tenant isolation.
See ExScim.Storage.EtsStorage for a reference implementation, or
ExScimEcto.StorageAdapter for a production-ready Ecto-based backend.
Configuration
config :ex_scim, storage_strategy: MyApp.Storage
Summary
Types
A domain-level group struct or map as stored by the backend.
A domain-level user struct or map as stored by the backend.
Parsed SCIM filter AST, or nil for no filter.
Unique identifier for a group resource.
Pagination options, e.g. [start_index: 1, count: 20].
Caller scope for multi-tenant isolation, or nil.
Sort options, e.g. [sort_by: {"userName", :asc}].
Unique identifier for a user resource.
Callbacks
Persists a new group.
Persists a new user.
Deletes a group by ID.
Deletes a user by ID.
Retrieves a single group by ID.
Retrieves a single user by ID.
Returns true if a group with the given ID exists.
Lists groups matching a filter with sorting and pagination. Returns {:ok, groups, total_count}.
Lists users matching a filter with sorting and pagination. Returns {:ok, users, total_count}.
Fully replaces an existing group (PUT).
Fully replaces an existing user (PUT).
Partially updates an existing group (PATCH).
Partially updates an existing user (PATCH).
Returns true if a user with the given ID exists.
Types
@type domain_group() :: struct()
A domain-level group struct or map as stored by the backend.
@type domain_user() :: struct()
A domain-level user struct or map as stored by the backend.
@type filter_ast() :: term() | nil
Parsed SCIM filter AST, or nil for no filter.
The AST is a nested tuple structure, e.g. {:eq, "userName", "alice"}
or {:and, {:eq, "active", "true"}, {:co, "userName", "john"}}.
@type group_id() :: binary()
Unique identifier for a group resource.
@type pagination_opts() :: keyword()
Pagination options, e.g. [start_index: 1, count: 20].
@type scope() :: ExScim.Scope.t() | nil
Caller scope for multi-tenant isolation, or nil.
@type sort_opts() :: keyword()
Sort options, e.g. [sort_by: {"userName", :asc}].
@type user_id() :: binary()
Unique identifier for a user resource.
Callbacks
@callback create_group(domain_group(), scope()) :: {:ok, domain_group()} | {:error, term()}
Persists a new group.
@callback create_user(domain_user(), scope()) :: {:ok, domain_user()} | {:error, term()}
Persists a new user.
Deletes a group by ID.
Deletes a user by ID.
@callback get_group(group_id(), scope()) :: {:ok, domain_group()} | {:error, :not_found}
Retrieves a single group by ID.
@callback get_user(user_id(), scope()) :: {:ok, domain_user()} | {:error, :not_found}
Retrieves a single user by ID.
Returns true if a group with the given ID exists.
@callback list_groups(filter_ast(), sort_opts(), pagination_opts(), scope()) :: {:ok, [domain_group()], non_neg_integer()}
Lists groups matching a filter with sorting and pagination. Returns {:ok, groups, total_count}.
@callback list_users(filter_ast(), sort_opts(), pagination_opts(), scope()) :: {:ok, [domain_user()], non_neg_integer()}
Lists users matching a filter with sorting and pagination. Returns {:ok, users, total_count}.
@callback replace_group(group_id(), domain_group(), scope()) :: {:ok, domain_group()} | {:error, term()}
Fully replaces an existing group (PUT).
@callback replace_user(user_id(), domain_user(), scope()) :: {:ok, domain_user()} | {:error, term()}
Fully replaces an existing user (PUT).
@callback update_group(group_id(), domain_group(), scope()) :: {:ok, domain_group()} | {:error, term()}
Partially updates an existing group (PATCH).
@callback update_user(user_id(), domain_user(), scope()) :: {:ok, domain_user()} | {:error, term()}
Partially updates an existing user (PATCH).
Returns true if a user with the given ID exists.