DocShell.Generate.OpenApi (DocShell v0.1.0)

Copy Markdown View Source

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.

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 or 3.1.

That validation is deliberately shallow. 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}

Summary

Functions

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

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

Functions

extract(adapter, opts)

@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 or 3.1.

validate(spec)

@spec validate(map()) :: :ok | {:error, :invalid_openapi_document}

Checks that a map identifies itself as an OpenAPI 3.0 or 3.1 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}