Skip to main content

soma_palette/
openapi.rs

1//! Product OpenAPI route metadata for `/v1/palette/*`.
2//!
3//! Mirrors the pattern `soma-api` uses for `/v1/gateway/{action}`: augment an
4//! already-generated OpenAPI document with hand-written path entries for
5//! routes this crate owns but that the generic provider-route generator
6//! doesn't know about.
7
8use serde_json::{json, Value};
9
10pub const CATALOG_PATH: &str = "/v1/palette/catalog";
11pub const SEARCH_PATH: &str = "/v1/palette/search";
12pub const SCHEMA_PATH: &str = "/v1/palette/schema";
13pub const EXECUTE_PATH: &str = "/v1/palette/execute";
14
15/// Insert Palette route entries into `value`'s `paths` object, if not
16/// already present. `value` is expected to already have a top-level `paths`
17/// object (as produced by `SomaApplication::openapi_document`).
18pub fn augment_with_palette_routes(value: &mut Value) {
19    let Some(paths) = value.get_mut("paths").and_then(Value::as_object_mut) else {
20        return;
21    };
22    paths
23        .entry(CATALOG_PATH.to_owned())
24        .or_insert_with(catalog_path_item);
25    paths
26        .entry(SEARCH_PATH.to_owned())
27        .or_insert_with(search_path_item);
28    paths
29        .entry(SCHEMA_PATH.to_owned())
30        .or_insert_with(schema_path_item);
31    paths
32        .entry(EXECUTE_PATH.to_owned())
33        .or_insert_with(execute_path_item);
34}
35
36fn catalog_path_item() -> Value {
37    json!({
38        "get": {
39            "summary": "List palette-exposed launcher entries",
40            "description": "Every provider tool whose palette overlay exposes it (default: exposed).",
41            "responses": {
42                "200": {"description": "Launcher catalog"}
43            }
44        }
45    })
46}
47
48fn search_path_item() -> Value {
49    json!({
50        "get": {
51            "summary": "Search palette-exposed launcher entries",
52            "parameters": [
53                {"name": "q", "in": "query", "required": false, "schema": {"type": "string"}},
54                {"name": "limit", "in": "query", "required": false, "schema": {"type": "integer"}}
55            ],
56            "responses": {
57                "200": {"description": "Matching launcher entries"}
58            }
59        }
60    })
61}
62
63fn schema_path_item() -> Value {
64    json!({
65        "get": {
66            "summary": "Get a launcher entry's input/output schema",
67            "parameters": [
68                {"name": "id", "in": "query", "required": true, "schema": {"type": "string"}}
69            ],
70            "responses": {
71                "200": {"description": "Launcher schema"},
72                "404": {"description": "Unknown or no longer palette-exposed launcher id"}
73            }
74        }
75    })
76}
77
78fn execute_path_item() -> Value {
79    json!({
80        "post": {
81            "summary": "Execute a palette launcher action",
82            "description": "Read/write scope, admin, and destructive-confirmation policy are enforced by the underlying provider dispatch.",
83            "requestBody": {
84                "required": true,
85                "content": {
86                    "application/json": {
87                        "schema": {
88                            "type": "object",
89                            "required": ["id"],
90                            "properties": {
91                                "id": {"type": "string"},
92                                "params": {"type": "object"},
93                                "confirmDestructive": {"type": "boolean"}
94                            }
95                        }
96                    }
97                }
98            },
99            "responses": {
100                "200": {"description": "Launcher execution result"},
101                "400": {"description": "Invalid params or missing destructive confirmation"},
102                "403": {"description": "Insufficient scope or admin required"},
103                "404": {"description": "Unknown or no longer palette-exposed launcher id"}
104            }
105        }
106    })
107}
108
109#[cfg(test)]
110#[path = "openapi_tests.rs"]
111mod tests;