Skip to main content

vimanam/
models.rs

1use indexmap::{IndexMap, IndexSet};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5/// OpenAPI spec model with flexibility for both 2.0 and 3.0 formats
6#[derive(Debug, Deserialize, Serialize)]
7pub(crate) struct OpenApiSpec {
8    // Support both "swagger" (2.0) and "openapi" (3.0+) version identifiers
9    #[serde(rename = "swagger", alias = "openapi", default)]
10    pub spec_version: Option<String>,
11
12    pub info: Info,
13
14    // Tags are optional
15    pub tags: Option<Vec<Tag>>,
16
17    // Paths are mandatory; IndexMap preserves spec order for deterministic output
18    pub paths: IndexMap<String, PathItem>,
19
20    // Optional servers field (OpenAPI 3.0+)
21    pub servers: Option<Vec<Server>>,
22
23    // Optional components field (OpenAPI 3.0+)
24    pub components: Option<Components>,
25
26    // Optional security field
27    pub security: Option<Vec<HashMap<String, Vec<String>>>>,
28
29    // Capture all other fields we don't explicitly model
30    #[serde(flatten)]
31    pub extensions: HashMap<String, serde_json::Value>,
32}
33/// Policy for extra properties of an object schema.
34#[derive(Debug, Deserialize, Serialize, Clone)]
35#[serde(untagged)]
36pub enum AdditionalProperties {
37    /// Permit or forbid extra object properties.
38    Bool(bool),
39    /// Constrain extra object properties with a schema.
40    Schema(Box<Schema>),
41}
42
43/// Deserializes an OpenAPI `type` as either a string (2.0/3.0) or an array of
44/// strings (3.1, e.g. `["string", "null"]`), preserving all non-`"null"` types
45/// as a pipe-separated string so union schemas are represented in full.
46fn 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    // A path item may itself be a `$ref` into `components/pathItems`; capture it
105    // so the parser can resolve it instead of silently dropping the operations.
106    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
107    pub reference: Option<String>,
108}
109
110impl PathItem {
111    /// The eight HTTP operations paired with their lowercase method name, in a
112    /// stable order. Centralizes the method list so callers don't repeat it.
113    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    // Defaulted so an operation missing `responses` doesn't fail the whole parse.
138    #[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/// A parameter declaration, or a synthetic OAS3 request-body parameter.
146#[derive(Debug, Deserialize, Serialize, Clone)]
147pub struct Parameter {
148    // A parameter may be a `$ref` into `components/parameters`; capture it so the
149    // parser can resolve it. `name`/`in` default to empty so the bare `$ref` form
150    // (which omits them) still deserializes — they come from the resolved target.
151    /// A `$ref` URI into the source specification.
152    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
153    pub reference: Option<String>,
154    /// The declared name.
155    #[serde(default)]
156    pub name: String,
157    /// Optional source description.
158    pub description: Option<String>,
159    /// Parameter location (`in`) from the specification.
160    #[serde(rename = "in", default)]
161    pub parameter_in: String,
162    /// Requiredness from the source declaration.
163    pub required: Option<bool>,
164    /// Declared schema for this value.
165    pub schema: Option<Schema>,
166    // Example carriers. Real OpenAPI 3 parameters may define these directly; the
167    // parser also reuses them to ferry a request body's media-type examples into
168    // the synthetic `body` parameter so the generator can render them.
169    /// Single source example, including explicit JSON null.
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub example: Option<serde_json::Value>,
172    /// Named source examples in declaration order.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub examples: Option<IndexMap<String, Example>>,
175    /// Unrecognized source fields preserved as JSON values.
176    #[serde(flatten)]
177    pub extensions: HashMap<String, serde_json::Value>,
178}
179
180// `Default` is for constructing schemas in code (tests build partial schemas
181// via `..Schema::default()`); serde does not consult it during deserialization
182// — missing fields fall back to `Option`/empty-map defaults on their own.
183/// A source schema with property order and unknown metadata preserved.
184#[derive(Debug, Default, Deserialize, Serialize, Clone)]
185pub struct Schema {
186    /// Declared title.
187    #[serde(rename = "title", skip_serializing_if = "Option::is_none")]
188    pub title: Option<String>,
189    /// Optional source description.
190    #[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    /// Type keyword, normalized from supported OAS 3.1 type arrays.
199    pub schema_type: Option<String>,
200    /// Optional format annotation such as `int64` or `date-time`.
201    #[serde(rename = "format", skip_serializing_if = "Option::is_none")]
202    pub format: Option<String>,
203    /// A `$ref` URI into the source specification.
204    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
205    pub reference: Option<String>,
206    /// Object properties in declaration order.
207    #[serde(rename = "properties", skip_serializing_if = "Option::is_none")]
208    pub properties: Option<IndexMap<String, Schema>>,
209    /// Schema of each array item.
210    #[serde(rename = "items", skip_serializing_if = "Option::is_none")]
211    pub items: Option<Box<Schema>>,
212    /// Names of required object properties.
213    #[serde(rename = "required", skip_serializing_if = "Option::is_none")]
214    pub required: Option<Vec<String>>,
215    /// Composition members that must all match.
216    #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
217    pub all_of: Option<Vec<Schema>>,
218    /// Composition members of which exactly one must match.
219    #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
220    pub one_of: Option<Vec<Schema>>,
221    /// Composition members of which at least one must match.
222    #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
223    pub any_of: Option<Vec<Schema>>,
224    /// Allowed values in declaration order.
225    #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
226    pub enum_values: Option<Vec<serde_json::Value>>,
227    /// Whether null is explicitly permitted.
228    #[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    /// Whether extra object properties are allowed, or their schema.
235    pub additional_properties: Option<AdditionalProperties>,
236    /// Unrecognized source fields preserved as JSON values.
237    #[serde(flatten)]
238    pub extensions: HashMap<String, serde_json::Value>,
239}
240
241/// A response declaration with its schema and media types.
242#[derive(Debug, Deserialize, Serialize, Clone)]
243pub struct Response {
244    // A response may be given as a `$ref` into `components/responses`;
245    // `resolve_response_ref` resolves it during parsing.
246    /// A `$ref` URI into the source specification.
247    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
248    pub reference: Option<String>,
249    /// Optional source description.
250    pub description: Option<String>,
251    /// Declared schema for this value.
252    pub schema: Option<Schema>,
253    /// Media types in declaration order.
254    #[serde(rename = "content", skip_serializing_if = "Option::is_none")]
255    pub content: Option<IndexMap<String, MediaType>>,
256    /// Unrecognized source fields preserved as JSON values.
257    #[serde(flatten)]
258    pub extensions: HashMap<String, serde_json::Value>,
259}
260
261// Server definition for OpenAPI 3.0+
262#[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// Components definition for OpenAPI 3.0+
278#[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    // IndexMap so example references resolve to a deterministically ordered set.
284    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    // IndexMap preserves spec order so the Authentication section is deterministic
289    #[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// Example struct
298/// A named example declaration or reference.
299#[derive(Debug, Deserialize, Serialize, Clone)]
300pub struct Example {
301    // A media-type `examples` entry may be a `$ref` into `components/examples`;
302    // capture it so the generator can resolve the reference.
303    /// A `$ref` URI into the source specification.
304    #[serde(rename = "$ref", skip_serializing_if = "Option::is_none")]
305    pub reference: Option<String>,
306    /// Optional operation summary.
307    pub summary: Option<String>,
308    /// Optional source description.
309    pub description: Option<String>,
310    /// Inline example value.
311    pub value: Option<serde_json::Value>,
312    /// URI of an externally stored example.
313    #[serde(rename = "externalValue", skip_serializing_if = "Option::is_none")]
314    pub external_value: Option<String>,
315    /// Unrecognized source fields preserved as JSON values.
316    #[serde(flatten)]
317    pub extensions: HashMap<String, serde_json::Value>,
318}
319
320// RequestBody struct
321#[derive(Debug, Deserialize, Serialize, Clone)]
322pub(crate) struct RequestBody {
323    // A `requestBody` may itself be a `$ref` into `components/requestBodies`;
324    // capture it so the parser can resolve the reference before use. `content`
325    // defaults to empty so the `$ref` form (which omits it) still deserializes.
326    #[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// MediaType struct
335/// Schema, examples, and encoding for a media type.
336#[derive(Debug, Deserialize, Serialize, Clone)]
337pub struct MediaType {
338    /// Declared schema for this value.
339    pub schema: Option<Schema>,
340    /// Single source example, including explicit JSON null.
341    pub example: Option<serde_json::Value>,
342    // IndexMap preserves spec order so rendered examples are deterministic.
343    /// Named source examples in declaration order.
344    pub examples: Option<IndexMap<String, Example>>,
345    /// Per-property encoding declarations.
346    pub encoding: Option<HashMap<String, Encoding>>,
347    /// Unrecognized source fields preserved as JSON values.
348    #[serde(flatten)]
349    pub extensions: HashMap<String, serde_json::Value>,
350}
351
352// Encoding struct
353/// Serialization metadata for one media-type property.
354#[derive(Debug, Deserialize, Serialize, Clone)]
355pub struct Encoding {
356    /// Content type for this encoded property.
357    #[serde(rename = "contentType")]
358    pub content_type: Option<String>,
359    /// Header declarations for this encoding.
360    pub headers: Option<HashMap<String, Header>>,
361    /// Serialization style declared by the spec.
362    pub style: Option<String>,
363    /// Whether arrays and objects expand into separate parameters.
364    pub explode: Option<bool>,
365    /// Whether reserved characters may remain unescaped.
366    #[serde(rename = "allowReserved")]
367    pub allow_reserved: Option<bool>,
368}
369
370// Header struct
371/// Header description and schema.
372#[derive(Debug, Deserialize, Serialize, Clone)]
373pub struct Header {
374    /// Optional source description.
375    pub description: Option<String>,
376    /// Declared schema for this value.
377    pub schema: Option<Schema>,
378}
379
380// SecurityScheme struct
381#[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// OAuthFlows struct
398#[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// OAuthFlow struct
409#[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// Link struct
421#[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// Callback struct - simplistic version
435#[derive(Debug, Deserialize, Serialize, Clone)]
436pub(crate) struct Callback {
437    // A more complete version would define this properly
438    #[serde(flatten)]
439    pub expression: HashMap<String, serde_json::Value>,
440}
441
442/// A tag-derived service used to group operations.
443#[derive(Debug, Clone)]
444pub struct Service {
445    /// The declared name.
446    pub name: String,
447    /// Optional source description.
448    pub description: Option<String>,
449}
450
451/// An operation normalized across OpenAPI versions.
452#[derive(Debug, Clone)]
453pub struct Endpoint {
454    /// Exact path template from the spec, without Swagger 2 basePath.
455    pub path: String,
456    /// Uppercase HTTP method.
457    pub method: String,
458    /// Service/tag names assigned to the operation.
459    pub services: Vec<String>, // References to service names
460    /// Optional operation summary.
461    pub summary: Option<String>,
462    /// Optional source description.
463    pub description: Option<String>,
464    /// Optional operationId exactly as declared.
465    pub operation_id: Option<String>,
466    /// Merged parameters, including synthetic OAS3 request-body parameters.
467    pub parameters: Vec<Parameter>,
468    /// Responses keyed by status code in declaration order.
469    pub responses: IndexMap<String, Response>,
470    /// Whether the operation is marked deprecated.
471    pub deprecated: bool,
472    // True when the operation declared no usable tags and the parser attributed
473    // it to the default service. `services` alone can't reveal this: the default
474    // is the first declared tag, so an untagged endpoint looks identical to one
475    // explicitly tagged with it. The spec hygiene report reads this flag.
476    /// Whether missing usable tags required attribution to a default service.
477    pub untagged: bool,
478}
479
480/// Configuration for documentation generation
481#[derive(Debug, Clone)]
482pub struct DocConfig {
483    /// How rendered operations are grouped.
484    pub group_by: GroupBy,
485    /// Optional service/tag names to include.
486    pub service_filter: Option<Vec<String>>,
487    /// Optional substring that must occur in the operation path.
488    pub path_filter: Option<String>,
489    /// Optional uppercase HTTP methods to include.
490    pub method_filter: Option<Vec<String>>,
491    // Exact operations picked with `--operation`/`--operation-id`; `None` when
492    // neither flag was given. ANDed with the other filters.
493    /// Exact operation selections; matching either set includes the operation.
494    pub operation_selector: Option<OperationSelector>,
495    /// Omit deprecated operations.
496    pub exclude_deprecated: bool,
497    /// Show only required parameters at standard/full detail.
498    pub required_only: bool,
499    /// Requested detail; token budgets may reduce it for operation reads.
500    pub detail_level: DetailLevel,
501    /// Include schemas at full detail.
502    pub include_schemas: bool,
503    // Expand every `$ref` inline at each use site instead of linking to a shared
504    // "Schema Definitions" section (the fully self-contained output).
505    /// Expand references at each use site instead of linking definitions.
506    pub inline_schemas: bool,
507    /// Maximum schema traversal edges from a root, in `0..=24`.
508    pub schema_depth: Option<usize>,
509    /// Named schemas to render independently of endpoint reachability.
510    pub schema_names: Vec<String>,
511    /// Subtree selectors in `NAME#JSON_POINTER` form, retaining ancestor metadata.
512    pub schema_fields: Vec<String>,
513    /// Source label used in retrieval commands; defaults to `spec.json`.
514    pub source_path: Option<String>,
515    /// Protobuf import roots repeated in source retrieval commands.
516    pub proto_paths: Vec<String>,
517    /// Include examples at full detail.
518    pub include_examples: bool,
519    /// Include server and authentication metadata.
520    pub include_auth: bool,
521    /// Include a table of contents.
522    pub include_toc: bool,
523    /// Order of rendered operations.
524    pub sort_method: SortMethod,
525    // When set, the generator renders at progressively lower detail until the
526    // estimated token count fits this budget (`--max-tokens`).
527    /// Approximate body budget using characters/4; explicit schema reads remain intact.
528    pub max_tokens: Option<usize>,
529    // Append the spec hygiene report after the documentation (on by default;
530    // `--no-report` turns it off).
531    /// Append hygiene in the CLI adapter; body-only Markdown rendering ignores this flag.
532    pub include_report: bool,
533}
534
535impl DocConfig {
536    /// A configuration that covers the whole spec at the richest detail level:
537    /// no filters, `--detail full --include-schemas`, default grouping and
538    /// sorting, no token budget, no hygiene report. `diff --report` uses it so
539    /// the hygiene and token deltas describe the complete documents.
540    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/// One `--operation` value: an uppercased HTTP method and a path template that
568/// must equal [`Endpoint::path`] byte for byte.
569#[derive(Debug, Clone, PartialEq, Eq, Hash)]
570pub struct OperationRef {
571    /// Uppercase HTTP method.
572    pub method: String,
573    /// Exact path template from the spec, without Swagger 2 basePath.
574    pub path: String,
575}
576
577impl OperationRef {
578    /// Whether the exact selector includes the given endpoint.
579    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/// The exact-operation selection from `--operation` and `--operation-id`. An
591/// endpoint is selected when it matches any entry of either set (a union).
592/// `IndexSet` keeps the order the values were given, so the unmatched-selector
593/// error lists them deterministically.
594#[derive(Debug, Clone, Default)]
595pub struct OperationSelector {
596    /// Exact method/path selections in insertion order.
597    pub operations: IndexSet<OperationRef>,
598    /// Case-sensitive operationId selections in insertion order.
599    pub operation_ids: IndexSet<String>,
600}
601
602impl OperationSelector {
603    /// Whether the exact selector includes the given endpoint.
604    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/// Grouping strategy for Markdown operation context.
614#[derive(Debug, Clone, PartialEq)]
615pub enum GroupBy {
616    /// Group by tag-derived service.
617    Service,
618    /// Group by HTTP method.
619    Method,
620    /// Group by path hierarchy.
621    Path,
622    /// Render a flat operation list.
623    Flat,
624}
625
626/// Amount of information included in operation context.
627#[derive(Debug, Clone, PartialEq)]
628pub enum DetailLevel {
629    /// Render only operation navigation.
630    Summary,
631    /// Render operation headings and summaries.
632    Basic,
633    /// Also render descriptions and parameter tables.
634    Standard,
635    /// Render full detail, including enabled schemas and examples.
636    Full,
637}
638
639/// Operation ordering within a rendered group.
640#[derive(Debug, Clone, PartialEq)]
641pub enum SortMethod {
642    /// Sort lexically by operation path.
643    Alphabetical,
644    /// Sort by path length.
645    PathLength,
646    /// Preserve spec declaration order.
647    None,
648}
649
650/// Intermediate representation for documentation generation
651#[derive(Debug)]
652pub struct ApiDocumentation {
653    /// Declared title.
654    pub title: String,
655    /// API version declared by the source spec.
656    pub version: String,
657    /// Optional source description.
658    pub description: Option<String>,
659    /// Declared and inferred services in source order.
660    pub services: Vec<Service>,
661    /// Operations in source order.
662    pub endpoints: Vec<Endpoint>,
663    /// Resolved server URLs.
664    pub servers: Vec<String>,
665    /// Authentication scheme descriptions keyed by name.
666    pub security_schemes: IndexMap<String, String>,
667    /// Reusable schemas keyed by name in declaration order.
668    pub schemas: IndexMap<String, Schema>,
669    // Reusable examples (`components/examples`), keyed by name for `$ref` lookups.
670    /// Named source examples in declaration order.
671    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}