# `Relyra.Provider`
[🔗](https://github.com/szTheory/relyra/blob/v1.1.0/lib/relyra/provider.ex#L1)

Provider preset registry for known SAML IdPs.

Presets carry safe defaults, label translations from our internal field
names to the IdP's admin UI vocabulary, and known-footgun checks. They
exist so that:

  * adopters get fail-closed defaults that match each IdP's quirks
    (e.g. Microsoft Entra's NameID `:persistent` requirement);
  * admin-channel error hints can speak the IdP's language
    (e.g. "Audience URI" for Okta, "Identifier (Entity ID)" for Entra);
  * footgun checks surface known cross-IdP gotchas before they become
    production incidents.

## Shape

Each preset is a module implementing `Relyra.Provider` callbacks. They
are pure data — no GenServers, no compile-time magic. The module-per-
provider shape (Assent's `default_config/1` idiom) plays naturally with
runtime config:

    config :my_app, :saml_connection,
      provider_preset: :okta,
      sp_entity_id: "https://my.app/sso/metadata",
      ...

## Public API

    Relyra.Provider.apply_defaults(:okta, user_keyword)
    Relyra.Provider.translate_label(:okta, :sp_entity_id)
    Relyra.Provider.check_footguns(:okta, %Relyra.Connection{...})
    Relyra.Provider.guide_url(:okta)

## Adding a preset

Create a module that `@behaviour Relyra.Provider` and register its id
in `@presets` below. Keep `default_config/0` strictly safer-than-spec —
presets exist to *narrow* the trust surface, never to widen it.

# `footgun`

```elixir
@type footgun() :: %{
  id: atom(),
  severity: :error | :warning,
  message: String.t(),
  check: (Relyra.Connection.t() -&gt; :ok | {:warn, String.t()})
}
```

Footgun definition. `check.(conn)` returns `:ok` or `{:warn, reason}`.
Severity guides whether the warning is logged or raised in
strict-config validation.

# `label_entry`

```elixir
@type label_entry() :: %{
  :idp_label =&gt; String.t(),
  optional(:idp_section) =&gt; String.t() | nil,
  optional(:hint) =&gt; String.t() | nil
}
```

Per-field translation entry.

# `label_key`

```elixir
@type label_key() ::
  :sp_entity_id
  | :acs_url
  | :idp_entity_id
  | :idp_sso_url
  | :idp_certificate
  | :name_id_format
  | :signing_algorithm
  | :digest_algorithm
```

Internal field name we expose to adopters.

# `label_map`

```elixir
@type label_map() :: %{required(label_key()) =&gt; label_entry()}
```

# `default_config`

```elixir
@callback default_config() :: keyword()
```

# `display_name`

```elixir
@callback display_name() :: String.t()
```

# `footguns`

```elixir
@callback footguns() :: [footgun()]
```

# `guide_url`

```elixir
@callback guide_url() :: String.t()
```

# `id`

```elixir
@callback id() :: atom()
```

# `labels`

```elixir
@callback labels() :: label_map()
```

# `apply_defaults`

```elixir
@spec apply_defaults(
  atom(),
  keyword()
) :: keyword()
```

Merge preset defaults under a user-supplied keyword list. User-supplied
keys win — presets only fill in what the adopter omitted.

Returns a keyword list suitable for building a `Relyra.Connection.t()`.

# `check_footguns`

```elixir
@spec check_footguns(atom(), Relyra.Connection.t()) :: [
  :ok | {:warn, atom(), String.t()} | {:check_failed, atom(), term()}
]
```

Run every footgun check defined by the preset against a resolved
connection. Returns a list of results in declaration order; the caller
decides whether to log warnings, raise errors, or aggregate.

Footguns whose `check` raises are caught and reported as
`{:check_failed, id, reason}` so a buggy preset never breaks the
consume pipeline.

# `display_name`

```elixir
@spec display_name(atom()) :: String.t()
```

Display name suitable for log lines and admin UIs.

# `fetch!`

```elixir
@spec fetch!(atom()) :: module()
```

Resolve a preset id to its implementing module. Raises if unknown so
typos fail loudly at config time, not at runtime during a login flow.

# `from_metadata_url`

```elixir
@spec from_metadata_url(atom(), String.t()) :: keyword()
```

Bootstrap a preset from an IdP metadata URL.

The URL is stored as `:idp_metadata_url` and the preset defaults are
still applied underneath user overrides.

# `guide_url`

```elixir
@spec guide_url(atom()) :: String.t()
```

Public guide URL for the preset.

# `hint_for`

```elixir
@spec hint_for(Relyra.Connection.t() | nil, label_key()) :: String.t() | nil
```

Build an admin-facing hint string suitable for `Relyra.Error` details.
Returns `nil` when no preset is bound to the connection (adopters
without a preset get clean errors with no hints injected).

# `label_entry`

```elixir
@spec label_entry(atom(), label_key()) :: label_entry() | nil
```

Return the full label entry (label + section + hint) for a field.
Use this when building admin error hints so the operator gets the
IdP's section name and a one-liner hint, not just the label.

# `list`

```elixir
@spec list() :: [atom()]
```

List supported preset ids.

# `translate_label`

```elixir
@spec translate_label(atom(), label_key()) :: String.t()
```

Translate one of our internal field names to the IdP's admin UI label.
Falls back to the underscored key as a string when the preset has no
entry, so missing translations degrade gracefully.

---

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