Skip to main content

server_less_openapi/
types.rs

1//! Typed OpenAPI structures.
2//!
3//! These types represent a subset of OpenAPI 3.0 used by server-less for spec generation.
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// An OpenAPI path with its operations.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct OpenApiPath {
11    /// The path pattern (e.g., "/users/{id}").
12    pub path: String,
13    /// HTTP method (lowercase: "get", "post", etc.).
14    pub method: String,
15    /// The operation definition.
16    pub operation: OpenApiOperation,
17}
18
19/// An OpenAPI operation (endpoint).
20#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
21pub struct OpenApiOperation {
22    /// Short summary of the operation.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub summary: Option<String>,
25    /// Extended description of the operation.
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub description: Option<String>,
28    /// Unique operation identifier.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub operation_id: Option<String>,
31    /// Tags for grouping operations in documentation.
32    #[serde(default, skip_serializing_if = "Vec::is_empty")]
33    pub tags: Vec<String>,
34    /// Whether this operation is deprecated.
35    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
36    pub deprecated: bool,
37    /// Operation parameters.
38    #[serde(default, skip_serializing_if = "Vec::is_empty")]
39    pub parameters: Vec<OpenApiParameter>,
40    /// Request body definition.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub request_body: Option<Value>,
43    /// Response definitions keyed by status code.
44    #[serde(default)]
45    pub responses: serde_json::Map<String, Value>,
46    /// Additional fields not explicitly modeled.
47    #[serde(flatten)]
48    pub extra: serde_json::Map<String, Value>,
49}
50
51/// An OpenAPI parameter.
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub struct OpenApiParameter {
54    /// Parameter name.
55    pub name: String,
56    /// Location: "path", "query", "header", or "cookie".
57    #[serde(rename = "in")]
58    pub location: String,
59    /// Whether the parameter is required.
60    #[serde(default)]
61    pub required: bool,
62    /// Parameter schema.
63    #[serde(default)]
64    pub schema: Value,
65    /// Parameter description.
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub description: Option<String>,
68    /// Additional fields.
69    #[serde(flatten)]
70    pub extra: serde_json::Map<String, Value>,
71}
72
73/// An OpenAPI schema definition.
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
75pub struct OpenApiSchema {
76    /// Schema name (used as key in components/schemas).
77    pub name: String,
78    /// The schema definition.
79    pub schema: Value,
80}
81
82impl OpenApiPath {
83    /// Create a new path.
84    pub fn new(path: impl Into<String>, method: impl Into<String>) -> Self {
85        Self {
86            path: path.into(),
87            method: method.into().to_lowercase(),
88            operation: OpenApiOperation::default(),
89        }
90    }
91
92    /// Set the operation for this path.
93    pub fn with_operation(mut self, operation: OpenApiOperation) -> Self {
94        self.operation = operation;
95        self
96    }
97}
98
99impl Default for OpenApiOperation {
100    fn default() -> Self {
101        Self {
102            summary: None,
103            description: None,
104            operation_id: None,
105            tags: Vec::new(),
106            deprecated: false,
107            parameters: Vec::new(),
108            request_body: None,
109            responses: serde_json::Map::new(),
110            extra: serde_json::Map::new(),
111        }
112    }
113}
114
115impl OpenApiOperation {
116    /// Create a new operation with a summary.
117    pub fn new(summary: impl Into<String>) -> Self {
118        Self {
119            summary: Some(summary.into()),
120            ..Default::default()
121        }
122    }
123
124    /// Set the operation ID.
125    pub fn with_id(mut self, id: impl Into<String>) -> Self {
126        self.operation_id = Some(id.into());
127        self
128    }
129
130    /// Set the description.
131    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
132        self.description = Some(desc.into());
133        self
134    }
135
136    /// Add a tag.
137    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
138        self.tags.push(tag.into());
139        self
140    }
141
142    /// Set multiple tags.
143    pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
144        self.tags.extend(tags.into_iter().map(|t| t.into()));
145        self
146    }
147
148    /// Mark as deprecated.
149    pub fn deprecated(mut self) -> Self {
150        self.deprecated = true;
151        self
152    }
153
154    /// Add a parameter.
155    pub fn with_parameter(mut self, param: OpenApiParameter) -> Self {
156        self.parameters.push(param);
157        self
158    }
159
160    /// Add a response.
161    pub fn with_response(mut self, status: impl Into<String>, response: Value) -> Self {
162        self.responses.insert(status.into(), response);
163        self
164    }
165}
166
167impl OpenApiParameter {
168    /// Create a path parameter.
169    pub fn path(name: impl Into<String>) -> Self {
170        Self {
171            name: name.into(),
172            location: "path".to_string(),
173            required: true, // Path params are always required
174            schema: serde_json::json!({"type": "string"}),
175            description: None,
176            extra: serde_json::Map::new(),
177        }
178    }
179
180    /// Create a query parameter.
181    pub fn query(name: impl Into<String>, required: bool) -> Self {
182        Self {
183            name: name.into(),
184            location: "query".to_string(),
185            required,
186            schema: serde_json::json!({"type": "string"}),
187            description: None,
188            extra: serde_json::Map::new(),
189        }
190    }
191
192    /// Create a header parameter.
193    pub fn header(name: impl Into<String>, required: bool) -> Self {
194        Self {
195            name: name.into(),
196            location: "header".to_string(),
197            required,
198            schema: serde_json::json!({"type": "string"}),
199            description: None,
200            extra: serde_json::Map::new(),
201        }
202    }
203
204    /// Set the schema.
205    pub fn with_schema(mut self, schema: Value) -> Self {
206        self.schema = schema;
207        self
208    }
209
210    /// Set the description.
211    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
212        self.description = Some(desc.into());
213        self
214    }
215}
216
217impl OpenApiSchema {
218    /// Create a new schema.
219    pub fn new(name: impl Into<String>, schema: Value) -> Self {
220        Self {
221            name: name.into(),
222            schema,
223        }
224    }
225}