Source code for fsh_lib.openapi
"""codegen's openapi-extension keys + their runtime constructor.
Routes the BE generates carry two custom extensions consumed by
the FE codegen layer:
* ``X_CACHE_KEY`` (``"x-cache-key"``) -- string identifying
the TanStack Query key root the FE should use for this route's
response cache. Only emitted on read routes (any GET, plus the
POST search endpoints that are semantically reads). Its
presence *is* the read signal: the FE generates ``useQuery`` /
``useSuspenseQuery`` wrappers for any GET or any route carrying
a cache key, and treats everything else as a mutation -- so no
separate "this is a query" extension is needed.
* ``X_RESOURCE`` (``"x-resource"``) -- the resource's *singular*
slug (the model's snake name, e.g. ``"task"`` for the ``tasks``
resource). Stamped on *every* CRUD route (reads and writes
alike, unlike ``X_CACHE_KEY``). It's the canonical resource
identity: the FE groups a resource's routes by it (to discover
the CRUD SDK fns), and derives the saved-view ``resource_type``
discriminator + default singular display label from it. The
plural cache namespace is the separate ``X_CACHE_KEY`` (e.g.
``"tasks"``); a read route carries both, which is how the FE
bridges its plural config key to this singular identity.
* ``X_AUTH_ROLE`` (``"x-auth-role"``) -- ``"login"`` /
``"validate"`` / ``"logout"`` on the three auth-router routes.
Lets the FE derive the auth SDK fns (and, off them, the session
+ credentials types) instead of naming them in fe.jsonnet.
* ``X_PAGINATION`` (``"x-pagination"``) -- pagination facts for a
paginated search route, e.g. ``{"mode": "keyset",
"default_page_size": 20, "max_page_size": 100}``. ``mode`` is
``"keyset"`` (request carries ``cursor``/``page_size``, response
is ``{items, next_cursor}``) or ``"offset"`` (request carries
``offset``/``limit``, response is ``{items, total,
total_pages}``). The FE codegen reads this to emit a paginated
table hook for the route; without it the response shape would
have to be inferred structurally from the ``{Model}Page``
schema, which is brittle for hand-written routes.
* ``X_FILES`` (``"x-fsh-files"``) -- file-resource role tag, e.g.
``{"role": "request"}``. Used so that we can associate file
operations with each other and then create a FE hook for the
uploading that uses multiple hooks at once.
:func:`construct_openapi_extra` builds a dict suitable for
FastAPI's ``openapi_extra=`` kwarg. Generated handlers call it
inline so the extension keys live in exactly one place (this
module). Hand-written handlers can use it the same way.
"""
from __future__ import annotations
X_CACHE_KEY = "x-cache-key"
X_RESOURCE = "x-resource"
X_AUTH_ROLE = "x-auth-role"
X_PAGINATION = "x-pagination"
X_ACTION = "x-action"
X_FSH_AUDIT = "x-fsh-audit"
X_FILES = "x-fsh-files"
X_FSH_ASYNC = "x-fsh-async"
[docs]
def construct_operation_id(operation: str, prefix: str = "") -> str:
"""Return a route's ``operation_id`` from its typed operation.
``operation`` is a generated ``<Resource>Operation`` member whose
value is the resource-qualified id (e.g. ``"list_categories"``,
``"asset_get_detail"``) -- the same value the async runner registry
and ``@negotiated`` key on. When the project configures an
``operation_id_prefix`` it is prepended here, so the FE-facing id
stays a single, typed composition point.
"""
return f"{prefix}_{operation}" if prefix else str(operation)
[docs]
def construct_openapi_extra(
*,
cache_key: str | None = None,
resource: str | None = None,
auth_role: str | None = None,
pagination: dict[str, object] | None = None,
action: dict[str, object] | None = None,
audit: str | None = None,
files: dict[str, object] | None = None,
async_ops: dict[str, object] | None = None,
) -> dict[str, object]:
"""Build the ``openapi_extra`` payload for a codegen-generated route.
Args:
cache_key: TanStack Query key root for the route's
response cache. ``None`` skips the ``X_CACHE_KEY``
entry (right for write routes, which don't seed any
cache). Its presence doubles as the read signal -- the
FE treats any GET or any cache-keyed route as a query,
so a read POST (a search endpoint) just sets this.
resource: The resource's singular slug (model snake name,
e.g. ``"task"``). ``None`` skips the ``X_RESOURCE``
entry. Stamped on both read and write routes; the
canonical resource identity the FE groups routes by
and derives the saved-view discriminator + label from.
auth_role: ``"login"`` / ``"validate"`` / ``"logout"`` on
an auth-router route. ``None`` skips the
``X_AUTH_ROLE`` entry. Lets the FE derive its auth SDK
fns and the session / credentials types.
pagination: Pagination facts for a paginated search route
-- ``{"mode": "keyset" | "offset", "default_page_size":
int, "max_page_size": int}``. ``None`` skips the
``X_PAGINATION`` entry (right for unpaginated or
non-search routes). The FE reads this to generate a
paginated table hook for the route.
action: Custom-action facts for an action route --
``{"name": str, "scope": "object" | "collection",
"bulk": bool}``. ``None`` skips the ``X_ACTION`` entry
(right for built-in CRUD routes). The FE reads this to
fold the action into the resource's action catalog and,
when ``bulk``, surface it as a multi-selection action.
audit: Parent-resource slug tagging an activity/changeset
route (``x-fsh-audit``). ``None`` skips the
``X_FSH_AUDIT`` entry (right for non-audit routes).
files: File-resource role tag for a route of a FileMixin
resource -- ``{"role": "request" | "complete" |
"download" | "delete"}``. ``None`` skips the
``X_FILES``
async_ops: Async-operation facts for an async-capable route
-- ``{"formats": ["csv"], "poll_route": "/_async/runs/{run_id}",
"default_async": True}``. ``None`` skips the
``X_FSH_ASYNC`` entry (right for sync-only routes).
Returns:
A dict suitable for ``@router.<method>(...,
openapi_extra=...)``. Empty when no argument is set.
"""
extra: dict[str, object] = {}
if cache_key is not None:
extra[X_CACHE_KEY] = cache_key
if resource is not None:
extra[X_RESOURCE] = resource
if auth_role is not None:
extra[X_AUTH_ROLE] = auth_role
if pagination is not None:
extra[X_PAGINATION] = pagination
if action is not None:
extra[X_ACTION] = action
if audit is not None:
extra[X_FSH_AUDIT] = audit
if files is not None:
extra[X_FILES] = files
if async_ops is not None:
extra[X_FSH_ASYNC] = async_ops
return extra