Metadata-Version: 2.4
Name: splent_feature_apidocs
Version: 0.5.1
License-Expression: LGPL-3.0-or-later
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: COPYING
License-File: COPYING.LESSER
Requires-Dist: flasgger==0.9.7.2.dev2
Requires-Dist: PyYAML>=6.0
Requires-Dist: splent_framework>=1.30.0
Dynamic: license-file

# splent_feature_apidocs

Documentation for a SPLENT product's REST API: an OpenAPI 3.1 document at
`/apispec_1.json`, generated from the product's routes and from what their
views declare, and a Swagger UI that reads it at `/apidocs/`. Both are the
addresses uvlhub 2.12 gave them.

The feature documents no route of its own. Every feature that has an API
documents it where the code is, and this one says what the API as a whole
is: its title, description and version, which paths are the API, and how it
takes a token.

[flasgger](https://github.com/flasgger/flasgger) keeps the two addresses and
nothing else. It no longer builds the spec (it read every docstring as YAML,
and one colon in a feature's free text answered 500 for the whole spec and
the page), and it no longer draws the page, which `templates/apidocs/index.html`
draws with the Swagger UI this feature ships (swagger-ui-dist 5.17.14;
flasgger's own is 3.15.1, which reads no OpenAPI 3.1), its script
`assets/js/apidocs.js` and its style `assets/css/apidocs.css`. The page loads
nothing from another site and has no inline script or style, so it keeps to
the framework's Content-Security-Policy as it is, and its fonts are the
system's.

Each version of the API, the rules under `/api/v1/`, `/api/v2/` and so on,
has a document of its own at `/api/<version>/openapi.json`, and the page
offers each in its top bar. `/apispec_1.json` is the first version's, with
what is under `APIDOCS_PATHS` that is no version.

## What goes in the document

`openapi.py` builds it from the url_map:

- **Every rule under `APIDOCS_PATHS`** (`/api/` by default) is an operation,
  whatever its view says: its path parameters typed from their converters
  (`<int:id>` is an integer), its tag from its blueprint, its summary from
  the first line of its docstring. The rest of a docstring without a spec
  block is not published: it is a note to whoever reads the code.
- **What the view declares** completes it, the first of:
  1. an `openapi` attribute, an OpenAPI 3.1 operation as data (or one per
     method, keyed by the lowercase method). `False` leaves the route out.
  2. the spec block after `---` in its docstring, the Swagger 2.0 operation
     flasgger read, converted: `type` and its limits into the parameter's
     `schema`, `formData` and a `file` into a `multipart/form-data` body, a
     `body` parameter into a JSON one, a response `schema` into `content`,
     `nullable` into a type that admits `null`. A block that is not valid
     YAML is logged as a warning naming the endpoint, and the route is
     documented from what its rule says.
  3. what apidocs knows of a feature that declares nothing yet
     (`known_operations.py`): apikeys' token API.
- A rule outside `APIDOCS_PATHS` goes in only when its view declares one.

The document is built once, and again when a setting it reads changes.

## Documenting a route

Declare the operation as data, which needs neither YAML nor this feature:

```python
@reports_bp.route("/api/v1/reports", methods=["GET"])
@require_token("read_reports")
def api_list_reports():
    """List the published reports."""
    ...


api_list_reports.openapi = {
    "tags": ["Reports"],
    "security": [{"ApiKeyAuth": []}],
    "parameters": [
        {"name": "year", "in": "query", "schema": {"type": "integer", "minimum": 1900}},
    ],
    "responses": {"200": {"description": "The reports, newest first"}},
}
```

A spec block in the docstring still works, as flasgger read it. Quote a
description that has a colon in it (`description: "Only with Prefer:
respond-async."`), or write it as a block (`description: >`): a bare one is
a second mapping to YAML, and the block is then skipped.

## Tokens

When the product installs `splent_feature_apikeys`, the document describes
how that feature reads a token:

| Scheme              | Type     | Header          | Value               |
|---------------------|----------|-----------------|---------------------|
| `bearerAuth`        | `http`   | `Authorization` | `Bearer <token>`    |
| one per alias       | `apiKey` | the alias       | the token as it is  |

The aliases are the headers the product lists in apikeys'
`APIKEYS_HEADER_ALIASES`, and each scheme is named after its header
(`X-API-Key`, say). An operation that needs a token says
`security: [{ApiKeyAuth: []}]`, the name the docstrings use, and the
document turns it into any one of the schemes. Without apikeys it describes
no token at all. An apikeys release that predates header aliases reads the
Bearer header alone, and then the document describes `bearerAuth` only.

## Settings

| Variable              | Default                 | Meaning                                  |
|-----------------------|-------------------------|------------------------------------------|
| `APIDOCS_TITLE`       | `{SITE_NAME} REST API`  | Title of the page and of the spec        |
| `APIDOCS_DESCRIPTION` | empty                   | Shown under the title, left out if empty |
| `APIDOCS_VERSION`     | `1.0.0`                 | The spec's `info.version`                |
| `APIDOCS_PATHS`       | `["/api/"]`             | The paths that are the API: every rule under them is documented |

`{SITE_NAME}` in the title or the description becomes the product's
`SITE_NAME` when the docs are served. A product without one gets the title
`REST API`. They are read when the docs are served rather than when the
feature loads, so a site name or a token header that another feature
configures later in the startup is always found.

```toml
# The product's pyproject.toml
[tool.splent.config]
APIDOCS_TITLE = "Reports API (v2)"
APIDOCS_DESCRIPTION = "The reports and the files they cite."
APIDOCS_VERSION = "2.0.0"
```

## The sidebar link

"API docs" is an entry of the framework's menu registry.

| Key | Section | Order | Icon | Opens | Shown to |
|-----|---------|-------|------|-------|----------|
| `apidocs` | `developer` | 10 | `book-open` | `/apidocs/`, in a new tab | Signed-in accounts |

It does not depend on apikeys: the developer section is one the framework
gives every product, so a product without apikeys still shows the link.
With apikeys its token entries come after it in the same section. The
entry is `external`, so the shell draws it with `target="_blank"` and
`rel="noopener"`. A product hides or reorders it by key in its
`[tool.splent.layout.menu]`.

For one minor the link is also added to `apikeys.sidebar.developer`, the
slot apikeys opens beside its API tokens entry, for the shells that fill
their sidebar from hooks, with the label it had there, "Read the API
docs". That markup is in
`templates/hooks/sidebar_apidocs.html`, which a product can override with
its own layout's, and without apikeys the slot does not exist. A shell
draws the registry or those slots, never both, or the link shows twice.

## Use it in a product

```bash
splent feature:add splent_feature_apidocs
```

It needs no `.env` entry and no migration.

## Frontend

The Swagger UI is an npm dependency, `swagger-ui-dist` 5.17.14 in
`frontend/package.json`, locked in `frontend/package-lock.json`. Nothing of
it is vendored: the SPLENT toolchain installs it and
`frontend/webpack.extra.js` copies its prebuilt `swagger-ui-bundle.js`,
`swagger-ui-standalone-preset.js` and `swagger-ui.css` into `assets/dist`,
byte for byte, where `SWAGGER_UI` in `__init__.py` points the page. The
feature has no entry of its own (`"entries": {}`), since the page's script,
`assets/js/apidocs.js`, is a plain script served as it is.

```bash
splent feature:compile apidocs            # build assets/dist
splent feature:compile apidocs --verify   # the files match assets/meta/manifest.json
splent feature:npm apidocs outdated       # a newer Swagger UI?
```

To move to another Swagger UI, install it with
`splent feature:npm apidocs install swagger-ui-dist@<version>` and check
that the page still reads an OpenAPI 3.1 document.

## License

The Swagger UI is SmartBear's, under the Apache License 2.0. A release
writes its LICENSE and NOTICE into `assets/meta/third_party.json`, and the
SBOM of what the package ships into `assets/meta/sbom.cdx.json`.

## Develop

```bash
splent feature:test splent_feature_apidocs
splent feature:contract splent_feature_apidocs
```
