# `GuardedStruct.Derive.Extension`
[🔗](https://github.com/mishka-group/guarded_struct/blob/v0.1.4/lib/guarded_struct/derive/extension.ex#L1)

Define custom derive validators / sanitizers via a Spark DSL.

## Usage

    defmodule MyApp.Derives do
      use GuardedStruct.Derive.Extension

      derives do
        validator :slug, fn input ->
          is_binary(input) and Regex.match?(~r/^[a-z0-9-]+$/, input)
        end

        sanitizer :slugify, fn input when is_binary(input) ->
          input
          |> String.downcase()
          |> String.replace(~r/[^a-z0-9-]+/u, "-")
        end
      end
    end

Register globally in `config/config.exs`:

    config :guarded_struct, derive_extensions: [MyApp.Derives]

Then any GuardedStruct module can use the new ops:

    defmodule Post do
      use GuardedStruct

      guardedstruct do
        field :slug, String.t(), derives: "sanitize(slugify) validate(slug)"
      end
    end

## Validator return shape

Validator functions return:

  * `true` — input passes
  * `false` — input fails (default error message generated)
  * `{:error, field, action, message}` — explicit error tuple
  * any other value — used as the validated value (for coercing validators)

## Introspection

The `derives do ... end` block is introspectable via
`Spark.Dsl.Extension.get_entities/2`. Verifiers and transformers can
plug in at the standard Spark extension points.

### Options

* `:extensions` (list of module that adopts `Spark.Dsl.Extension`) - A list of DSL extensions to add to the `Spark.Dsl`

* `:otp_app` (`t:atom/0`) - The otp_app to use for any application configurable options

* `:fragments` (list of `t:module/0`) - Fragments to include in the `Spark.Dsl`. See the fragments guide for more.

# `all_extension_sanitizers`

All sanitizer op atoms known across every extension visible from `module`.

# `all_extension_validators`

All validator op atoms known across every extension visible from the
given module.

# `clear_cache`

Clear the cached extension list. Call from test setup if you mutate
`:guarded_struct, :derive_extensions` and need the change visible
before the next `registered_extensions/0` call.

# `current_module`

The user module currently being built. Set by `Runtime.with_telemetry/2`
around every top-level `builder/1` call; nested sub_field builds inherit
via the process dictionary.

# `dispatch_sanitize`

Try the current module's extensions for a sanitizer op.

# `dispatch_sanitize`

Try a specific list of extensions for a sanitizer op.

# `dispatch_validate`

Try the current module's extensions for a validator op.

# `dispatch_validate`

Try a specific list of extensions for a validator op.

# `extensions_for`

```elixir
@spec extensions_for(module() | nil) :: [module()]
```

Resolve the effective extension list for a specific user module.

# `registered_extensions`

Resolved list of registered extension modules from `:guarded_struct,
:derive_extensions` application config.

Cached in `:persistent_term` and invalidated automatically when the
underlying config changes (e.g. between test cases that call
`Application.put_env/3`).

# `resolve_opt`

```elixir
@spec resolve_opt(list() | nil) :: [module()]
```

Resolve a per-module `derive_extensions:` opt — the raw list user wrote
in `use GuardedStruct, derive_extensions: [...]` — into a flat list of
extension modules with `:config` expanded to the current global config
at the position it appears.

## Resolution rules

  * `nil` → fall back to the global Application config (no per-module opt set)
  * `[]` → no extensions at all (intentional opt-out, ignores global)
  * `[A, B]` (no `:config`) → these only; global is ignored
  * `[:config, A]` → global ++ [A] (global wins on op-name collisions)
  * `[A, :config]` → [A] ++ global (A wins on op-name collisions)
  * `[A, :config, B]` → [A] ++ global ++ [B]

# `validate_opt!`

Validate a `derive_extensions:` opt list at compile time. Raises
`ArgumentError` if entries are not modules / `:config`, or if `:config`
appears more than once.

---

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