elif_openapi/
specification.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Complete OpenAPI 3.0 specification
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct OpenApiSpec {
7    /// OpenAPI specification version
8    pub openapi: String,
9
10    /// API metadata
11    pub info: ApiInfo,
12
13    /// Server URLs
14    #[serde(skip_serializing_if = "Vec::is_empty", default)]
15    pub servers: Vec<Server>,
16
17    /// API paths and operations
18    #[serde(default)]
19    pub paths: HashMap<String, PathItem>,
20
21    /// Reusable components
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub components: Option<Components>,
24
25    /// Global security requirements
26    #[serde(skip_serializing_if = "Vec::is_empty", default)]
27    pub security: Vec<SecurityRequirement>,
28
29    /// Tags for grouping operations
30    #[serde(skip_serializing_if = "Vec::is_empty", default)]
31    pub tags: Vec<Tag>,
32
33    /// External documentation
34    #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
35    pub external_docs: Option<ExternalDocumentation>,
36}
37
38/// API metadata information
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct ApiInfo {
41    /// API title
42    pub title: String,
43
44    /// API description  
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub description: Option<String>,
47
48    /// Terms of service URL
49    #[serde(rename = "termsOfService", skip_serializing_if = "Option::is_none")]
50    pub terms_of_service: Option<String>,
51
52    /// Contact information
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub contact: Option<Contact>,
55
56    /// License information
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub license: Option<License>,
59
60    /// API version
61    pub version: String,
62}
63
64/// Contact information
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct Contact {
67    #[serde(skip_serializing_if = "Option::is_none")]
68    pub name: Option<String>,
69    #[serde(skip_serializing_if = "Option::is_none")]
70    pub url: Option<String>,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub email: Option<String>,
73}
74
75/// License information
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct License {
78    pub name: String,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub url: Option<String>,
81}
82
83/// Server configuration
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct Server {
86    /// Server URL
87    pub url: String,
88
89    /// Server description
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub description: Option<String>,
92
93    /// Variable substitutions for server URL
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub variables: Option<HashMap<String, ServerVariable>>,
96}
97
98/// Server URL variable
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct ServerVariable {
101    /// Default value
102    pub default: String,
103
104    /// Allowed values
105    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
106    pub enum_values: Option<Vec<String>>,
107
108    /// Description
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub description: Option<String>,
111}
112
113/// Path item containing operations for a specific path
114#[derive(Debug, Clone, Serialize, Deserialize, Default)]
115pub struct PathItem {
116    /// Optional summary
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub summary: Option<String>,
119
120    /// Optional description
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub description: Option<String>,
123
124    /// GET operation
125    #[serde(skip_serializing_if = "Option::is_none")]
126    pub get: Option<Operation>,
127
128    /// PUT operation
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub put: Option<Operation>,
131
132    /// POST operation
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub post: Option<Operation>,
135
136    /// DELETE operation
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub delete: Option<Operation>,
139
140    /// OPTIONS operation
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub options: Option<Operation>,
143
144    /// HEAD operation
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub head: Option<Operation>,
147
148    /// PATCH operation
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub patch: Option<Operation>,
151
152    /// TRACE operation
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub trace: Option<Operation>,
155
156    /// Common parameters for all operations on this path
157    #[serde(skip_serializing_if = "Vec::is_empty", default)]
158    pub parameters: Vec<Parameter>,
159}
160
161/// HTTP operation (GET, POST, etc.)
162#[derive(Debug, Clone, Serialize, Deserialize, Default)]
163pub struct Operation {
164    /// Tags for grouping
165    #[serde(skip_serializing_if = "Vec::is_empty", default)]
166    pub tags: Vec<String>,
167
168    /// Short summary
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub summary: Option<String>,
171
172    /// Long description
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub description: Option<String>,
175
176    /// External documentation
177    #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
178    pub external_docs: Option<ExternalDocumentation>,
179
180    /// Unique operation ID
181    #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")]
182    pub operation_id: Option<String>,
183
184    /// Parameters
185    #[serde(skip_serializing_if = "Vec::is_empty", default)]
186    pub parameters: Vec<Parameter>,
187
188    /// Request body
189    #[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
190    pub request_body: Option<RequestBody>,
191
192    /// Possible responses
193    #[serde(default)]
194    pub responses: HashMap<String, Response>,
195
196    /// Security requirements
197    #[serde(skip_serializing_if = "Vec::is_empty", default)]
198    pub security: Vec<SecurityRequirement>,
199
200    /// Servers specific to this operation
201    #[serde(skip_serializing_if = "Vec::is_empty", default)]
202    pub servers: Vec<Server>,
203
204    /// Deprecated flag
205    #[serde(skip_serializing_if = "Option::is_none")]
206    pub deprecated: Option<bool>,
207}
208
209/// Parameter for operations
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct Parameter {
212    /// Parameter name
213    pub name: String,
214
215    /// Parameter location (query, header, path, cookie)
216    #[serde(rename = "in")]
217    pub location: String,
218
219    /// Parameter description
220    #[serde(skip_serializing_if = "Option::is_none")]
221    pub description: Option<String>,
222
223    /// Required flag
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub required: Option<bool>,
226
227    /// Deprecated flag
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub deprecated: Option<bool>,
230
231    /// Schema defining the parameter
232    #[serde(skip_serializing_if = "Option::is_none")]
233    pub schema: Option<Schema>,
234
235    /// Example value
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub example: Option<serde_json::Value>,
238}
239
240/// Request body specification
241#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct RequestBody {
243    /// Description
244    #[serde(skip_serializing_if = "Option::is_none")]
245    pub description: Option<String>,
246
247    /// Media type content
248    pub content: HashMap<String, MediaType>,
249
250    /// Required flag
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub required: Option<bool>,
253}
254
255/// Response specification
256#[derive(Debug, Clone, Serialize, Deserialize)]
257pub struct Response {
258    /// Description
259    pub description: String,
260
261    /// Headers
262    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
263    pub headers: HashMap<String, Header>,
264
265    /// Content
266    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
267    pub content: HashMap<String, MediaType>,
268
269    /// Links to other operations
270    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
271    pub links: HashMap<String, Link>,
272}
273
274/// Header specification
275#[derive(Debug, Clone, Serialize, Deserialize)]
276pub struct Header {
277    #[serde(skip_serializing_if = "Option::is_none")]
278    pub description: Option<String>,
279    #[serde(skip_serializing_if = "Option::is_none")]
280    pub required: Option<bool>,
281    #[serde(skip_serializing_if = "Option::is_none")]
282    pub deprecated: Option<bool>,
283    #[serde(skip_serializing_if = "Option::is_none")]
284    pub schema: Option<Schema>,
285}
286
287/// Media type specification
288#[derive(Debug, Clone, Serialize, Deserialize)]
289pub struct MediaType {
290    /// Schema
291    #[serde(skip_serializing_if = "Option::is_none")]
292    pub schema: Option<Schema>,
293
294    /// Example value
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub example: Option<serde_json::Value>,
297
298    /// Multiple examples
299    #[serde(skip_serializing_if = "HashMap::is_empty")]
300    pub examples: HashMap<String, Example>,
301}
302
303/// Example specification
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct Example {
306    #[serde(skip_serializing_if = "Option::is_none")]
307    pub summary: Option<String>,
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub description: Option<String>,
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub value: Option<serde_json::Value>,
312    #[serde(rename = "externalValue", skip_serializing_if = "Option::is_none")]
313    pub external_value: Option<String>,
314}
315
316/// Link specification
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct Link {
319    #[serde(rename = "operationRef", skip_serializing_if = "Option::is_none")]
320    pub operation_ref: Option<String>,
321    #[serde(rename = "operationId", skip_serializing_if = "Option::is_none")]
322    pub operation_id: Option<String>,
323    #[serde(skip_serializing_if = "HashMap::is_empty")]
324    pub parameters: HashMap<String, serde_json::Value>,
325    #[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
326    pub request_body: Option<serde_json::Value>,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub description: Option<String>,
329}
330
331/// Schema for data types
332#[derive(Debug, Clone, Serialize, Deserialize, Default)]
333pub struct Schema {
334    /// Schema title
335    #[serde(skip_serializing_if = "Option::is_none")]
336    pub title: Option<String>,
337
338    /// Data type
339    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
340    pub schema_type: Option<String>,
341
342    /// Format specifier
343    #[serde(skip_serializing_if = "Option::is_none")]
344    pub format: Option<String>,
345
346    /// Description
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub description: Option<String>,
349
350    /// Default value
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub default: Option<serde_json::Value>,
353
354    /// Example value
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub example: Option<serde_json::Value>,
357
358    /// Nullable flag
359    #[serde(skip_serializing_if = "Option::is_none")]
360    pub nullable: Option<bool>,
361
362    /// Properties for object types
363    #[serde(skip_serializing_if = "HashMap::is_empty")]
364    pub properties: HashMap<String, Schema>,
365
366    /// Required properties
367    #[serde(skip_serializing_if = "Vec::is_empty")]
368    pub required: Vec<String>,
369
370    /// Additional properties schema
371    #[serde(
372        rename = "additionalProperties",
373        skip_serializing_if = "Option::is_none"
374    )]
375    pub additional_properties: Option<Box<Schema>>,
376
377    /// Items schema for arrays
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub items: Option<Box<Schema>>,
380
381    /// Enum values
382    #[serde(rename = "enum", skip_serializing_if = "Vec::is_empty")]
383    pub enum_values: Vec<serde_json::Value>,
384
385    /// Reference to another schema
386    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
387    pub reference: Option<String>,
388
389    /// AllOf composition
390    #[serde(rename = "allOf", skip_serializing_if = "Vec::is_empty")]
391    pub all_of: Vec<Schema>,
392
393    /// AnyOf composition
394    #[serde(rename = "anyOf", skip_serializing_if = "Vec::is_empty")]
395    pub any_of: Vec<Schema>,
396
397    /// OneOf composition
398    #[serde(rename = "oneOf", skip_serializing_if = "Vec::is_empty")]
399    pub one_of: Vec<Schema>,
400
401    /// Validation: minimum value
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub minimum: Option<f64>,
404
405    /// Validation: maximum value
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub maximum: Option<f64>,
408
409    /// Validation: minimum length
410    #[serde(rename = "minLength", skip_serializing_if = "Option::is_none")]
411    pub min_length: Option<usize>,
412
413    /// Validation: maximum length
414    #[serde(rename = "maxLength", skip_serializing_if = "Option::is_none")]
415    pub max_length: Option<usize>,
416
417    /// Validation: pattern
418    #[serde(skip_serializing_if = "Option::is_none")]
419    pub pattern: Option<String>,
420}
421
422/// Reusable components
423#[derive(Debug, Clone, Serialize, Deserialize, Default)]
424pub struct Components {
425    /// Reusable schemas
426    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
427    pub schemas: HashMap<String, Schema>,
428
429    /// Reusable responses
430    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
431    pub responses: HashMap<String, Response>,
432
433    /// Reusable parameters
434    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
435    pub parameters: HashMap<String, Parameter>,
436
437    /// Reusable examples
438    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
439    pub examples: HashMap<String, Example>,
440
441    /// Reusable request bodies
442    #[serde(
443        rename = "requestBodies",
444        skip_serializing_if = "HashMap::is_empty",
445        default
446    )]
447    pub request_bodies: HashMap<String, RequestBody>,
448
449    /// Reusable headers
450    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
451    pub headers: HashMap<String, Header>,
452
453    /// Security schemes
454    #[serde(
455        rename = "securitySchemes",
456        skip_serializing_if = "HashMap::is_empty",
457        default
458    )]
459    pub security_schemes: HashMap<String, SecurityScheme>,
460
461    /// Reusable links
462    #[serde(skip_serializing_if = "HashMap::is_empty", default)]
463    pub links: HashMap<String, Link>,
464}
465
466/// Security scheme
467#[derive(Debug, Clone, Serialize, Deserialize)]
468#[serde(tag = "type")]
469pub enum SecurityScheme {
470    #[serde(rename = "apiKey")]
471    ApiKey {
472        name: String,
473        #[serde(rename = "in")]
474        location: String,
475    },
476    #[serde(rename = "http")]
477    Http {
478        scheme: String,
479        #[serde(rename = "bearerFormat", skip_serializing_if = "Option::is_none")]
480        bearer_format: Option<String>,
481    },
482    #[serde(rename = "oauth2")]
483    OAuth2 { flows: OAuth2Flows },
484    #[serde(rename = "openIdConnect")]
485    OpenIdConnect {
486        #[serde(rename = "openIdConnectUrl")]
487        open_id_connect_url: String,
488    },
489}
490
491/// OAuth2 flows
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct OAuth2Flows {
494    #[serde(rename = "implicit", skip_serializing_if = "Option::is_none")]
495    pub implicit: Option<OAuth2Flow>,
496    #[serde(rename = "password", skip_serializing_if = "Option::is_none")]
497    pub password: Option<OAuth2Flow>,
498    #[serde(rename = "clientCredentials", skip_serializing_if = "Option::is_none")]
499    pub client_credentials: Option<OAuth2Flow>,
500    #[serde(rename = "authorizationCode", skip_serializing_if = "Option::is_none")]
501    pub authorization_code: Option<OAuth2Flow>,
502}
503
504/// OAuth2 flow
505#[derive(Debug, Clone, Serialize, Deserialize)]
506pub struct OAuth2Flow {
507    #[serde(rename = "authorizationUrl", skip_serializing_if = "Option::is_none")]
508    pub authorization_url: Option<String>,
509    #[serde(rename = "tokenUrl", skip_serializing_if = "Option::is_none")]
510    pub token_url: Option<String>,
511    #[serde(rename = "refreshUrl", skip_serializing_if = "Option::is_none")]
512    pub refresh_url: Option<String>,
513    pub scopes: HashMap<String, String>,
514}
515
516/// Security requirement
517pub type SecurityRequirement = HashMap<String, Vec<String>>;
518
519/// Tag for grouping operations
520#[derive(Debug, Clone, Serialize, Deserialize)]
521pub struct Tag {
522    pub name: String,
523    #[serde(skip_serializing_if = "Option::is_none")]
524    pub description: Option<String>,
525    #[serde(rename = "externalDocs", skip_serializing_if = "Option::is_none")]
526    pub external_docs: Option<ExternalDocumentation>,
527}
528
529/// External documentation
530#[derive(Debug, Clone, Serialize, Deserialize)]
531pub struct ExternalDocumentation {
532    pub url: String,
533    #[serde(skip_serializing_if = "Option::is_none")]
534    pub description: Option<String>,
535}
536
537impl OpenApiSpec {
538    /// Create a new OpenAPI specification
539    pub fn new(title: &str, version: &str) -> Self {
540        Self {
541            openapi: "3.0.3".to_string(),
542            info: ApiInfo {
543                title: title.to_string(),
544                description: None,
545                terms_of_service: None,
546                contact: None,
547                license: None,
548                version: version.to_string(),
549            },
550            servers: Vec::new(),
551            paths: HashMap::new(),
552            components: None,
553            security: Vec::new(),
554            tags: Vec::new(),
555            external_docs: None,
556        }
557    }
558}