Skip to main content

sz_orm_swagger/
lib.rs

1//! # SZ-ORM Swagger — OpenAPI/Swagger specification generation
2//!
3//! Provides building and serialization of OpenAPI 3.0 specifications, supporting paths,
4//! methods, parameters, responses, Schema definitions, SecurityScheme, Tags, Servers, and
5//! example generation; the output can be consumed directly by tools such as Swagger UI.
6//!
7//! ## Main types
8//!
9//! - [`OpenAPISpec`] — specification root object
10//! - [`PathInfo`] — single (path, method) operation description
11//! - [`Schema`] / [`SchemaRef`] / [`ObjectType`] / [`ArrayType`] — Schema definitions
12//! - [`SecurityScheme`] — authentication schemes (Basic/Bearer/ApiKey/OAuth2)
13//! - [`Tag`] — interface grouping tag
14//! - [`Server`] — environment server configuration
15//! - [`ExampleBuilder`] — generates examples based on Schema
16
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20// ============================================================================
21// 根对象 OpenAPISpec
22// ============================================================================
23
24/// OpenAPI 3.0 specification root object
25///
26/// The complete structure contains paths, components (schemas/securitySchemes), tags, servers, info.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct OpenAPISpec {
29    pub openapi: String,
30    pub info: serde_json::Value,
31    pub paths: HashMap<String, serde_json::Value>,
32    /// Components table (schemas, securitySchemes, etc.)
33    #[serde(default, skip_serializing_if = "Option::is_none")]
34    pub components: Option<Components>,
35    /// Interface grouping tag list
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub tags: Vec<Tag>,
38    /// Environment server list
39    #[serde(default, skip_serializing_if = "Vec::is_empty")]
40    pub servers: Vec<Server>,
41    /// Global security requirements
42    #[serde(default, skip_serializing_if = "Vec::is_empty")]
43    pub security: Vec<SecurityRequirement>,
44}
45
46impl OpenAPISpec {
47    /// Serializes to a pretty-printed JSON string
48    pub fn to_json_string(&self) -> String {
49        serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
50    }
51}
52
53/// Components table, holds reusable Schema and SecurityScheme
54#[derive(Debug, Clone, Default, Serialize, Deserialize)]
55pub struct Components {
56    /// Reusable Schema definitions
57    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
58    pub schemas: HashMap<String, Schema>,
59    /// Reusable SecurityScheme definitions
60    #[serde(
61        rename = "securitySchemes",
62        default,
63        skip_serializing_if = "HashMap::is_empty"
64    )]
65    pub security_schemes: HashMap<String, SecurityScheme>,
66}
67
68/// Security requirement: key-value mapping of required authentication schemes
69///
70/// Example: `{"bearerAuth": []}` means the bearerAuth scheme is required
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct SecurityRequirement {
73    #[serde(flatten)]
74    pub requirements: HashMap<String, Vec<String>>,
75}
76
77impl SecurityRequirement {
78    pub fn new(scheme: &str) -> Self {
79        let mut requirements = HashMap::new();
80        requirements.insert(scheme.to_string(), vec![]);
81        Self { requirements }
82    }
83
84    pub fn with_scopes(mut self, scheme: &str, scopes: Vec<String>) -> Self {
85        self.requirements.insert(scheme.to_string(), scopes);
86        self
87    }
88}
89
90// ============================================================================
91// Path 描述
92// ============================================================================
93
94/// Describes a single (path, method) operation in an OpenAPI spec.
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct PathInfo {
97    pub method: String,
98    pub summary: String,
99    #[serde(default, skip_serializing_if = "Option::is_none")]
100    pub description: Option<String>,
101    #[serde(default, skip_serializing_if = "Vec::is_empty")]
102    pub tags: Vec<String>,
103    #[serde(default, skip_serializing_if = "Vec::is_empty")]
104    pub parameters: Vec<serde_json::Value>,
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub request_body: Option<RequestBody>,
107    pub responses: HashMap<String, serde_json::Value>,
108    #[serde(default, skip_serializing_if = "Vec::is_empty")]
109    pub security: Vec<SecurityRequirement>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub operation_id: Option<String>,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub deprecated: Option<bool>,
114}
115
116impl PathInfo {
117    pub fn new(method: &str, summary: &str) -> Self {
118        Self {
119            method: method.to_string(),
120            summary: summary.to_string(),
121            description: None,
122            tags: vec![],
123            parameters: vec![],
124            request_body: None,
125            responses: HashMap::new(),
126            security: vec![],
127            operation_id: None,
128            deprecated: None,
129        }
130    }
131
132    pub fn with_response(mut self, code: &str, desc: &str) -> Self {
133        self.responses
134            .insert(code.to_string(), serde_json::json!({ "description": desc }));
135        self
136    }
137
138    /// Adds a response with a Schema
139    pub fn with_response_schema(mut self, code: &str, desc: &str, schema_ref: &str) -> Self {
140        self.responses.insert(
141            code.to_string(),
142            serde_json::json!({
143                "description": desc,
144                "content": {
145                    "application/json": {
146                        "schema": { "$ref": schema_ref }
147                    }
148                }
149            }),
150        );
151        self
152    }
153
154    pub fn with_parameter(mut self, param: serde_json::Value) -> Self {
155        self.parameters.push(param);
156        self
157    }
158
159    /// Adds a path parameter
160    pub fn with_path_param(self, name: &str, desc: &str, required: bool) -> Self {
161        self.with_parameter(serde_json::json!({
162            "name": name,
163            "in": "path",
164            "description": desc,
165            "required": required,
166            "schema": { "type": "string" }
167        }))
168    }
169
170    /// Adds a query parameter
171    pub fn with_query_param(self, name: &str, desc: &str, required: bool) -> Self {
172        self.with_parameter(serde_json::json!({
173            "name": name,
174            "in": "query",
175            "description": desc,
176            "required": required,
177            "schema": { "type": "string" }
178        }))
179    }
180
181    /// Adds a tag
182    pub fn with_tag(mut self, tag: &str) -> Self {
183        self.tags.push(tag.to_string());
184        self
185    }
186
187    /// Sets the request body
188    pub fn with_request_body(mut self, body: RequestBody) -> Self {
189        self.request_body = Some(body);
190        self
191    }
192
193    /// Sets the request body (simplified: references a Schema)
194    pub fn with_request_body_ref(self, desc: &str, schema_ref: &str, required: bool) -> Self {
195        self.with_request_body(RequestBody::new(desc, schema_ref, required))
196    }
197
198    /// Sets the operation ID
199    pub fn with_operation_id(mut self, id: &str) -> Self {
200        self.operation_id = Some(id.to_string());
201        self
202    }
203
204    /// Sets the security requirement
205    pub fn with_security(mut self, req: SecurityRequirement) -> Self {
206        self.security.push(req);
207        self
208    }
209
210    /// Marks as deprecated
211    pub fn deprecated(mut self) -> Self {
212        self.deprecated = Some(true);
213        self
214    }
215
216    /// Sets the description
217    pub fn with_description(mut self, desc: &str) -> Self {
218        self.description = Some(desc.to_string());
219        self
220    }
221}
222
223/// Request body definition
224#[derive(Debug, Clone, Serialize, Deserialize)]
225pub struct RequestBody {
226    pub description: String,
227    pub content: HashMap<String, MediaType>,
228    pub required: bool,
229}
230
231impl RequestBody {
232    pub fn new(desc: &str, schema_ref: &str, required: bool) -> Self {
233        let mut content = HashMap::new();
234        content.insert(
235            "application/json".to_string(),
236            MediaType::with_schema_ref(schema_ref),
237        );
238        Self {
239            description: desc.to_string(),
240            content,
241            required,
242        }
243    }
244
245    pub fn with_content(mut self, content_type: &str, media: MediaType) -> Self {
246        self.content.insert(content_type.to_string(), media);
247        self
248    }
249}
250
251/// Media type (contains Schema and examples)
252#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct MediaType {
254    #[serde(default, skip_serializing_if = "Option::is_none")]
255    pub schema: Option<Schema>,
256    #[serde(default, skip_serializing_if = "Option::is_none")]
257    pub example: Option<serde_json::Value>,
258    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
259    pub examples: HashMap<String, Example>,
260}
261
262impl MediaType {
263    pub fn with_schema_ref(schema_ref: &str) -> Self {
264        Self {
265            schema: Some(Schema::ref_to(schema_ref)),
266            example: None,
267            examples: HashMap::new(),
268        }
269    }
270
271    pub fn with_schema(mut self, schema: Schema) -> Self {
272        self.schema = Some(schema);
273        self
274    }
275
276    pub fn with_example(mut self, example: serde_json::Value) -> Self {
277        self.example = Some(example);
278        self
279    }
280}
281
282/// Example (OpenAPI 3.0 example object)
283#[derive(Debug, Clone, Serialize, Deserialize)]
284pub struct Example {
285    #[serde(default, skip_serializing_if = "Option::is_none")]
286    pub summary: Option<String>,
287    #[serde(default, skip_serializing_if = "Option::is_none")]
288    pub description: Option<String>,
289    #[serde(default, skip_serializing_if = "Option::is_none")]
290    pub value: Option<serde_json::Value>,
291    #[serde(default, skip_serializing_if = "Option::is_none")]
292    pub external_value: Option<String>,
293}
294
295impl Example {
296    pub fn new(value: serde_json::Value) -> Self {
297        Self {
298            summary: None,
299            description: None,
300            value: Some(value),
301            external_value: None,
302        }
303    }
304
305    pub fn with_summary(mut self, summary: &str) -> Self {
306        self.summary = Some(summary.to_string());
307        self
308    }
309
310    pub fn with_description(mut self, desc: &str) -> Self {
311        self.description = Some(desc.to_string());
312        self
313    }
314}
315
316// ============================================================================
317// Schema 定义(SchemaRef / ObjectType / ArrayType)
318// ============================================================================
319
320/// Schema definition, supports reference, object, array, and primitive types
321///
322/// - `Ref`: references a Schema defined in components.schemas via `$ref`
323/// - `Object`: object type, contains properties and required
324/// - `Array`: array type, contains items
325/// - `Primitive`: primitive type (string/integer/number/boolean)
326#[derive(Debug, Clone, Serialize, Deserialize)]
327#[serde(untagged)]
328pub enum Schema {
329    /// References another already-defined Schema
330    Ref {
331        #[serde(rename = "$ref")]
332        ref_path: String,
333    },
334    /// Object type
335    Object(ObjectType),
336    /// Array type
337    Array(ArrayType),
338    /// Primitive type (string/integer/number/boolean)
339    Primitive(PrimitiveSchema),
340}
341
342impl Schema {
343    /// Creates a reference Schema
344    pub fn ref_to(name: &str) -> Self {
345        Self::Ref {
346            ref_path: format!("#/components/schemas/{}", name),
347        }
348    }
349
350    /// Creates an object Schema
351    pub fn object(obj: ObjectType) -> Self {
352        Self::Object(obj)
353    }
354
355    /// Creates an array Schema
356    pub fn array(arr: ArrayType) -> Self {
357        Self::Array(arr)
358    }
359
360    /// Creates a string Schema
361    pub fn string() -> Self {
362        Self::Primitive(PrimitiveSchema::string())
363    }
364
365    /// Creates an integer Schema
366    pub fn integer() -> Self {
367        Self::Primitive(PrimitiveSchema::integer())
368    }
369
370    /// Creates a number Schema
371    pub fn number() -> Self {
372        Self::Primitive(PrimitiveSchema::number())
373    }
374
375    /// Creates a boolean Schema
376    pub fn boolean() -> Self {
377        Self::Primitive(PrimitiveSchema::boolean())
378    }
379
380    /// Determines whether it is a reference type
381    pub fn is_ref(&self) -> bool {
382        matches!(self, Schema::Ref { .. })
383    }
384
385    /// Determines whether it is an object type
386    pub fn is_object(&self) -> bool {
387        matches!(self, Schema::Object(_))
388    }
389
390    /// Determines whether it is an array type
391    pub fn is_array(&self) -> bool {
392        matches!(self, Schema::Array(_))
393    }
394
395    /// Sets the format (only effective for Primitive types; other types return the original value)
396    pub fn with_format(self, format: &str) -> Self {
397        match self {
398            Self::Primitive(p) => Self::Primitive(p.with_format(format)),
399            _ => self,
400        }
401    }
402
403    /// Sets the description (only effective for Primitive types)
404    pub fn with_description(self, desc: &str) -> Self {
405        match self {
406            Self::Primitive(p) => Self::Primitive(p.with_description(desc)),
407            _ => self,
408        }
409    }
410
411    /// Sets the example value (only effective for Primitive types)
412    pub fn with_example(self, value: serde_json::Value) -> Self {
413        match self {
414            Self::Primitive(p) => Self::Primitive(p.with_example(value)),
415            _ => self,
416        }
417    }
418
419    /// Sets the default value (only effective for Primitive types)
420    pub fn with_default(self, value: serde_json::Value) -> Self {
421        match self {
422            Self::Primitive(p) => Self::Primitive(p.with_default(value)),
423            _ => self,
424        }
425    }
426}
427
428/// Object type Schema
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct ObjectType {
431    #[serde(rename = "type")]
432    pub schema_type: String,
433    /// Field definitions: name -> Schema
434    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
435    pub properties: HashMap<String, Schema>,
436    /// Required field list
437    #[serde(default, skip_serializing_if = "Vec::is_empty")]
438    pub required: Vec<String>,
439    #[serde(default, skip_serializing_if = "Option::is_none")]
440    pub description: Option<String>,
441    #[serde(default, skip_serializing_if = "Option::is_none")]
442    pub additional_properties: Option<Box<Schema>>,
443}
444
445impl ObjectType {
446    pub fn new() -> Self {
447        Self {
448            schema_type: "object".to_string(),
449            properties: HashMap::new(),
450            required: vec![],
451            description: None,
452            additional_properties: None,
453        }
454    }
455
456    /// Adds a field (optional)
457    pub fn with_property(mut self, name: &str, schema: Schema) -> Self {
458        self.properties.insert(name.to_string(), schema);
459        self
460    }
461
462    /// Adds a required field
463    pub fn with_required_property(mut self, name: &str, schema: Schema) -> Self {
464        self.properties.insert(name.to_string(), schema);
465        self.required.push(name.to_string());
466        self
467    }
468
469    /// Sets the description
470    pub fn with_description(mut self, desc: &str) -> Self {
471        self.description = Some(desc.to_string());
472        self
473    }
474
475    /// Sets additional properties (for Map types)
476    pub fn with_additional_properties(mut self, schema: Schema) -> Self {
477        self.additional_properties = Some(Box::new(schema));
478        self
479    }
480}
481
482impl Default for ObjectType {
483    fn default() -> Self {
484        Self::new()
485    }
486}
487
488/// Array type Schema
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct ArrayType {
491    #[serde(rename = "type")]
492    pub schema_type: String,
493    /// Array element Schema
494    pub items: Box<Schema>,
495    #[serde(default, skip_serializing_if = "Option::is_none")]
496    pub min_items: Option<u32>,
497    #[serde(default, skip_serializing_if = "Option::is_none")]
498    pub max_items: Option<u32>,
499    #[serde(default, skip_serializing_if = "Option::is_none")]
500    pub unique_items: Option<bool>,
501    #[serde(default, skip_serializing_if = "Option::is_none")]
502    pub description: Option<String>,
503}
504
505impl ArrayType {
506    pub fn new(items: Schema) -> Self {
507        Self {
508            schema_type: "array".to_string(),
509            items: Box::new(items),
510            min_items: None,
511            max_items: None,
512            unique_items: None,
513            description: None,
514        }
515    }
516
517    pub fn with_min_items(mut self, min: u32) -> Self {
518        self.min_items = Some(min);
519        self
520    }
521
522    pub fn with_max_items(mut self, max: u32) -> Self {
523        self.max_items = Some(max);
524        self
525    }
526
527    pub fn unique_items(mut self) -> Self {
528        self.unique_items = Some(true);
529        self
530    }
531
532    pub fn with_description(mut self, desc: &str) -> Self {
533        self.description = Some(desc.to_string());
534        self
535    }
536}
537
538/// Primitive type Schema (string/integer/number/boolean)
539#[derive(Debug, Clone, Serialize, Deserialize)]
540pub struct PrimitiveSchema {
541    #[serde(rename = "type")]
542    pub schema_type: String,
543    #[serde(default, skip_serializing_if = "Option::is_none")]
544    pub format: Option<String>,
545    #[serde(default, skip_serializing_if = "Option::is_none")]
546    pub description: Option<String>,
547    #[serde(default, skip_serializing_if = "Option::is_none")]
548    pub default: Option<serde_json::Value>,
549    #[serde(default, skip_serializing_if = "Option::is_none")]
550    pub example: Option<serde_json::Value>,
551    #[serde(default, skip_serializing_if = "Option::is_none")]
552    pub enum_values: Option<Vec<serde_json::Value>>,
553    #[serde(default, skip_serializing_if = "Option::is_none")]
554    pub minimum: Option<f64>,
555    #[serde(default, skip_serializing_if = "Option::is_none")]
556    pub maximum: Option<f64>,
557    #[serde(default, skip_serializing_if = "Option::is_none")]
558    pub min_length: Option<u32>,
559    #[serde(default, skip_serializing_if = "Option::is_none")]
560    pub max_length: Option<u32>,
561    #[serde(default, skip_serializing_if = "Option::is_none")]
562    pub pattern: Option<String>,
563}
564
565impl PrimitiveSchema {
566    pub fn string() -> Self {
567        Self {
568            schema_type: "string".to_string(),
569            format: None,
570            description: None,
571            default: None,
572            example: None,
573            enum_values: None,
574            minimum: None,
575            maximum: None,
576            min_length: None,
577            max_length: None,
578            pattern: None,
579        }
580    }
581
582    pub fn integer() -> Self {
583        Self {
584            schema_type: "integer".to_string(),
585            format: Some("int64".to_string()),
586            description: None,
587            default: None,
588            example: None,
589            enum_values: None,
590            minimum: None,
591            maximum: None,
592            min_length: None,
593            max_length: None,
594            pattern: None,
595        }
596    }
597
598    pub fn number() -> Self {
599        Self {
600            schema_type: "number".to_string(),
601            format: Some("double".to_string()),
602            description: None,
603            default: None,
604            example: None,
605            enum_values: None,
606            minimum: None,
607            maximum: None,
608            min_length: None,
609            max_length: None,
610            pattern: None,
611        }
612    }
613
614    pub fn boolean() -> Self {
615        Self {
616            schema_type: "boolean".to_string(),
617            format: None,
618            description: None,
619            default: None,
620            example: None,
621            enum_values: None,
622            minimum: None,
623            maximum: None,
624            min_length: None,
625            max_length: None,
626            pattern: None,
627        }
628    }
629
630    pub fn with_format(mut self, format: &str) -> Self {
631        self.format = Some(format.to_string());
632        self
633    }
634
635    pub fn with_description(mut self, desc: &str) -> Self {
636        self.description = Some(desc.to_string());
637        self
638    }
639
640    pub fn with_default(mut self, value: serde_json::Value) -> Self {
641        self.default = Some(value);
642        self
643    }
644
645    pub fn with_example(mut self, value: serde_json::Value) -> Self {
646        self.example = Some(value);
647        self
648    }
649
650    pub fn with_enum(mut self, values: Vec<serde_json::Value>) -> Self {
651        self.enum_values = Some(values);
652        self
653    }
654
655    pub fn with_range(mut self, min: f64, max: f64) -> Self {
656        self.minimum = Some(min);
657        self.maximum = Some(max);
658        self
659    }
660
661    pub fn with_length_range(mut self, min: u32, max: u32) -> Self {
662        self.min_length = Some(min);
663        self.max_length = Some(max);
664        self
665    }
666
667    pub fn with_pattern(mut self, pattern: &str) -> Self {
668        self.pattern = Some(pattern.to_string());
669        self
670    }
671}
672
673// ============================================================================
674// SecurityScheme 认证方案
675// ============================================================================
676
677/// Authentication scheme, supports HTTP (Basic/Bearer), ApiKey, OAuth2
678#[derive(Debug, Clone, Serialize, Deserialize)]
679#[serde(tag = "type")]
680pub enum SecurityScheme {
681    /// HTTP authentication (Basic or Bearer)
682    ///
683    /// In OpenAPI 3.0, both Basic and Bearer use `type: http`,
684    /// distinguished by the `scheme` field (`basic` or `bearer`).
685    #[serde(rename = "http")]
686    Http {
687        /// Authentication scheme: `basic` or `bearer`
688        scheme: String,
689        /// Bearer format (used only when scheme=bearer, e.g., `JWT`)
690        #[serde(
691            rename = "bearerFormat",
692            default,
693            skip_serializing_if = "Option::is_none"
694        )]
695        bearer_format: Option<String>,
696        #[serde(default, skip_serializing_if = "Option::is_none")]
697        description: Option<String>,
698    },
699    /// API Key authentication (Header / Query / Cookie)
700    #[serde(rename = "apiKey")]
701    ApiKey {
702        name: String,
703        #[serde(rename = "in")]
704        location: ApiKeyLocation,
705        #[serde(default, skip_serializing_if = "Option::is_none")]
706        description: Option<String>,
707    },
708    /// OAuth2 authentication
709    #[serde(rename = "oauth2")]
710    OAuth2 {
711        flows: Box<OAuth2Flows>,
712        #[serde(default, skip_serializing_if = "Option::is_none")]
713        description: Option<String>,
714    },
715}
716
717impl SecurityScheme {
718    /// Creates a Basic authentication scheme
719    pub fn basic() -> Self {
720        Self::Http {
721            scheme: "basic".to_string(),
722            bearer_format: None,
723            description: None,
724        }
725    }
726
727    /// Creates a Bearer Token authentication scheme
728    pub fn bearer(jwt_format: bool) -> Self {
729        Self::Http {
730            scheme: "bearer".to_string(),
731            bearer_format: if jwt_format {
732                Some("JWT".to_string())
733            } else {
734                None
735            },
736            description: None,
737        }
738    }
739
740    /// Creates an API Key authentication scheme
741    pub fn api_key(name: &str, location: ApiKeyLocation) -> Self {
742        Self::ApiKey {
743            name: name.to_string(),
744            location,
745            description: None,
746        }
747    }
748
749    /// Creates an OAuth2 authentication scheme
750    pub fn oauth2(flows: OAuth2Flows) -> Self {
751        Self::OAuth2 {
752            flows: Box::new(flows),
753            description: None,
754        }
755    }
756
757    /// Determines whether it is Basic authentication
758    pub fn is_basic(&self) -> bool {
759        matches!(self, Self::Http { scheme, .. } if scheme == "basic")
760    }
761
762    /// Determines whether it is Bearer authentication
763    pub fn is_bearer(&self) -> bool {
764        matches!(self, Self::Http { scheme, .. } if scheme == "bearer")
765    }
766
767    /// Sets the description
768    pub fn with_description(self, desc: &str) -> Self {
769        match self {
770            Self::Http {
771                scheme,
772                bearer_format,
773                ..
774            } => Self::Http {
775                scheme,
776                bearer_format,
777                description: Some(desc.to_string()),
778            },
779            Self::ApiKey { name, location, .. } => Self::ApiKey {
780                name,
781                location,
782                description: Some(desc.to_string()),
783            },
784            Self::OAuth2 { flows, .. } => Self::OAuth2 {
785                flows,
786                description: Some(desc.to_string()),
787            },
788        }
789    }
790}
791
792/// API Key location
793#[derive(Debug, Clone, Serialize, Deserialize)]
794#[serde(rename_all = "lowercase")]
795pub enum ApiKeyLocation {
796    Query,
797    Header,
798    Cookie,
799}
800
801/// OAuth2 flow collection
802#[derive(Debug, Clone, Default, Serialize, Deserialize)]
803pub struct OAuth2Flows {
804    #[serde(default, skip_serializing_if = "Option::is_none")]
805    pub implicit: Option<ImplicitFlow>,
806    #[serde(default, skip_serializing_if = "Option::is_none")]
807    pub password: Option<PasswordFlow>,
808    #[serde(default, skip_serializing_if = "Option::is_none")]
809    pub client_credentials: Option<ClientCredentialsFlow>,
810    #[serde(default, skip_serializing_if = "Option::is_none")]
811    pub authorization_code: Option<AuthorizationCodeFlow>,
812}
813
814impl OAuth2Flows {
815    pub fn new() -> Self {
816        Self::default()
817    }
818
819    pub fn with_implicit(mut self, flow: ImplicitFlow) -> Self {
820        self.implicit = Some(flow);
821        self
822    }
823
824    pub fn with_password(mut self, flow: PasswordFlow) -> Self {
825        self.password = Some(flow);
826        self
827    }
828
829    pub fn with_client_credentials(mut self, flow: ClientCredentialsFlow) -> Self {
830        self.client_credentials = Some(flow);
831        self
832    }
833
834    pub fn with_authorization_code(mut self, flow: AuthorizationCodeFlow) -> Self {
835        self.authorization_code = Some(flow);
836        self
837    }
838}
839
840/// Implicit flow
841#[derive(Debug, Clone, Serialize, Deserialize)]
842pub struct ImplicitFlow {
843    #[serde(rename = "authorizationUrl")]
844    pub authorization_url: String,
845    #[serde(
846        rename = "refreshUrl",
847        default,
848        skip_serializing_if = "Option::is_none"
849    )]
850    pub refresh_url: Option<String>,
851    pub scopes: HashMap<String, String>,
852}
853
854/// Password flow
855#[derive(Debug, Clone, Serialize, Deserialize)]
856pub struct PasswordFlow {
857    #[serde(rename = "tokenUrl")]
858    pub token_url: String,
859    #[serde(
860        rename = "refreshUrl",
861        default,
862        skip_serializing_if = "Option::is_none"
863    )]
864    pub refresh_url: Option<String>,
865    pub scopes: HashMap<String, String>,
866}
867
868/// Client Credentials flow
869#[derive(Debug, Clone, Serialize, Deserialize)]
870pub struct ClientCredentialsFlow {
871    #[serde(rename = "tokenUrl")]
872    pub token_url: String,
873    #[serde(
874        rename = "refreshUrl",
875        default,
876        skip_serializing_if = "Option::is_none"
877    )]
878    pub refresh_url: Option<String>,
879    pub scopes: HashMap<String, String>,
880}
881
882/// Authorization Code flow
883#[derive(Debug, Clone, Serialize, Deserialize)]
884pub struct AuthorizationCodeFlow {
885    #[serde(rename = "authorizationUrl")]
886    pub authorization_url: String,
887    #[serde(rename = "tokenUrl")]
888    pub token_url: String,
889    #[serde(
890        rename = "refreshUrl",
891        default,
892        skip_serializing_if = "Option::is_none"
893    )]
894    pub refresh_url: Option<String>,
895    pub scopes: HashMap<String, String>,
896}
897
898// ============================================================================
899// Tags 接口分组
900// ============================================================================
901
902/// Interface grouping tag
903#[derive(Debug, Clone, Serialize, Deserialize)]
904pub struct Tag {
905    pub name: String,
906    #[serde(default, skip_serializing_if = "Option::is_none")]
907    pub description: Option<String>,
908    #[serde(default, skip_serializing_if = "Option::is_none")]
909    pub external_docs: Option<ExternalDocs>,
910}
911
912impl Tag {
913    pub fn new(name: &str) -> Self {
914        Self {
915            name: name.to_string(),
916            description: None,
917            external_docs: None,
918        }
919    }
920
921    pub fn with_description(mut self, desc: &str) -> Self {
922        self.description = Some(desc.to_string());
923        self
924    }
925
926    pub fn with_external_docs(mut self, docs: ExternalDocs) -> Self {
927        self.external_docs = Some(docs);
928        self
929    }
930}
931
932/// External documentation link
933#[derive(Debug, Clone, Serialize, Deserialize)]
934pub struct ExternalDocs {
935    pub url: String,
936    #[serde(default, skip_serializing_if = "Option::is_none")]
937    pub description: Option<String>,
938}
939
940impl ExternalDocs {
941    pub fn new(url: &str) -> Self {
942        Self {
943            url: url.to_string(),
944            description: None,
945        }
946    }
947
948    pub fn with_description(mut self, desc: &str) -> Self {
949        self.description = Some(desc.to_string());
950        self
951    }
952}
953
954// ============================================================================
955// Servers 环境配置
956// ============================================================================
957
958/// Environment server configuration
959#[derive(Debug, Clone, Serialize, Deserialize)]
960pub struct Server {
961    pub url: String,
962    #[serde(default, skip_serializing_if = "Option::is_none")]
963    pub description: Option<String>,
964    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
965    pub variables: HashMap<String, ServerVariable>,
966}
967
968impl Server {
969    pub fn new(url: &str) -> Self {
970        Self {
971            url: url.to_string(),
972            description: None,
973            variables: HashMap::new(),
974        }
975    }
976
977    pub fn with_description(mut self, desc: &str) -> Self {
978        self.description = Some(desc.to_string());
979        self
980    }
981
982    pub fn with_variable(mut self, name: &str, var: ServerVariable) -> Self {
983        self.variables.insert(name.to_string(), var);
984        self
985    }
986}
987
988/// Server variable (used for URL template substitution)
989#[derive(Debug, Clone, Serialize, Deserialize)]
990pub struct ServerVariable {
991    #[serde(rename = "default")]
992    pub default_value: String,
993    #[serde(default, skip_serializing_if = "Option::is_none")]
994    pub description: Option<String>,
995    #[serde(default, skip_serializing_if = "Vec::is_empty")]
996    pub enum_values: Vec<String>,
997}
998
999impl ServerVariable {
1000    pub fn new(default: &str) -> Self {
1001        Self {
1002            default_value: default.to_string(),
1003            description: None,
1004            enum_values: vec![],
1005        }
1006    }
1007
1008    pub fn with_description(mut self, desc: &str) -> Self {
1009        self.description = Some(desc.to_string());
1010        self
1011    }
1012
1013    pub fn with_enum(mut self, values: Vec<String>) -> Self {
1014        self.enum_values = values;
1015        self
1016    }
1017}
1018
1019// ============================================================================
1020// 示例生成器(基于 Schema 自动生成示例值)
1021// ============================================================================
1022
1023/// Generator that produces example values based on a Schema
1024pub struct ExampleBuilder;
1025
1026impl ExampleBuilder {
1027    /// Generates an example value based on the Schema
1028    ///
1029    /// - Object: recursively generates examples for all fields
1030    /// - Array: generates a single-element array
1031    /// - Reference: returns null (the caller needs to resolve the reference)
1032    /// - Primitive: generates a reasonable default value based on type/format
1033    pub fn from_schema(schema: &Schema) -> serde_json::Value {
1034        match schema {
1035            Schema::Ref { .. } => serde_json::Value::Null,
1036            Schema::Object(obj) => Self::from_object(obj),
1037            Schema::Array(arr) => {
1038                let item = Self::from_schema(&arr.items);
1039                serde_json::Value::Array(vec![item])
1040            }
1041            Schema::Primitive(p) => Self::from_primitive(p),
1042        }
1043    }
1044
1045    /// Generates an example object based on an object Schema
1046    pub fn from_object(obj: &ObjectType) -> serde_json::Value {
1047        let mut map = serde_json::Map::new();
1048        for (name, schema) in &obj.properties {
1049            map.insert(name.clone(), Self::from_schema(schema));
1050        }
1051        serde_json::Value::Object(map)
1052    }
1053
1054    /// Generates an example value based on a primitive type Schema
1055    pub fn from_primitive(p: &PrimitiveSchema) -> serde_json::Value {
1056        // 优先返回 example 字段
1057        if let Some(example) = &p.example {
1058            return example.clone();
1059        }
1060        // 其次返回 default 字段
1061        if let Some(default) = &p.default {
1062            return default.clone();
1063        }
1064        // 再次返回 enum 的第一个值
1065        if let Some(enum_vals) = &p.enum_values {
1066            if let Some(first) = enum_vals.first() {
1067                return first.clone();
1068            }
1069        }
1070        // 根据 type 生成默认示例
1071        match p.schema_type.as_str() {
1072            "string" => match p.format.as_deref() {
1073                Some("date") => serde_json::Value::String("2026-01-01".to_string()),
1074                Some("date-time") => serde_json::Value::String("2026-01-01T00:00:00Z".to_string()),
1075                Some("email") => serde_json::Value::String("user@example.com".to_string()),
1076                Some("uuid") => {
1077                    serde_json::Value::String("550e8400-e29b-41d4-a716-446655440000".to_string())
1078                }
1079                Some("uri") => serde_json::Value::String("https://example.com".to_string()),
1080                _ => serde_json::Value::String("string".to_string()),
1081            },
1082            "integer" => {
1083                serde_json::Value::Number(serde_json::Number::from(p.minimum.unwrap_or(0.0) as i64))
1084            }
1085            "number" => serde_json::json!(p.minimum.unwrap_or(0.0)),
1086            "boolean" => serde_json::Value::Bool(false),
1087            _ => serde_json::Value::Null,
1088        }
1089    }
1090}
1091
1092// ============================================================================
1093// OpenAPIGenerator 生成器
1094// ============================================================================
1095
1096pub struct OpenAPIGenerator {
1097    paths: Vec<(String, PathInfo)>,
1098    info: serde_json::Value,
1099    schemas: HashMap<String, Schema>,
1100    security_schemes: HashMap<String, SecurityScheme>,
1101    tags: Vec<Tag>,
1102    servers: Vec<Server>,
1103    security: Vec<SecurityRequirement>,
1104}
1105
1106impl OpenAPIGenerator {
1107    pub fn new() -> Self {
1108        Self {
1109            paths: vec![],
1110            info: serde_json::json!({
1111                "title": "API",
1112                "version": "1.0.0",
1113                "description": "Generated by sz-orm-swagger"
1114            }),
1115            schemas: HashMap::new(),
1116            security_schemes: HashMap::new(),
1117            tags: vec![],
1118            servers: vec![],
1119            security: vec![],
1120        }
1121    }
1122
1123    pub fn with_info(mut self, info: serde_json::Value) -> Self {
1124        self.info = info;
1125        self
1126    }
1127
1128    /// Registers a (path, method) operation. Multiple methods for the same path are merged into the same paths entry.
1129    pub fn register_path(&mut self, path: &str, info: PathInfo) -> &mut Self {
1130        self.paths.push((path.to_string(), info));
1131        self
1132    }
1133
1134    /// Registers a reusable Schema definition
1135    pub fn register_schema(&mut self, name: &str, schema: Schema) -> &mut Self {
1136        self.schemas.insert(name.to_string(), schema);
1137        self
1138    }
1139
1140    /// Registers a reusable SecurityScheme
1141    pub fn register_security_scheme(&mut self, name: &str, scheme: SecurityScheme) -> &mut Self {
1142        self.security_schemes.insert(name.to_string(), scheme);
1143        self
1144    }
1145
1146    /// Adds an interface grouping tag
1147    pub fn add_tag(&mut self, tag: Tag) -> &mut Self {
1148        self.tags.push(tag);
1149        self
1150    }
1151
1152    /// Adds an environment server
1153    pub fn add_server(&mut self, server: Server) -> &mut Self {
1154        self.servers.push(server);
1155        self
1156    }
1157
1158    /// Sets the global security requirement
1159    pub fn with_global_security(mut self, req: SecurityRequirement) -> Self {
1160        self.security.push(req);
1161        self
1162    }
1163
1164    /// Generates the OpenAPISpec
1165    pub fn generate(&self) -> OpenAPISpec {
1166        let mut paths: HashMap<String, serde_json::Value> = HashMap::new();
1167        for (path, info) in &self.paths {
1168            let method = info.method.to_lowercase();
1169            let entry = paths
1170                .entry(path.clone())
1171                .or_insert_with(|| serde_json::json!({}));
1172            let mut op = serde_json::json!({
1173                "summary": info.summary,
1174                "parameters": info.parameters,
1175                "responses": info.responses
1176            });
1177            if !info.tags.is_empty() {
1178                op["tags"] = serde_json::json!(info.tags);
1179            }
1180            if let Some(desc) = &info.description {
1181                op["description"] = serde_json::json!(desc);
1182            }
1183            if let Some(body) = &info.request_body {
1184                op["requestBody"] = serde_json::json!(body);
1185            }
1186            if !info.security.is_empty() {
1187                op["security"] = serde_json::json!(info.security);
1188            }
1189            if let Some(op_id) = &info.operation_id {
1190                op["operationId"] = serde_json::json!(op_id);
1191            }
1192            if let Some(deprecated) = info.deprecated {
1193                op["deprecated"] = serde_json::json!(deprecated);
1194            }
1195            entry[method] = op;
1196        }
1197
1198        let components = if self.schemas.is_empty() && self.security_schemes.is_empty() {
1199            None
1200        } else {
1201            Some(Components {
1202                schemas: self.schemas.clone(),
1203                security_schemes: self.security_schemes.clone(),
1204            })
1205        };
1206
1207        OpenAPISpec {
1208            openapi: "3.0.0".to_string(),
1209            paths,
1210            info: self.info.clone(),
1211            components,
1212            tags: self.tags.clone(),
1213            servers: self.servers.clone(),
1214            security: self.security.clone(),
1215        }
1216    }
1217}
1218
1219impl Default for OpenAPIGenerator {
1220    fn default() -> Self {
1221        Self::new()
1222    }
1223}
1224
1225// ============================================================================
1226// SwaggerUi 渲染器
1227// ============================================================================
1228
1229pub struct SwaggerUi {
1230    mount_path: String,
1231    spec: Option<OpenAPISpec>,
1232}
1233
1234impl SwaggerUi {
1235    pub fn new(path: &str) -> Self {
1236        Self {
1237            mount_path: path.to_string(),
1238            spec: None,
1239        }
1240    }
1241
1242    pub fn with_spec(mut self, spec: OpenAPISpec) -> Self {
1243        self.spec = Some(spec);
1244        self
1245    }
1246
1247    pub fn mount(&self) -> String {
1248        format!("{}docs", self.mount_path)
1249    }
1250
1251    /// Renders a self-contained Swagger UI HTML page with the spec JSON embedded
1252    pub fn render_html(&self) -> String {
1253        let spec_json = match &self.spec {
1254            Some(s) => s.to_json_string(),
1255            None => serde_json::json!({
1256                "openapi": "3.0.0",
1257                "info": { "title": "API", "version": "1.0.0" },
1258                "paths": {}
1259            })
1260            .to_string(),
1261        };
1262        let mount = self.mount();
1263        format!(
1264            r#"<!DOCTYPE html>
1265<html lang="en">
1266<head>
1267    <meta charset="UTF-8">
1268    <title>Swagger UI</title>
1269    <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@4.19.0/swagger-ui.css">
1270</head>
1271<body>
1272    <div id="swagger-ui"></div>
1273    <script src="https://unpkg.com/swagger-ui-dist@4.19.0/swagger-ui-bundle.js"></script>
1274    <script src="https://unpkg.com/swagger-ui-dist@4.19.0/swagger-ui-standalone-preset.js"></script>
1275    <script>
1276        const spec = {spec};
1277        window.onload = () => {{
1278            SwaggerUIBundle({{
1279                spec: spec,
1280                dom_id: '#swagger-ui',
1281                url: '{mount}/openapi.json',
1282                presets: [SwaggerUIBundle.presets.apis, SwaggerUIStandalonePreset],
1283                layout: 'StandaloneLayout'
1284            }});
1285        }};
1286    </script>
1287</body>
1288</html>"#,
1289            spec = spec_json,
1290            mount = mount
1291        )
1292    }
1293}
1294
1295// ============================================================================
1296// Model -> OpenAPI Schema 生成
1297// ============================================================================
1298
1299/// Converts a sz-orm `Model` definition into an OpenAPI 3.0 object Schema.
1300///
1301/// Generates an `ObjectType` based on the (field name, type string) list returned by
1302/// [`sz_orm_core::Model::fields`]; the type string follows the sz-orm casts convention
1303/// (`"integer"` / `"float"` / `"boolean"` / `"string"` / `"datetime"` / `"date"` /
1304/// `"time"` / `"json"` / `"array"` / `"bytes"`).
1305///
1306/// # Example
1307///
1308/// ```ignore
1309/// use sz_orm_core::Model;
1310/// use sz_orm_swagger::model_to_openapi_schema;
1311///
1312/// struct User { id: i64, name: String }
1313/// impl Model for User {
1314///     type PrimaryKey = i64;
1315///     fn table_name() -> &'static str { "users" }
1316///     fn pk(&self) -> Self::PrimaryKey { self.id }
1317///     fn set_pk(&mut self, pk: Self::PrimaryKey) { self.id = pk; }
1318///     fn fields() -> Vec<(&'static str, &'static str)> {
1319///         vec![("id", "integer"), ("name", "string")]
1320///     }
1321/// }
1322///
1323/// let schema = model_to_openapi_schema::<User>();
1324/// ```
1325pub fn model_to_openapi_schema<T: sz_orm_core::Model>() -> Schema {
1326    let mut obj = ObjectType::new().with_description(T::table_name());
1327    for (name, type_str) in T::fields() {
1328        obj = obj.with_property(name, cast_type_to_schema(type_str));
1329    }
1330    Schema::object(obj)
1331}
1332
1333/// Maps a sz-orm casts type string to an OpenAPI primitive type Schema.
1334fn cast_type_to_schema(type_str: &str) -> Schema {
1335    match type_str {
1336        "integer" => Schema::integer(),
1337        "float" | "double" => Schema::number(),
1338        "boolean" => Schema::boolean(),
1339        "datetime" => Schema::string().with_format("date-time"),
1340        "date" => Schema::string().with_format("date"),
1341        "time" => Schema::string().with_format("time"),
1342        // json/array/bytes 在 OpenAPI 中无直接对应基本类型,统一以 string 表示
1343        "json" | "array" | "bytes" => Schema::string(),
1344        _ => Schema::string(),
1345    }
1346}
1347
1348#[cfg(test)]
1349mod tests {
1350    use super::*;
1351
1352    #[test]
1353    fn test_gen_empty_has_no_paths() {
1354        let s = OpenAPIGenerator::new().generate();
1355        assert!(s.paths.is_empty());
1356        assert_eq!(s.info["title"], "API");
1357        assert_eq!(s.openapi, "3.0.0");
1358    }
1359
1360    #[test]
1361    fn test_register_and_generate_single_path() {
1362        let mut g = OpenAPIGenerator::new();
1363        g.register_path(
1364            "/users",
1365            PathInfo::new("GET", "List users").with_response("200", "OK"),
1366        );
1367        let spec = g.generate();
1368        let users = spec.paths.get("/users").expect("/users should exist");
1369        let get = users.get("get").expect("GET method should exist");
1370        assert_eq!(get["summary"], "List users");
1371        assert!(get["responses"]["200"].is_object());
1372    }
1373
1374    #[test]
1375    fn test_register_multiple_methods_same_path() {
1376        let mut g = OpenAPIGenerator::new();
1377        g.register_path(
1378            "/users",
1379            PathInfo::new("GET", "List users").with_response("200", "OK"),
1380        );
1381        g.register_path(
1382            "/users",
1383            PathInfo::new("POST", "Create user").with_response("201", "Created"),
1384        );
1385        let spec = g.generate();
1386        assert_eq!(spec.paths.len(), 1, "only one /users path key");
1387        let users = spec.paths.get("/users").unwrap();
1388        assert!(users.get("get").is_some());
1389        assert!(users.get("post").is_some());
1390        assert_eq!(users["get"]["summary"], "List users");
1391        assert_eq!(users["post"]["summary"], "Create user");
1392        assert_eq!(users["post"]["responses"]["201"]["description"], "Created");
1393    }
1394
1395    #[test]
1396    fn test_register_multiple_paths() {
1397        let mut g = OpenAPIGenerator::new();
1398        g.register_path("/users", PathInfo::new("GET", "List users"));
1399        g.register_path("/orders", PathInfo::new("GET", "List orders"));
1400        g.register_path(
1401            "/items/{id}",
1402            PathInfo::new("GET", "Get item").with_response("404", "Not found"),
1403        );
1404        let spec = g.generate();
1405        assert_eq!(spec.paths.len(), 3);
1406        assert!(spec.paths.contains_key("/users"));
1407        assert!(spec.paths.contains_key("/orders"));
1408        assert!(spec.paths.contains_key("/items/{id}"));
1409    }
1410
1411    #[test]
1412    fn test_ui_mount() {
1413        let ui = SwaggerUi::new("/api");
1414        assert_eq!(ui.mount(), "/apidocs");
1415    }
1416
1417    #[test]
1418    fn test_ui_html_contains_cdn_and_bundle() {
1419        let ui = SwaggerUi::new("/api").with_spec(OpenAPIGenerator::new().generate());
1420        let html = ui.render_html();
1421        assert!(html.contains("swagger-ui-dist"));
1422        assert!(html.contains("swagger-ui.css"));
1423        assert!(html.contains("swagger-ui-bundle.js"));
1424        assert!(html.contains("SwaggerUIBundle"));
1425        assert!(html.contains("id=\"swagger-ui\""));
1426        assert!(html.contains("<!DOCTYPE html>"));
1427    }
1428
1429    #[test]
1430    fn test_ui_html_embeds_spec_content() {
1431        let mut g = OpenAPIGenerator::new();
1432        g.register_path(
1433            "/items",
1434            PathInfo::new("GET", "List items").with_response("200", "OK"),
1435        );
1436        let ui = SwaggerUi::new("/api").with_spec(g.generate());
1437        let html = ui.render_html();
1438        assert!(html.contains("/items"));
1439        assert!(html.contains("List items"));
1440        assert!(html.contains("\"get\""));
1441    }
1442
1443    #[test]
1444    fn test_ui_html_without_spec_uses_default_spec() {
1445        let ui = SwaggerUi::new("/api");
1446        let html = ui.render_html();
1447        assert!(html.contains("swagger-ui"));
1448        assert!(html.contains("\"openapi\""));
1449    }
1450
1451    #[test]
1452    fn test_spec_to_json_string_is_valid_json() {
1453        let mut g = OpenAPIGenerator::new();
1454        g.register_path("/users", PathInfo::new("GET", "List users"));
1455        let spec = g.generate();
1456        let json = spec.to_json_string();
1457        let parsed: serde_json::Value = serde_json::from_str(&json).expect("should parse");
1458        assert!(parsed["paths"]["/users"]["get"].is_object());
1459        assert_eq!(parsed["openapi"], "3.0.0");
1460    }
1461
1462    #[test]
1463    fn test_path_info_builder() {
1464        let p = PathInfo::new("PUT", "Update user")
1465            .with_response("200", "OK")
1466            .with_response("404", "Not found")
1467            .with_parameter(serde_json::json!({"name": "id", "in": "path"}));
1468        assert_eq!(p.method, "PUT");
1469        assert_eq!(p.responses.len(), 2);
1470        assert_eq!(p.parameters.len(), 1);
1471    }
1472
1473    // ===== Schema 定义测试 =====
1474
1475    #[test]
1476    fn test_schema_ref_to() {
1477        let s = Schema::ref_to("User");
1478        match &s {
1479            Schema::Ref { ref_path } => {
1480                assert_eq!(ref_path, "#/components/schemas/User");
1481            }
1482            _ => panic!("Expected Ref variant"),
1483        }
1484        assert!(s.is_ref());
1485        assert!(!s.is_object());
1486        assert!(!s.is_array());
1487    }
1488
1489    #[test]
1490    fn test_schema_primitive_string() {
1491        let s = Schema::string();
1492        match &s {
1493            Schema::Primitive(p) => {
1494                assert_eq!(p.schema_type, "string");
1495            }
1496            _ => panic!("Expected Primitive variant"),
1497        }
1498    }
1499
1500    #[test]
1501    fn test_schema_primitive_integer_with_format() {
1502        let s = Schema::integer();
1503        match &s {
1504            Schema::Primitive(p) => {
1505                assert_eq!(p.schema_type, "integer");
1506                assert_eq!(p.format.as_deref(), Some("int64"));
1507            }
1508            _ => panic!("Expected Primitive variant"),
1509        }
1510    }
1511
1512    #[test]
1513    fn test_schema_primitive_number() {
1514        let s = Schema::number();
1515        match &s {
1516            Schema::Primitive(p) => {
1517                assert_eq!(p.schema_type, "number");
1518                assert_eq!(p.format.as_deref(), Some("double"));
1519            }
1520            _ => panic!("Expected Primitive variant"),
1521        }
1522    }
1523
1524    #[test]
1525    fn test_schema_primitive_boolean() {
1526        let s = Schema::boolean();
1527        match &s {
1528            Schema::Primitive(p) => {
1529                assert_eq!(p.schema_type, "boolean");
1530            }
1531            _ => panic!("Expected Primitive variant"),
1532        }
1533    }
1534
1535    #[test]
1536    fn test_object_type_builder() {
1537        let obj = ObjectType::new()
1538            .with_required_property("id", Schema::integer())
1539            .with_property("name", Schema::string())
1540            .with_description("User object");
1541        assert_eq!(obj.schema_type, "object");
1542        assert_eq!(obj.properties.len(), 2);
1543        assert!(obj.required.contains(&"id".to_string()));
1544        assert!(!obj.required.contains(&"name".to_string()));
1545        assert_eq!(obj.description.as_deref(), Some("User object"));
1546    }
1547
1548    #[test]
1549    fn test_object_type_additional_properties() {
1550        let obj = ObjectType::new()
1551            .with_description("String map")
1552            .with_additional_properties(Schema::string());
1553        assert!(obj.additional_properties.is_some());
1554    }
1555
1556    #[test]
1557    fn test_array_type_builder() {
1558        let arr = ArrayType::new(Schema::string())
1559            .with_min_items(1)
1560            .with_max_items(100)
1561            .unique_items()
1562            .with_description("List of names");
1563        assert_eq!(arr.schema_type, "array");
1564        assert_eq!(arr.min_items, Some(1));
1565        assert_eq!(arr.max_items, Some(100));
1566        assert_eq!(arr.unique_items, Some(true));
1567        assert_eq!(arr.description.as_deref(), Some("List of names"));
1568    }
1569
1570    #[test]
1571    fn test_schema_is_object_array() {
1572        let obj_schema = Schema::object(ObjectType::new());
1573        assert!(obj_schema.is_object());
1574        assert!(!obj_schema.is_array());
1575
1576        let arr_schema = Schema::array(ArrayType::new(Schema::string()));
1577        assert!(!arr_schema.is_object());
1578        assert!(arr_schema.is_array());
1579    }
1580
1581    #[test]
1582    fn test_primitive_schema_with_format() {
1583        let p = PrimitiveSchema::string()
1584            .with_format("email")
1585            .with_description("Email address");
1586        assert_eq!(p.format.as_deref(), Some("email"));
1587        assert_eq!(p.description.as_deref(), Some("Email address"));
1588    }
1589
1590    #[test]
1591    fn test_primitive_schema_with_default() {
1592        let p = PrimitiveSchema::integer().with_default(serde_json::json!(42));
1593        assert_eq!(p.default, Some(serde_json::json!(42)));
1594    }
1595
1596    #[test]
1597    fn test_primitive_schema_with_example() {
1598        let p = PrimitiveSchema::string().with_example(serde_json::json!("John Doe"));
1599        assert_eq!(p.example, Some(serde_json::json!("John Doe")));
1600    }
1601
1602    #[test]
1603    fn test_primitive_schema_with_enum() {
1604        let p = PrimitiveSchema::string().with_enum(vec![
1605            serde_json::json!("active"),
1606            serde_json::json!("inactive"),
1607        ]);
1608        assert!(p.enum_values.is_some());
1609        assert_eq!(p.enum_values.as_ref().unwrap().len(), 2);
1610    }
1611
1612    #[test]
1613    fn test_primitive_schema_with_range() {
1614        let p = PrimitiveSchema::integer().with_range(0.0, 100.0);
1615        assert_eq!(p.minimum, Some(0.0));
1616        assert_eq!(p.maximum, Some(100.0));
1617    }
1618
1619    #[test]
1620    fn test_primitive_schema_with_length_range() {
1621        let p = PrimitiveSchema::string().with_length_range(3, 50);
1622        assert_eq!(p.min_length, Some(3));
1623        assert_eq!(p.max_length, Some(50));
1624    }
1625
1626    #[test]
1627    fn test_primitive_schema_with_pattern() {
1628        let p = PrimitiveSchema::string().with_pattern("^[a-z]+$");
1629        assert_eq!(p.pattern.as_deref(), Some("^[a-z]+$"));
1630    }
1631
1632    #[test]
1633    fn test_schema_serialization() {
1634        let obj = ObjectType::new()
1635            .with_required_property("id", Schema::integer())
1636            .with_property("name", Schema::string());
1637        let schema = Schema::object(obj);
1638        let json = serde_json::to_string(&schema).unwrap();
1639        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
1640        assert_eq!(parsed["type"], "object");
1641        assert!(parsed["properties"]["id"].is_object());
1642        assert!(parsed["required"].is_array());
1643    }
1644
1645    // ===== SecurityScheme 测试 =====
1646
1647    #[test]
1648    fn test_security_scheme_basic() {
1649        let s = SecurityScheme::basic();
1650        assert!(s.is_basic());
1651        assert!(!s.is_bearer());
1652        match &s {
1653            SecurityScheme::Http { scheme, .. } => {
1654                assert_eq!(scheme, "basic");
1655            }
1656            _ => panic!("Expected Http variant"),
1657        }
1658    }
1659
1660    #[test]
1661    fn test_security_scheme_bearer_with_jwt() {
1662        let s = SecurityScheme::bearer(true);
1663        assert!(s.is_bearer());
1664        assert!(!s.is_basic());
1665        match &s {
1666            SecurityScheme::Http {
1667                scheme,
1668                bearer_format,
1669                ..
1670            } => {
1671                assert_eq!(scheme, "bearer");
1672                assert_eq!(bearer_format.as_deref(), Some("JWT"));
1673            }
1674            _ => panic!("Expected Http variant"),
1675        }
1676    }
1677
1678    #[test]
1679    fn test_security_scheme_bearer_without_format() {
1680        let s = SecurityScheme::bearer(false);
1681        match &s {
1682            SecurityScheme::Http { bearer_format, .. } => {
1683                assert!(bearer_format.is_none());
1684            }
1685            _ => panic!("Expected Http variant"),
1686        }
1687    }
1688
1689    #[test]
1690    fn test_security_scheme_api_key_header() {
1691        let s = SecurityScheme::api_key("X-API-Key", ApiKeyLocation::Header);
1692        match &s {
1693            SecurityScheme::ApiKey { name, location, .. } => {
1694                assert_eq!(name, "X-API-Key");
1695                assert!(matches!(location, ApiKeyLocation::Header));
1696            }
1697            _ => panic!("Expected ApiKey variant"),
1698        }
1699    }
1700
1701    #[test]
1702    fn test_security_scheme_api_key_query() {
1703        let s = SecurityScheme::api_key("api_key", ApiKeyLocation::Query);
1704        match &s {
1705            SecurityScheme::ApiKey { location, .. } => {
1706                assert!(matches!(location, ApiKeyLocation::Query));
1707            }
1708            _ => panic!("Expected ApiKey variant"),
1709        }
1710    }
1711
1712    #[test]
1713    fn test_security_scheme_api_key_cookie() {
1714        let s = SecurityScheme::api_key("session", ApiKeyLocation::Cookie);
1715        match &s {
1716            SecurityScheme::ApiKey { location, .. } => {
1717                assert!(matches!(location, ApiKeyLocation::Cookie));
1718            }
1719            _ => panic!("Expected ApiKey variant"),
1720        }
1721    }
1722
1723    #[test]
1724    fn test_security_scheme_oauth2() {
1725        let mut scopes = HashMap::new();
1726        scopes.insert("read".to_string(), "Read access".to_string());
1727        scopes.insert("write".to_string(), "Write access".to_string());
1728        let flow = AuthorizationCodeFlow {
1729            authorization_url: "https://example.com/oauth/authorize".to_string(),
1730            token_url: "https://example.com/oauth/token".to_string(),
1731            refresh_url: None,
1732            scopes,
1733        };
1734        let flows = OAuth2Flows::new().with_authorization_code(flow);
1735        let s = SecurityScheme::oauth2(flows);
1736        match &s {
1737            SecurityScheme::OAuth2 { flows, .. } => {
1738                assert!(flows.authorization_code.is_some());
1739                assert!(flows.implicit.is_none());
1740            }
1741            _ => panic!("Expected OAuth2 variant"),
1742        }
1743    }
1744
1745    #[test]
1746    fn test_security_scheme_with_description() {
1747        let s = SecurityScheme::bearer(true).with_description("JWT auth");
1748        match &s {
1749            SecurityScheme::Http { description, .. } => {
1750                assert_eq!(description.as_deref(), Some("JWT auth"));
1751            }
1752            _ => panic!("Expected Http variant"),
1753        }
1754    }
1755
1756    #[test]
1757    fn test_oauth2_flows_builder() {
1758        let mut scopes = HashMap::new();
1759        scopes.insert("read".to_string(), "Read access".to_string());
1760        let implicit = ImplicitFlow {
1761            authorization_url: "https://example.com/oauth/authorize".to_string(),
1762            refresh_url: None,
1763            scopes,
1764        };
1765        let flows = OAuth2Flows::new()
1766            .with_implicit(implicit)
1767            .with_password(PasswordFlow {
1768                token_url: "https://example.com/oauth/token".to_string(),
1769                refresh_url: None,
1770                scopes: HashMap::new(),
1771            })
1772            .with_client_credentials(ClientCredentialsFlow {
1773                token_url: "https://example.com/oauth/token".to_string(),
1774                refresh_url: None,
1775                scopes: HashMap::new(),
1776            });
1777        assert!(flows.implicit.is_some());
1778        assert!(flows.password.is_some());
1779        assert!(flows.client_credentials.is_some());
1780        assert!(flows.authorization_code.is_none());
1781    }
1782
1783    // ===== Tags 测试 =====
1784
1785    #[test]
1786    fn test_tag_builder() {
1787        let tag = Tag::new("users")
1788            .with_description("User management endpoints")
1789            .with_external_docs(
1790                ExternalDocs::new("https://example.com/docs/users").with_description("User docs"),
1791            );
1792        assert_eq!(tag.name, "users");
1793        assert_eq!(
1794            tag.description.as_deref(),
1795            Some("User management endpoints")
1796        );
1797        assert!(tag.external_docs.is_some());
1798        assert_eq!(
1799            tag.external_docs.as_ref().unwrap().url,
1800            "https://example.com/docs/users"
1801        );
1802    }
1803
1804    #[test]
1805    fn test_external_docs_builder() {
1806        let docs = ExternalDocs::new("https://example.com/docs")
1807            .with_description("External documentation");
1808        assert_eq!(docs.url, "https://example.com/docs");
1809        assert_eq!(docs.description.as_deref(), Some("External documentation"));
1810    }
1811
1812    // ===== Servers 测试 =====
1813
1814    #[test]
1815    fn test_server_builder() {
1816        let server = Server::new("https://{env}.example.com")
1817            .with_description("Environment-specific server")
1818            .with_variable(
1819                "env",
1820                ServerVariable::new("api")
1821                    .with_description("Environment name")
1822                    .with_enum(vec![
1823                        "api".to_string(),
1824                        "staging".to_string(),
1825                        "prod".to_string(),
1826                    ]),
1827            );
1828        assert_eq!(server.url, "https://{env}.example.com");
1829        assert_eq!(
1830            server.description.as_deref(),
1831            Some("Environment-specific server")
1832        );
1833        assert_eq!(server.variables.len(), 1);
1834        let env_var = server.variables.get("env").unwrap();
1835        assert_eq!(env_var.default_value, "api");
1836        assert_eq!(env_var.enum_values.len(), 3);
1837    }
1838
1839    #[test]
1840    fn test_server_variable_builder() {
1841        let var = ServerVariable::new("v1")
1842            .with_description("API version")
1843            .with_enum(vec!["v1".to_string(), "v2".to_string()]);
1844        assert_eq!(var.default_value, "v1");
1845        assert_eq!(var.description.as_deref(), Some("API version"));
1846        assert_eq!(var.enum_values, vec!["v1".to_string(), "v2".to_string()]);
1847    }
1848
1849    // ===== 示例生成测试 =====
1850
1851    #[test]
1852    fn test_example_builder_primitive_string() {
1853        let p = PrimitiveSchema::string();
1854        let v = ExampleBuilder::from_primitive(&p);
1855        assert_eq!(v, serde_json::Value::String("string".to_string()));
1856    }
1857
1858    #[test]
1859    fn test_example_builder_primitive_string_with_example() {
1860        let p = PrimitiveSchema::string().with_example(serde_json::json!("hello"));
1861        let v = ExampleBuilder::from_primitive(&p);
1862        assert_eq!(v, serde_json::json!("hello"));
1863    }
1864
1865    #[test]
1866    fn test_example_builder_primitive_string_with_default() {
1867        let p = PrimitiveSchema::string().with_default(serde_json::json!("default_val"));
1868        let v = ExampleBuilder::from_primitive(&p);
1869        assert_eq!(v, serde_json::json!("default_val"));
1870    }
1871
1872    #[test]
1873    fn test_example_builder_primitive_email_format() {
1874        let p = PrimitiveSchema::string().with_format("email");
1875        let v = ExampleBuilder::from_primitive(&p);
1876        assert_eq!(v, serde_json::json!("user@example.com"));
1877    }
1878
1879    #[test]
1880    fn test_example_builder_primitive_date_format() {
1881        let p = PrimitiveSchema::string().with_format("date");
1882        let v = ExampleBuilder::from_primitive(&p);
1883        assert_eq!(v, serde_json::json!("2026-01-01"));
1884    }
1885
1886    #[test]
1887    fn test_example_builder_primitive_datetime_format() {
1888        let p = PrimitiveSchema::string().with_format("date-time");
1889        let v = ExampleBuilder::from_primitive(&p);
1890        assert_eq!(v, serde_json::json!("2026-01-01T00:00:00Z"));
1891    }
1892
1893    #[test]
1894    fn test_example_builder_primitive_uuid_format() {
1895        let p = PrimitiveSchema::string().with_format("uuid");
1896        let v = ExampleBuilder::from_primitive(&p);
1897        assert!(v.as_str().unwrap().contains("-"));
1898    }
1899
1900    #[test]
1901    fn test_example_builder_primitive_integer() {
1902        let p = PrimitiveSchema::integer();
1903        let v = ExampleBuilder::from_primitive(&p);
1904        assert_eq!(v, serde_json::json!(0));
1905    }
1906
1907    #[test]
1908    fn test_example_builder_primitive_integer_with_minimum() {
1909        let p = PrimitiveSchema::integer().with_range(10.0, 100.0);
1910        let v = ExampleBuilder::from_primitive(&p);
1911        assert_eq!(v, serde_json::json!(10));
1912    }
1913
1914    #[test]
1915    fn test_example_builder_primitive_number() {
1916        let p = PrimitiveSchema::number();
1917        let v = ExampleBuilder::from_primitive(&p);
1918        assert_eq!(v, serde_json::json!(0.0));
1919    }
1920
1921    #[test]
1922    fn test_example_builder_primitive_boolean() {
1923        let p = PrimitiveSchema::boolean();
1924        let v = ExampleBuilder::from_primitive(&p);
1925        assert_eq!(v, serde_json::json!(false));
1926    }
1927
1928    #[test]
1929    fn test_example_builder_primitive_with_enum() {
1930        let p = PrimitiveSchema::string().with_enum(vec![
1931            serde_json::json!("active"),
1932            serde_json::json!("inactive"),
1933        ]);
1934        let v = ExampleBuilder::from_primitive(&p);
1935        assert_eq!(v, serde_json::json!("active"));
1936    }
1937
1938    #[test]
1939    fn test_example_builder_object() {
1940        let obj = ObjectType::new()
1941            .with_required_property("id", Schema::integer())
1942            .with_property("name", Schema::string());
1943        let v = ExampleBuilder::from_object(&obj);
1944        assert!(v.is_object());
1945        assert_eq!(v["id"], serde_json::json!(0));
1946        assert_eq!(v["name"], serde_json::json!("string"));
1947    }
1948
1949    #[test]
1950    fn test_example_builder_array() {
1951        let arr = ArrayType::new(Schema::string());
1952        let schema = Schema::array(arr);
1953        let v = ExampleBuilder::from_schema(&schema);
1954        assert!(v.is_array());
1955        assert_eq!(v[0], serde_json::json!("string"));
1956    }
1957
1958    #[test]
1959    fn test_example_builder_ref_returns_null() {
1960        let schema = Schema::ref_to("User");
1961        let v = ExampleBuilder::from_schema(&schema);
1962        assert!(v.is_null());
1963    }
1964
1965    // ===== 集成测试:Generator 与 Schema/SecurityScheme/Tags/Servers =====
1966
1967    #[test]
1968    fn test_generator_with_schema() {
1969        let mut g = OpenAPIGenerator::new();
1970        let user_schema = Schema::object(
1971            ObjectType::new()
1972                .with_required_property("id", Schema::integer())
1973                .with_required_property("name", Schema::string())
1974                .with_property("email", Schema::string().with_format("email")),
1975        );
1976        g.register_schema("User", user_schema);
1977        let spec = g.generate();
1978        let components = spec.components.expect("components should exist");
1979        assert!(components.schemas.contains_key("User"));
1980    }
1981
1982    #[test]
1983    fn test_generator_with_security_scheme() {
1984        let mut g = OpenAPIGenerator::new();
1985        g.register_security_scheme("bearerAuth", SecurityScheme::bearer(true));
1986        let spec = g.generate();
1987        let components = spec.components.expect("components should exist");
1988        assert!(components.security_schemes.contains_key("bearerAuth"));
1989    }
1990
1991    #[test]
1992    fn test_generator_with_tags() {
1993        let mut g = OpenAPIGenerator::new();
1994        g.add_tag(Tag::new("users").with_description("User endpoints"));
1995        g.add_tag(Tag::new("orders").with_description("Order endpoints"));
1996        let spec = g.generate();
1997        assert_eq!(spec.tags.len(), 2);
1998        assert_eq!(spec.tags[0].name, "users");
1999        assert_eq!(spec.tags[1].name, "orders");
2000    }
2001
2002    #[test]
2003    fn test_generator_with_servers() {
2004        let mut g = OpenAPIGenerator::new();
2005        g.add_server(Server::new("https://api.example.com").with_description("Production"));
2006        g.add_server(Server::new("https://staging.example.com").with_description("Staging"));
2007        let spec = g.generate();
2008        assert_eq!(spec.servers.len(), 2);
2009        assert_eq!(spec.servers[0].url, "https://api.example.com");
2010        assert_eq!(spec.servers[1].url, "https://staging.example.com");
2011    }
2012
2013    #[test]
2014    fn test_generator_with_global_security() {
2015        let g =
2016            OpenAPIGenerator::new().with_global_security(SecurityRequirement::new("bearerAuth"));
2017        let spec = g.generate();
2018        assert_eq!(spec.security.len(), 1);
2019    }
2020
2021    #[test]
2022    fn test_path_info_with_request_body() {
2023        let p = PathInfo::new("POST", "Create user").with_request_body_ref(
2024            "User payload",
2025            "#/components/schemas/User",
2026            true,
2027        );
2028        assert!(p.request_body.is_some());
2029        let body = p.request_body.unwrap();
2030        assert!(body.required);
2031        assert!(body.content.contains_key("application/json"));
2032    }
2033
2034    #[test]
2035    fn test_path_info_with_tags_and_security() {
2036        let p = PathInfo::new("GET", "List users")
2037            .with_tag("users")
2038            .with_tag("admin")
2039            .with_security(SecurityRequirement::new("bearerAuth"));
2040        assert_eq!(p.tags, vec!["users", "admin"]);
2041        assert_eq!(p.security.len(), 1);
2042    }
2043
2044    #[test]
2045    fn test_path_info_with_path_param() {
2046        let p = PathInfo::new("GET", "Get user").with_path_param("id", "User ID", true);
2047        assert_eq!(p.parameters.len(), 1);
2048        assert_eq!(p.parameters[0]["in"], "path");
2049        assert_eq!(p.parameters[0]["name"], "id");
2050        assert_eq!(p.parameters[0]["required"], true);
2051    }
2052
2053    #[test]
2054    fn test_path_info_with_query_param() {
2055        let p = PathInfo::new("GET", "List users")
2056            .with_query_param("page", "Page number", false)
2057            .with_query_param("limit", "Items per page", false);
2058        assert_eq!(p.parameters.len(), 2);
2059        assert_eq!(p.parameters[0]["in"], "query");
2060        assert_eq!(p.parameters[1]["in"], "query");
2061    }
2062
2063    #[test]
2064    fn test_path_info_with_operation_id() {
2065        let p = PathInfo::new("GET", "Get user").with_operation_id("getUserById");
2066        assert_eq!(p.operation_id.as_deref(), Some("getUserById"));
2067    }
2068
2069    #[test]
2070    fn test_path_info_deprecated() {
2071        let p = PathInfo::new("GET", "Old endpoint").deprecated();
2072        assert_eq!(p.deprecated, Some(true));
2073    }
2074
2075    #[test]
2076    fn test_path_info_with_response_schema() {
2077        let p = PathInfo::new("GET", "Get user").with_response_schema(
2078            "200",
2079            "OK",
2080            "#/components/schemas/User",
2081        );
2082        let resp = &p.responses["200"];
2083        assert_eq!(resp["description"], "OK");
2084        assert_eq!(
2085            resp["content"]["application/json"]["schema"]["$ref"],
2086            "#/components/schemas/User"
2087        );
2088    }
2089
2090    #[test]
2091    fn test_security_requirement_with_scopes() {
2092        let req = SecurityRequirement::new("oauth2")
2093            .with_scopes("oauth2", vec!["read".to_string(), "write".to_string()]);
2094        assert_eq!(req.requirements.len(), 1);
2095        let scopes = req.requirements.get("oauth2").unwrap();
2096        assert_eq!(scopes.len(), 2);
2097    }
2098
2099    #[test]
2100    fn test_media_type_builder() {
2101        let media = MediaType::with_schema_ref("#/components/schemas/User")
2102            .with_example(serde_json::json!({"id": 1, "name": "John"}));
2103        assert!(media.schema.is_some());
2104        assert!(media.example.is_some());
2105    }
2106
2107    #[test]
2108    fn test_request_body_with_multiple_content_types() {
2109        let mut body = RequestBody::new("User data", "#/components/schemas/User", true);
2110        body = body.with_content(
2111            "application/xml",
2112            MediaType::with_schema_ref("#/components/schemas/User"),
2113        );
2114        assert_eq!(body.content.len(), 2);
2115        assert!(body.content.contains_key("application/json"));
2116        assert!(body.content.contains_key("application/xml"));
2117    }
2118
2119    #[test]
2120    fn test_example_builder() {
2121        let ex = Example::new(serde_json::json!({"id": 1, "name": "John"}))
2122            .with_summary("Sample user")
2123            .with_description("A typical user object");
2124        assert_eq!(ex.summary.as_deref(), Some("Sample user"));
2125        assert_eq!(ex.description.as_deref(), Some("A typical user object"));
2126        assert!(ex.value.is_some());
2127    }
2128
2129    #[test]
2130    fn test_full_spec_generation_with_all_components() {
2131        let mut g = OpenAPIGenerator::new();
2132
2133        // 注册 Schema
2134        let user_schema = Schema::object(
2135            ObjectType::new()
2136                .with_required_property("id", Schema::integer())
2137                .with_required_property("name", Schema::string())
2138                .with_property("email", Schema::string().with_format("email")),
2139        );
2140        g.register_schema("User", user_schema);
2141
2142        // 注册 SecurityScheme
2143        g.register_security_scheme("bearerAuth", SecurityScheme::bearer(true));
2144
2145        // 添加 Tags
2146        g.add_tag(Tag::new("users").with_description("User management"));
2147
2148        // 添加 Servers
2149        g.add_server(Server::new("https://api.example.com").with_description("Production"));
2150
2151        // 注册路径
2152        g.register_path(
2153            "/users",
2154            PathInfo::new("GET", "List users")
2155                .with_tag("users")
2156                .with_response_schema("200", "OK", "#/components/schemas/User")
2157                .with_security(SecurityRequirement::new("bearerAuth"))
2158                .with_operation_id("listUsers"),
2159        );
2160        g.register_path(
2161            "/users",
2162            PathInfo::new("POST", "Create user")
2163                .with_tag("users")
2164                .with_request_body_ref("User to create", "#/components/schemas/User", true)
2165                .with_response_schema("201", "Created", "#/components/schemas/User")
2166                .with_operation_id("createUser"),
2167        );
2168
2169        let spec = g.generate();
2170        let json = spec.to_json_string();
2171        let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
2172
2173        assert_eq!(parsed["openapi"], "3.0.0");
2174        assert!(parsed["components"]["schemas"]["User"].is_object());
2175        assert!(parsed["components"]["securitySchemes"]["bearerAuth"].is_object());
2176        assert_eq!(parsed["tags"][0]["name"], "users");
2177        assert_eq!(parsed["servers"][0]["url"], "https://api.example.com");
2178        assert!(parsed["paths"]["/users"]["get"].is_object());
2179        assert!(parsed["paths"]["/users"]["post"].is_object());
2180        assert_eq!(parsed["paths"]["/users"]["get"]["operationId"], "listUsers");
2181        assert_eq!(
2182            parsed["paths"]["/users"]["post"]["operationId"],
2183            "createUser"
2184        );
2185    }
2186
2187    #[test]
2188    fn test_model_to_openapi_schema() {
2189        use sz_orm_core::Model;
2190
2191        struct User;
2192        impl Model for User {
2193            type PrimaryKey = i64;
2194            fn table_name() -> &'static str {
2195                "users"
2196            }
2197            fn pk(&self) -> Self::PrimaryKey {
2198                0
2199            }
2200            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2201            fn fields() -> Vec<(&'static str, &'static str)> {
2202                vec![("id", "integer"), ("name", "string"), ("active", "boolean")]
2203            }
2204        }
2205
2206        let schema = model_to_openapi_schema::<User>();
2207        let json = serde_json::to_value(&schema).unwrap();
2208        assert_eq!(json["type"], "object");
2209        assert_eq!(json["description"], "users");
2210        assert_eq!(json["properties"]["id"]["type"], "integer");
2211        assert_eq!(json["properties"]["name"]["type"], "string");
2212        assert_eq!(json["properties"]["active"]["type"], "boolean");
2213    }
2214
2215    #[test]
2216    fn test_model_to_openapi_schema_empty_fields() {
2217        use sz_orm_core::Model;
2218
2219        struct Empty;
2220        impl Model for Empty {
2221            type PrimaryKey = i64;
2222            fn table_name() -> &'static str {
2223                "empty"
2224            }
2225            fn pk(&self) -> Self::PrimaryKey {
2226                0
2227            }
2228            fn set_pk(&mut self, _pk: Self::PrimaryKey) {}
2229        }
2230
2231        let schema = model_to_openapi_schema::<Empty>();
2232        let json = serde_json::to_value(&schema).unwrap();
2233        assert_eq!(json["type"], "object");
2234        assert_eq!(json["description"], "empty");
2235        // 默认 fields() 返回空,properties 不应出现
2236        assert!(
2237            json.get("properties").is_none() || json["properties"].as_object().unwrap().is_empty()
2238        );
2239    }
2240}
2241
2242#[cfg(feature = "openapi-reverse")]
2243pub mod reverse;