Serves cached artifacts over HTTP, behind an authorization gate you supply.
Mount it wherever the documentation JSON should live:
forward "/docs/api",
to: DocShell.Web.Plug,
init_opts: [gate: &MyApp.Auth.allow_docs?/1]The last path segment names the artifact, with .json optional, so
GET /docs/api/navigation and GET /docs/api/navigation.json both return
navigation.json from DocShell.Web.Cache. The stored envelope is served
as-is, so generated_at is the time of the build that produced the
artifact — not the time of the request, which would be useless to a client
and would defeat any caching built on top of it.
The conn is halted once a response is sent, so this is safe to place in a
pipeline as well as behind forward.
The gate
Documentation is rarely uniformly public. Internal runbooks, unreleased features, and per-tenant guides all need someone to decide who may read them — and that someone is the host, which knows about sessions, roles, and tenancy. DocShell knows about none of it and should not pretend otherwise.
So authorization is a single :gate option, either a unary function or an
{module, function, extra_args} tuple receiving the conn first:
init_opts: [gate: {MyApp.Auth, :allow_docs?, [:internal]}]Return :ok or true to allow the request; anything else yields 403.
Omitting :gate serves every artifact to every caller, which is the right
default for genuinely public documentation and the wrong one everywhere
else — decide deliberately.
Responses
200withapplication/json— the enveloped artifact403— the gate refused404— no such artifact, or a path that is not a single segment500— the artifact is cached but could not be encoded
Multi-segment paths are rejected rather than joined, so no request can walk out of the cache and into the filesystem.
Multiple caches
The :cache option selects which named DocShell.Web.Cache to read,
defaulting to DocShell.Web.Cache itself. A host serving public and
internal artifact sets runs two caches and mounts this plug twice, with a
different gate on each.
Hosts that would rather route through their own controller can call
DocShell.Web.Controller.show/2 instead. This module is only compiled when
Plug is installed.