1use indexmap::{IndexMap, IndexSet};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Deserialize, Serialize)]
7pub(crate) struct OpenApiSpec {
8 #[serde(rename = "swagger", alias = "openapi", default)]
10 pub spec_version: Option<String>,
11
12 pub info: Info,
13
14 pub tags: Option<Vec<Tag>>,
16
17 pub paths: IndexMap<String, PathItem>,
19
20 pub servers: Option<Vec<Server>>,
22
23 pub components: Option<Components>,
25
26 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
28
29 #[serde(flatten)]
31 pub extensions: HashMap<String, serde_json::Value>,
32}
33#[derive(Debug, Deserialize, Serialize, Clone)]
35#[serde(untagged)]
36pub enum AdditionalProperties {
37 Bool(bool),
39 Schema(Box<Schema>),
41}
42
43fn deserialize_optional_type<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
47where
48 D: serde::Deserializer<'de>,
49{
50 #[derive(Deserialize)]
51 #[serde(untagged)]
52 enum TypeField {
53 Single(String),
54 Multi(Vec<String>),
55 }
56
57 Ok(match Option::<TypeField>::deserialize(deserializer)? {
58 None => None,
59 Some(TypeField::Single(s)) => Some(s),
60 Some(TypeField::Multi(types)) => {
61 let non_null_types: Vec<String> = types.into_iter().filter(|t| t != "null").collect();
62 if non_null_types.is_empty() {
63 None
64 } else {
65 Some(non_null_types.join(" | "))
66 }
67 }
68 })
69}
70
71#[derive(Debug, Deserialize, Serialize, Clone)]
72pub(crate) struct Info {
73 pub title: String,
74 pub version: String,
75 pub description: Option<String>,
76}
77
78#[derive(Debug, Deserialize, Serialize, Clone)]
79pub(crate) struct Tag {
80 pub name: String,
81 pub description: Option<String>,
82}
83
84#[derive(Debug, Deserialize, Serialize, Clone)]
85pub(crate) struct PathItem {
86 #[serde(rename = "get", skip_serializing_if = "Option::is_none")]
87 pub get: Option<Operation>,
88 #[serde(rename = "put", skip_serializing_if = "Option::is_none")]
89 pub put: Option<Operation>,
90 #[serde(rename = "post", skip_serializing_if = "Option::is_none")]
91 pub post: Option<Operation>,
92 #[serde(rename = "delete", skip_serializing_if = "Option::is_none")]
93 pub delete: Option<Operation>,
94 #[serde(rename = "options", skip_serializing_if = "Option::is_none")]
95 pub options: Option<Operation>,
96 #[serde(rename = "head", skip_serializing_if = "Option::is_none")]
97 pub head: Option<Operation>,
98 #[serde(rename = "patch", skip_serializing_if = "Option::is_none")]
99 pub patch: Option<Operation>,
100 #[serde(rename = "trace", skip_serializing_if = "Option::is_none")]
101 pub trace: Option<Operation>,
102 #[serde(rename = "parameters", skip_serializing_if = "Option::is_none")]
103 pub parameters: Option<Vec<Parameter>>,
104 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
107 pub reference: Option<String>,
108}
109
110impl PathItem {
111 pub fn operations(&self) -> [(&'static str, &Option<Operation>); 8] {
114 [
115 ("get", &self.get),
116 ("post", &self.post),
117 ("put", &self.put),
118 ("delete", &self.delete),
119 ("options", &self.options),
120 ("head", &self.head),
121 ("patch", &self.patch),
122 ("trace", &self.trace),
123 ]
124 }
125}
126
127#[derive(Debug, Deserialize, Serialize, Clone)]
128pub(crate) struct Operation {
129 pub tags: Option<Vec<String>>,
130 pub summary: Option<String>,
131 pub description: Option<String>,
132 #[serde(rename = "operationId")]
133 pub operation_id: Option<String>,
134 pub parameters: Option<Vec<Parameter>>,
135 #[serde(rename = "requestBody", skip_serializing_if = "Option::is_none")]
136 pub request_body: Option<RequestBody>,
137 #[serde(default)]
139 pub responses: IndexMap<String, Response>,
140 pub deprecated: Option<bool>,
141 #[serde(rename = "security", skip_serializing_if = "Option::is_none")]
142 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
143}
144
145#[derive(Debug, Deserialize, Serialize, Clone)]
147pub struct Parameter {
148 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
153 pub reference: Option<String>,
154 #[serde(default)]
156 pub name: String,
157 pub description: Option<String>,
159 #[serde(rename = "in", default)]
161 pub parameter_in: String,
162 pub required: Option<bool>,
164 pub schema: Option<Schema>,
166 #[serde(skip_serializing_if = "Option::is_none")]
171 pub example: Option<serde_json::Value>,
172 #[serde(skip_serializing_if = "Option::is_none")]
174 pub examples: Option<IndexMap<String, Example>>,
175 #[serde(flatten)]
177 pub extensions: HashMap<String, serde_json::Value>,
178}
179
180#[derive(Debug, Default, Deserialize, Serialize, Clone)]
185pub struct Schema {
186 #[serde(rename = "title", skip_serializing_if = "Option::is_none")]
188 pub title: Option<String>,
189 #[serde(rename = "description", skip_serializing_if = "Option::is_none")]
191 pub description: Option<String>,
192 #[serde(
193 rename = "type",
194 default,
195 deserialize_with = "deserialize_optional_type",
196 skip_serializing_if = "Option::is_none"
197 )]
198 pub schema_type: Option<String>,
200 #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
202 pub format: Option<String>,
203 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
205 pub reference: Option<String>,
206 #[serde(rename = "properties", skip_serializing_if = "Option::is_none")]
208 pub properties: Option<IndexMap<String, Schema>>,
209 #[serde(rename = "items", skip_serializing_if = "Option::is_none")]
211 pub items: Option<Box<Schema>>,
212 #[serde(rename = "required", skip_serializing_if = "Option::is_none")]
214 pub required: Option<Vec<String>>,
215 #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
217 pub all_of: Option<Vec<Schema>>,
218 #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
220 pub one_of: Option<Vec<Schema>>,
221 #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
223 pub any_of: Option<Vec<Schema>>,
224 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
226 pub enum_values: Option<Vec<serde_json::Value>>,
227 #[serde(rename = "nullable", skip_serializing_if = "Option::is_none")]
229 pub nullable: Option<bool>,
230 #[serde(
231 rename = "additionalProperties",
232 skip_serializing_if = "Option::is_none"
233 )]
234 pub additional_properties: Option<AdditionalProperties>,
236 #[serde(flatten)]
238 pub extensions: HashMap<String, serde_json::Value>,
239}
240
241#[derive(Debug, Deserialize, Serialize, Clone)]
243pub struct Response {
244 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
248 pub reference: Option<String>,
249 pub description: Option<String>,
251 pub schema: Option<Schema>,
253 #[serde(rename = "content", skip_serializing_if = "Option::is_none")]
255 pub content: Option<IndexMap<String, MediaType>>,
256 #[serde(flatten)]
258 pub extensions: HashMap<String, serde_json::Value>,
259}
260
261#[derive(Debug, Deserialize, Serialize, Clone)]
263pub(crate) struct Server {
264 pub url: String,
265 pub description: Option<String>,
266 pub variables: Option<HashMap<String, ServerVariable>>,
267}
268
269#[derive(Debug, Deserialize, Serialize, Clone)]
270pub(crate) struct ServerVariable {
271 #[serde(rename = "enum")]
272 pub enum_values: Option<Vec<String>>,
273 pub default: String,
274 pub description: Option<String>,
275}
276
277#[derive(Debug, Deserialize, Serialize, Clone)]
279pub(crate) struct Components {
280 pub schemas: Option<IndexMap<String, Schema>>,
281 pub responses: Option<HashMap<String, Response>>,
282 pub parameters: Option<HashMap<String, Parameter>>,
283 pub examples: Option<IndexMap<String, Example>>,
285 #[serde(rename = "requestBodies")]
286 pub request_bodies: Option<HashMap<String, RequestBody>>,
287 pub headers: Option<HashMap<String, Header>>,
288 #[serde(rename = "securitySchemes")]
290 pub security_schemes: Option<IndexMap<String, SecurityScheme>>,
291 pub links: Option<HashMap<String, Link>>,
292 pub callbacks: Option<HashMap<String, Callback>>,
293 #[serde(flatten)]
294 pub extensions: HashMap<String, serde_json::Value>,
295}
296
297#[derive(Debug, Deserialize, Serialize, Clone)]
300pub struct Example {
301 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
305 pub reference: Option<String>,
306 pub summary: Option<String>,
308 pub description: Option<String>,
310 pub value: Option<serde_json::Value>,
312 #[serde(rename = "externalValue", skip_serializing_if = "Option::is_none")]
314 pub external_value: Option<String>,
315 #[serde(flatten)]
317 pub extensions: HashMap<String, serde_json::Value>,
318}
319
320#[derive(Debug, Deserialize, Serialize, Clone)]
322pub(crate) struct RequestBody {
323 #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
327 pub reference: Option<String>,
328 pub description: Option<String>,
329 #[serde(default)]
330 pub content: IndexMap<String, MediaType>,
331 pub required: Option<bool>,
332}
333
334#[derive(Debug, Deserialize, Serialize, Clone)]
337pub struct MediaType {
338 pub schema: Option<Schema>,
340 pub example: Option<serde_json::Value>,
342 pub examples: Option<IndexMap<String, Example>>,
345 pub encoding: Option<HashMap<String, Encoding>>,
347 #[serde(flatten)]
349 pub extensions: HashMap<String, serde_json::Value>,
350}
351
352#[derive(Debug, Deserialize, Serialize, Clone)]
355pub struct Encoding {
356 #[serde(rename = "contentType")]
358 pub content_type: Option<String>,
359 pub headers: Option<HashMap<String, Header>>,
361 pub style: Option<String>,
363 pub explode: Option<bool>,
365 #[serde(rename = "allowReserved")]
367 pub allow_reserved: Option<bool>,
368}
369
370#[derive(Debug, Deserialize, Serialize, Clone)]
373pub struct Header {
374 pub description: Option<String>,
376 pub schema: Option<Schema>,
378}
379
380#[derive(Debug, Deserialize, Serialize, Clone)]
382pub(crate) struct SecurityScheme {
383 #[serde(rename = "type")]
384 pub security_type: String,
385 pub description: Option<String>,
386 pub name: Option<String>,
387 #[serde(rename = "in")]
388 pub location: Option<String>,
389 pub scheme: Option<String>,
390 #[serde(rename = "bearerFormat")]
391 pub bearer_format: Option<String>,
392 pub flows: Option<OAuthFlows>,
393 #[serde(rename = "openIdConnectUrl")]
394 pub open_id_connect_url: Option<String>,
395}
396
397#[derive(Debug, Deserialize, Serialize, Clone)]
399pub(crate) struct OAuthFlows {
400 pub implicit: Option<OAuthFlow>,
401 pub password: Option<OAuthFlow>,
402 #[serde(rename = "clientCredentials")]
403 pub client_credentials: Option<OAuthFlow>,
404 #[serde(rename = "authorizationCode")]
405 pub authorization_code: Option<OAuthFlow>,
406}
407
408#[derive(Debug, Deserialize, Serialize, Clone)]
410pub(crate) struct OAuthFlow {
411 #[serde(rename = "authorizationUrl")]
412 pub authorization_url: Option<String>,
413 #[serde(rename = "tokenUrl")]
414 pub token_url: Option<String>,
415 #[serde(rename = "refreshUrl")]
416 pub refresh_url: Option<String>,
417 pub scopes: HashMap<String, String>,
418}
419
420#[derive(Debug, Deserialize, Serialize, Clone)]
422pub(crate) struct Link {
423 #[serde(rename = "operationRef")]
424 pub operation_ref: Option<String>,
425 #[serde(rename = "operationId")]
426 pub operation_id: Option<String>,
427 pub parameters: Option<HashMap<String, serde_json::Value>>,
428 #[serde(rename = "requestBody")]
429 pub request_body: Option<serde_json::Value>,
430 pub description: Option<String>,
431 pub server: Option<Server>,
432}
433
434#[derive(Debug, Deserialize, Serialize, Clone)]
436pub(crate) struct Callback {
437 #[serde(flatten)]
439 pub expression: HashMap<String, serde_json::Value>,
440}
441
442#[derive(Debug, Clone)]
444pub struct Service {
445 pub name: String,
447 pub description: Option<String>,
449}
450
451#[derive(Debug, Clone)]
453pub struct Endpoint {
454 pub path: String,
456 pub method: String,
458 pub services: Vec<String>, pub summary: Option<String>,
462 pub description: Option<String>,
464 pub operation_id: Option<String>,
466 pub parameters: Vec<Parameter>,
468 pub responses: IndexMap<String, Response>,
470 pub deprecated: bool,
472 pub untagged: bool,
478}
479
480#[derive(Debug, Clone)]
482pub struct DocConfig {
483 pub group_by: GroupBy,
485 pub service_filter: Option<Vec<String>>,
487 pub path_filter: Option<String>,
489 pub method_filter: Option<Vec<String>>,
491 pub operation_selector: Option<OperationSelector>,
495 pub exclude_deprecated: bool,
497 pub required_only: bool,
499 pub detail_level: DetailLevel,
501 pub include_schemas: bool,
503 pub inline_schemas: bool,
507 pub schema_depth: Option<usize>,
509 pub schema_names: Vec<String>,
511 pub schema_fields: Vec<String>,
513 pub source_path: Option<String>,
515 pub proto_paths: Vec<String>,
517 pub include_examples: bool,
519 pub include_auth: bool,
521 pub include_toc: bool,
523 pub sort_method: SortMethod,
525 pub max_tokens: Option<usize>,
529 pub include_report: bool,
533}
534
535impl DocConfig {
536 pub fn unfiltered() -> Self {
541 Self {
542 group_by: GroupBy::Service,
543 service_filter: None,
544 path_filter: None,
545 method_filter: None,
546 operation_selector: None,
547 exclude_deprecated: false,
548 required_only: false,
549 detail_level: DetailLevel::Full,
550 include_schemas: true,
551 inline_schemas: false,
552 schema_depth: None,
553 schema_names: Vec::new(),
554 schema_fields: Vec::new(),
555 source_path: None,
556 proto_paths: Vec::new(),
557 include_examples: false,
558 include_auth: false,
559 include_toc: true,
560 sort_method: SortMethod::Alphabetical,
561 max_tokens: None,
562 include_report: false,
563 }
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Eq, Hash)]
570pub struct OperationRef {
571 pub method: String,
573 pub path: String,
575}
576
577impl OperationRef {
578 pub fn matches(&self, endpoint: &Endpoint) -> bool {
580 endpoint.method == self.method && endpoint.path == self.path
581 }
582}
583
584impl std::fmt::Display for OperationRef {
585 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
586 write!(f, "{} {}", self.method, self.path)
587 }
588}
589
590#[derive(Debug, Clone, Default)]
595pub struct OperationSelector {
596 pub operations: IndexSet<OperationRef>,
598 pub operation_ids: IndexSet<String>,
600}
601
602impl OperationSelector {
603 pub fn matches(&self, endpoint: &Endpoint) -> bool {
605 self.operations.iter().any(|op| op.matches(endpoint))
606 || endpoint
607 .operation_id
608 .as_ref()
609 .is_some_and(|id| self.operation_ids.contains(id))
610 }
611}
612
613#[derive(Debug, Clone, PartialEq)]
615pub enum GroupBy {
616 Service,
618 Method,
620 Path,
622 Flat,
624}
625
626#[derive(Debug, Clone, PartialEq)]
628pub enum DetailLevel {
629 Summary,
631 Basic,
633 Standard,
635 Full,
637}
638
639#[derive(Debug, Clone, PartialEq)]
641pub enum SortMethod {
642 Alphabetical,
644 PathLength,
646 None,
648}
649
650#[derive(Debug)]
652pub struct ApiDocumentation {
653 pub title: String,
655 pub version: String,
657 pub description: Option<String>,
659 pub services: Vec<Service>,
661 pub endpoints: Vec<Endpoint>,
663 pub servers: Vec<String>,
665 pub security_schemes: IndexMap<String, String>,
667 pub schemas: IndexMap<String, Schema>,
669 pub examples: IndexMap<String, Example>,
672}
673
674#[derive(Debug, Clone, Copy, PartialEq, Eq)]
675#[cfg_attr(not(feature = "cli"), allow(dead_code))]
676pub(crate) enum SplitMode {
677 Service,
678 Tag,
679 Endpoint,
680}