# `ExScim.Lifecycle.Adapter`
[🔗](https://github.com/ExScim/ex_scim/blob/main/lib/ex_scim/lifecycle/adapter.ex#L1)

Behaviour for lifecycle hooks around SCIM operations.

Implement this behaviour to run custom logic before or after SCIM operations.
All callbacks are optional. Unimplemented callbacks are no-ops.

## Configuration

    config :ex_scim, lifecycle_adapter: MyApp.ScimLifecycle

## Before hooks

Fire after validation/mapping/metadata, immediately before storage.
Can modify domain data or reject the operation by returning `{:error, term()}`.

## After hooks

Fire after the write is committed and mapped to a SCIM response. Observe-only
(return `:ok`).

## Before-commit hook

`before_commit/4` runs inside the storage transaction, right after the write
for create, replace, patch, and delete. It receives the record returned by
storage, or the ID for delete. Returning `{:error, reason}` or raising rolls
back the write, skips the after hook, and fires `on_error`. Use it for work
that must be committed with the write. Without
`c:ExScim.Storage.Adapter.transaction/1`, the write is not rolled back.

## Error hook

Fires when an operation fails. Observe-only.

## Example

    defmodule MyApp.ScimLifecycle do
      use ExScim.Lifecycle.Adapter

      @impl true
      def before_create(:user, data, _caller) do
        if some_business_rule_violated?(data) do
          {:error, {:forbidden, "Business rule violated"}}
        else
          {:ok, data}
        end
      end

      @impl true
      def after_create(:user, scim_response, _caller) do
        MyApp.Notifications.send_welcome(scim_response["id"])
        :ok
      end
    end

# `caller`

```elixir
@type caller() :: ExScim.Scope.t()
```

The authenticated caller scope for the current request.

# `operation`

```elixir
@type operation() :: :create | :replace | :patch | :delete | :get
```

The SCIM operation type that triggered the hook.

# `resource_data`

```elixir
@type resource_data() :: term()
```

The domain-level resource data (struct or map) being processed.

# `resource_id`

```elixir
@type resource_id() :: binary()
```

The unique identifier of the resource.

# `resource_type`

```elixir
@type resource_type() :: :user | :group
```

The type of resource being operated on: `:user` or `:group`.

# `scim_response`

```elixir
@type scim_response() :: map()
```

The SCIM JSON response map after mapping from the domain struct.

# `write_operation`

```elixir
@type write_operation() :: :create | :replace | :patch | :delete
```

The SCIM operations that write to storage.

# `after_create`
*optional* 

```elixir
@callback after_create(resource_type(), scim_response(), caller()) :: :ok
```

Fires after a resource is successfully created. Observe-only.

# `after_delete`
*optional* 

```elixir
@callback after_delete(resource_type(), resource_id(), caller()) :: :ok
```

Fires after a resource is successfully deleted. Observe-only.

# `after_get`
*optional* 

```elixir
@callback after_get(resource_type(), scim_response(), caller()) :: :ok
```

Fires after a resource is successfully retrieved. Observe-only.

# `after_patch`
*optional* 

```elixir
@callback after_patch(resource_type(), scim_response(), caller()) :: :ok
```

Fires after a resource is successfully patched. Observe-only.

# `after_replace`
*optional* 

```elixir
@callback after_replace(resource_type(), scim_response(), caller()) :: :ok
```

Fires after a resource is successfully replaced. Observe-only.

# `before_commit`
*optional* 

```elixir
@callback before_commit(
  write_operation(),
  resource_type(),
  resource_data() | resource_id(),
  caller()
) ::
  :ok | {:error, term()}
```

Fires inside the storage transaction after a write. Receives the record
returned by storage, or the ID for delete. Return `:ok` to commit or
`{:error, reason}` to roll back.

# `before_create`
*optional* 

```elixir
@callback before_create(resource_type(), resource_data(), caller()) ::
  {:ok, resource_data()} | {:error, term()}
```

Fires before a resource is created. Return `{:ok, data}` to proceed or `{:error, reason}` to reject.

# `before_delete`
*optional* 

```elixir
@callback before_delete(resource_type(), resource_id(), caller()) ::
  :ok | {:error, term()}
```

Fires before a resource is deleted. Return `:ok` to proceed or `{:error, reason}` to reject.

# `before_get`
*optional* 

```elixir
@callback before_get(resource_type(), resource_id(), caller()) :: :ok | {:error, term()}
```

Fires before a resource is retrieved. Return `:ok` to proceed or `{:error, reason}` to reject.

# `before_patch`
*optional* 

```elixir
@callback before_patch(resource_type(), resource_id(), resource_data(), caller()) ::
  {:ok, resource_data()} | {:error, term()}
```

Fires before a resource is patched. Return `{:ok, data}` to proceed or `{:error, reason}` to reject.

# `before_replace`
*optional* 

```elixir
@callback before_replace(resource_type(), resource_id(), resource_data(), caller()) ::
  {:ok, resource_data()} | {:error, term()}
```

Fires before a resource is replaced (PUT). Return `{:ok, data}` to proceed or `{:error, reason}` to reject.

# `on_error`
*optional* 

```elixir
@callback on_error(operation(), resource_type(), term(), caller()) :: :ok
```

Fires when an operation fails. Observe-only; intended for logging or metrics.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
