# `DocShell.Generate.OpenApi`
[🔗](https://github.com/futhr/doc_shell/blob/v2.0.0/lib/doc_shell/generate/open_api.ex#L1)

Loads an OpenAPI document through a pluggable adapter, and sanity-checks it.

API reference pages belong in a documentation site, but where the OpenAPI
document comes from varies wildly: derived from Ash domains, declared with
OpenApiSpex, generated by a service in another language, or checked in by
hand. Binding the build pipeline to any one of those would make DocShell
useful to a fraction of the projects that want it.

So the pipeline talks to `DocShell.Generate.OpenApi.Adapter` and never to a
spec library. Three adapters ship with the package — see
`DocShell.Generate.OpenApi.Adapters.AshOaskit`,
`DocShell.Generate.OpenApi.Adapters.OpenApiSpex`, and
`DocShell.Generate.OpenApi.Adapters.RawJson` — and hosts add their own by
implementing one callback. Writing one is covered in the
[OpenAPI adapters notebook](openapi-adapters.html).

## What this module guarantees

`extract/2` is the trust boundary between the build and code it does not
own. It asks Elixir to load the adapter module, checks that loaded modules
export `load/1`, calls the callback inside a rescue so an adapter raising
cannot take down a build with a
stacktrace instead of a reason, and validates that whatever came back
actually claims to be OpenAPI 3.0, 3.1, or 3.2.

Validation also rejects unencodable values and duplicate encoded object keys.
Full schema validation is the job of
the spec library that produced the document, and duplicating it here would
mean DocShell rejecting documents its own adapters consider fine. What
`validate/1` catches is the common integration mistake: an adapter returning
a config map, an envelope, or a spec struct that was never rendered.

## Errors

  * `{:error, :nofile}` — the adapter module could not be loaded
  * `{:error, :invalid_adapter}` — the loaded module does not export `load/1`
  * `{:error, :invalid_openapi_source}` — the adapter returned something
    other than `{:ok, map}` or `{:error, reason}`
  * `{:error, :invalid_openapi_document}` — no usable `openapi` version key
  * `{:error, {:openapi_adapter_failed, message}}` — the adapter raised
  * anything the adapter itself returned as `{:error, reason}`

# `extract`

```elixir
@spec extract(module(), keyword()) :: {:ok, map()} | {:error, term()}
```

Loads the OpenAPI document from `adapter`, passing `opts` through to it.

Guards against a missing, non-conforming, or raising adapter, and rejects
documents that do not identify as OpenAPI 3.0, 3.1, or 3.2.

# `validate`

```elixir
@spec validate(term()) :: :ok | {:error, term()}
```

Checks that a map identifies itself as an OpenAPI 3.0, 3.1, or 3.2 document.

Accepts either a string or atom `openapi` key, since adapters build documents
with whichever their underlying library prefers. This is a shape check, not
schema validation — see the module documentation for where that line sits.

## Examples

    iex> DocShell.Generate.OpenApi.validate(%{"openapi" => "3.1.0"})
    :ok

    iex> DocShell.Generate.OpenApi.validate(%{openapi: "3.0.3"})
    :ok

    iex> DocShell.Generate.OpenApi.validate(%{"swagger" => "2.0"})
    {:error, :invalid_openapi_document}

---

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