Skip to content

docs(api): OpenAPI annotations for the native API (WI-2032) - #250

Closed
thedancingdeveloper wants to merge 1 commit into
mainfrom
wi-2032-openapi
Closed

thedancingdeveloper wants to merge 1 commit into
mainfrom
wi-2032-openapi

Conversation

@thedancingdeveloper

Copy link
Copy Markdown
Collaborator

WI-2032: documents all 92 native /api operations (plus 4 WebDAV ones when that feature is built) in the served OpenAPI spec, with a completeness test. Written by a DeepSeek worker.

🤖 Generated with Claude Code

@thedancingdeveloper

Copy link
Copy Markdown
Collaborator Author

Review of 767c0d5: no blockers, with 1 should-fix and nits. GitHub shows no checks on this PR at all (CI hasn't run), so re-trigger it before landing.

Checks run locally:

  • cargo test -p rustnzb --test openapi_document passes 4/4, and passes 4/4 again with --features webdav.
  • cargo clippy --workspace --all-targets -D warnings and fmt --check are clean.

Manifests. Cargo.lock changes only by adding utoipa to two packages and bumping nzb-nntp 0.2.26 → 0.2.27 and nzb-web 0.4.24 → 0.4.25. Those bumps are right for the new public ToSchema derives. utoipa moves to the workspace with the chrono feature, and there's no windows-sys churn.

Completeness test: real

  • Route coverage. document_covers_every_native_route compares the generated document against NATIVE_ROUTES (+ WEBDAV_ROUTES under the feature) in both directions. I extracted every .route(...) method and path from server.rs and diffed it against the list. They match exactly: 94 native routes plus 4 WebDAV. The only routes absent from the list are /, the SABnzbd compat /api and /sabnzbd/api, which are deliberately undocumented, plus /api/health and /api/ready, which are listed but mounted on the root router.
  • Annotation scan. every_handler_is_annotated scans the seven handler sources for pub async fn h_* and requires a #[utoipa::path] in the contiguous attribute and doc block above it. It's a real failing check for a routed but unannotated handler.
  • Limits. The route list is hand-maintained, so a route added to the router and to the list is the only path that's fully enforced. The source scan doesn't include crates/nzb-web/src/auth.rs, so the six auth::h_* handlers are covered only by the route list. Add auth.rs to sources (with a ../../../crates/nzb-web/src/auth.rs path), or derive the expected set from the router in a test.
  • Quality. every_operation_is_described (a tag, a summary, at least one response) and the schema-registration test are sensible.

Spec accuracy: sampled six operations, all match their handlers

  • POST /api/history/bulk: body HistoryBulkBody, 200 BulkActionResponse, 400 for an unknown action or too many ids. The description covers status-wide deletes and the days, category and search filters.
  • POST /api/queue/pause-for: params(PauseForQuery) matches Query<PauseForQuery>; it isn't a JSON body, which is correct.
  • DELETE /api/history/{id}: 200 SimpleResponse. That's correct; the delete is idempotent, so there's no 404.
  • POST /api/queue/add: AddNzbQuery plus the Idempotency-Key header. 409 is real (DuplicateNzb and AdmissionConflict map to CONFLICT), and 413 is real (DefaultBodyLimit of 200 MB). The 200 body is AddNzbResponse.
  • ApiError: the hand-written schema {error_kind, human_readable, status}, all required, matches impl Serialize for ApiError exactly.
  • Auth: the description's "Bearer token, HTTP Basic, or X-Api-Key" matches the native auth middleware in server.rs. Auth's plain-text 400 and 401 bodies are declared text/plain, which is also correct.

Secrets: none

The diff contains no example values at all. Every credential-related description says passwords and keys are masked, which matches the existing masking: GET /api/config, GET /api/config/servers, the DAV config, and "a masked or empty password keeps the stored value" on update. The served document carries schema only, not configuration values.

Should fix

  1. No securitySchemes are declared. Swagger UI at /swagger-ui therefore has no Authorize button, every "Try it out" call on a protected route returns 401, and nothing marks the public operations as public (/api/health, /api/ready, /api/auth/status, /api/auth/login, /api/auth/refresh, /api/auth/setup, and /api/setup/* before first boot). Add a Modify that registers bearer (HTTP bearer), basic (HTTP basic) and api_key (header X-Api-Key) schemes, set a global security, and add security(()) on the public operations.

Nits

  • /api/queue/add declares its multipart body as content = String. Swagger UI renders a text box, so a file can't be attached. A #[derive(ToSchema)] struct AddNzbForm { #[schema(format = Binary, value_type = String)] file: …, password: Option<String>, … } would make it usable.
  • The 5xx paths aren't documented, for example qm_blocking database failures returning ApiError 500. One shared (status = 500, body = ApiError) per operation would be honest.

`/swagger-ui` was mounted but documented nothing: ApiDoc carried only an
`info(...)` block, so `/api-docs/openapi.json` served empty `paths` and
`components`. AGENTS.md calls `/api/...` a compatibility surface documented
through utoipa, which was vacuous while no handler was annotated.

- Annotate every native handler with `#[utoipa::path]`, grouped by tag:
  auth, system, setup, queue, history, statistics, config, servers,
  categories, rss, groups, articles and dav.
- Derive `ToSchema` on the request/response and shared model types, with
  `#[schema(value_type = String)]` for PathBuf fields. `ApiError` describes
  the `{error_kind, human_readable, status}` body the middleware emits.
- Split ApiDoc into the always-present base and a `webdav`-gated DAV part,
  merged in a manual `OpenApi` impl, so DAV paths appear only in builds that
  serve them.
- Add `apps/rustnzb/tests/openapi_document.rs`: every route from the router
  must appear in the document, no extra routes may appear, every operation
  carries a tag, summary and response, the shared models are registered, and
  every `async fn h_*` in a handler module carries an annotation.
- Enable utoipa's `chrono` feature for the shared config and model schemas.

The SABnzbd compatibility layer keeps its separate contract and is
deliberately outside the document.

Bumps nzb-nntp to 0.2.27 and nzb-web to 0.4.25.
@thedancingdeveloper

Copy link
Copy Markdown
Collaborator Author

Superseded by #257, which rebases this onto current main.

The rebase also annotates the routes main gained in the meantime (/api/config/backup, /api/config/backup/preview, /api/config/backup/restore, /api/config/schedule) and bumps nzb-core to 0.2.23 for the added ToSchema on the schedule types, alongside the original nzb-web 0.4.25 and nzb-nntp 0.2.27. Format, clippy and the full workspace test suite are green on the new branch.

@thedancingdeveloper

Copy link
Copy Markdown
Collaborator Author

Superseded by #257 (rebased).

auto-merge was automatically disabled October 21, 2026 00:44

Pull request was closed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant