super-stt-daemon/src/daemon/http/openapi.rs
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-3.0-only |
2 | | //! The `OpenAPI` document for the daemon protocol. |
3 | | //! |
4 | | //! The document is generated from the router, not written beside it: every |
5 | | //! `/v1` route is registered through [`utoipa_axum::routes!`], which reads the |
6 | | //! `#[utoipa::path]` attribute on the handler it points at. A route and its |
7 | | //! documentation are therefore one declaration — adding a route without |
8 | | //! documenting it does not compile, and changing a path changes both. |
9 | | //! |
10 | | //! `just openapi` writes the result to `docs/protocol/openapi.json`; |
11 | | //! `just openapi-check` fails when the committed file is stale, so a protocol |
12 | | //! change cannot merge without the published spec moving with it. |
13 | | //! |
14 | | //! The prose reference under `docs/protocol/` is not replaced by any of this. |
15 | | //! It explains *when* to call an endpoint and how the pieces fit; the spec |
16 | | //! states the shapes exactly, for tooling and for a client generator. |
17 | | |
18 | | use utoipa::Modify; |
19 | | use utoipa::OpenApi; |
20 | | use utoipa::openapi::security::{HttpAuthScheme, HttpBuilder, SecurityScheme}; |
21 | | |
22 | | /// Base document: everything that is true of the protocol as a whole rather |
23 | | /// than of one endpoint. The paths and schemas are filled in from the router |
24 | | /// (see [`super::v1::openapi`]). |
25 | | #[derive(OpenApi)] |
26 | | #[openapi( |
27 | | info( |
28 | | title = "Super STT daemon protocol", |
29 | | description = "\ |
30 | | HTTP/1.1 + JSON over a Unix domain socket at `$XDG_RUNTIME_DIR/stt/super-stt-http.sock` \ |
31 | | (override with `SUPER_STT_HTTP_SOCKET`). There is no TCP listener: the socket's \ |
32 | | filesystem permissions are the first layer of access control, and the daemon reads \ |
33 | | `SO_PEERCRED` on each connection to identify the calling binary. |
34 | | |
35 | | Every endpoint except `POST /v1/auth/request` requires `Authorization: Bearer <token>`. \ |
36 | | A token is minted only after the user approves your app in a consent popup, and is bound \ |
37 | | to the approved binary — an app cannot widen its own permissions. See `docs/protocol/auth.md`. |
38 | | |
39 | | Because the transport is a Unix socket, the `servers` entry below is nominal; point your \ |
40 | | client at the socket and use any `Host`. With curl: |
41 | | |
42 | | ``` |
43 | | curl --unix-socket \"$XDG_RUNTIME_DIR/stt/super-stt-http.sock\" \\ |
44 | | -H \"Authorization: Bearer $STT_TOKEN\" \\ |
45 | | http://stt.local/v1/ping |
46 | | ```", |
47 | | license(name = "GPL-3.0-only", identifier = "GPL-3.0-only"), |
48 | | contact(name = "Super STT", url = "https://github.com/jorge-menjivar/super-stt"), |
49 | | ), |
50 | | servers((url = "http://stt.local", description = "Nominal host; the transport is the Unix socket")), |
51 | | modifiers(&BearerAuth), |
52 | | tags( |
53 | | (name = "auth", description = "Consent handshake and token probing."), |
54 | | (name = "health", description = "Liveness and what the daemon is currently running."), |
55 | | (name = "transcribe", description = "Start, stop and stream transcription."), |
56 | | (name = "events", description = "Server-Sent Events for recording state, audio levels, model and download progress, and final transcripts."), |
57 | | (name = "pipeline", description = "The ordered stages a transcript passes through: which backend fills each, which model runs there, and on what device."), |
58 | | (name = "settings", description = "Stored daemon preferences, one value apiece, all under `/v1/settings`: audio cues, write and notification methods, language, update policy. Sharing the `settings` scope is not the same as being a setting \u{2014} `backends`, `pipeline` and `registry` are guarded by it too."), |
59 | | (name = "hardware", description = "What the daemon can see of this machine: GPUs, drivers, runtimes."), |
60 | | (name = "update", description = "Whether a newer daemon exists, and asking it to look now."), |
61 | | (name = "backends", description = "Installed backends: their models, options and secrets."), |
62 | | (name = "registry", description = "The published backend catalog: browse, install, update, uninstall."), |
63 | | ), |
64 | | )] |
65 | | pub(crate) struct ApiDoc; |
66 | | |
67 | | /// The one security scheme: the session token from `POST /v1/auth/request`, |
68 | | /// presented as a bearer token. Declared here rather than per endpoint so the |
69 | | /// scheme has a single definition; which *scopes* each endpoint needs is stated |
70 | | /// on the endpoint, since that is where it differs. |
71 | | struct BearerAuth; |
72 | | |
73 | | impl Modify for BearerAuth { |
74 | 14 | fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { |
75 | 14 | let components = openapi |
76 | 14 | .components |
77 | 14 | .as_mut() |
78 | 14 | .expect("the derived document always carries a components object"); |
79 | 14 | components.add_security_scheme( |
80 | | "session_token", |
81 | 14 | SecurityScheme::Http( |
82 | 14 | HttpBuilder::new() |
83 | 14 | .scheme(HttpAuthScheme::Bearer) |
84 | 14 | .description(Some( |
85 | 14 | "Session token from `POST /v1/auth/request`. Bound to the calling \ |
86 | 14 | binary and valid for 30 days.", |
87 | 14 | )) |
88 | 14 | .build(), |
89 | 14 | ), |
90 | | ); |
91 | 14 | } |
92 | | } |