Skip to main content

spikard_http/openapi/
schema_conversion.rs

1//! JSON Schema to OpenAPI schema conversion utilities
2
3use utoipa::openapi::{RefOr, Schema};
4
5/// Convert serde_json::Value (JSON Schema) to utoipa Schema
6/// OpenAPI 3.1.0 is fully compatible with JSON Schema Draft 2020-12
7pub fn json_value_to_schema(value: &serde_json::Value) -> Result<RefOr<Schema>, String> {
8    if let Some(type_str) = value.get("type").and_then(|t| t.as_str()) {
9        match type_str {
10            "object" => {
11                let mut object_schema = utoipa::openapi::ObjectBuilder::new();
12
13                if let Some(properties) = value.get("properties").and_then(|p| p.as_object()) {
14                    for (prop_name, prop_schema) in properties {
15                        let prop_openapi_schema = json_value_to_schema(prop_schema)?;
16                        object_schema = object_schema.property(prop_name, prop_openapi_schema);
17                    }
18                }
19
20                if let Some(required) = value.get("required").and_then(|r| r.as_array()) {
21                    for field in required {
22                        if let Some(field_name) = field.as_str() {
23                            object_schema = object_schema.required(field_name);
24                        }
25                    }
26                }
27
28                Ok(RefOr::T(Schema::Object(object_schema.build())))
29            }
30            "array" => {
31                let mut array_schema = utoipa::openapi::ArrayBuilder::new();
32
33                if let Some(items) = value.get("items") {
34                    let items_schema = json_value_to_schema(items)?;
35                    array_schema = array_schema.items(items_schema);
36                }
37
38                Ok(RefOr::T(Schema::Array(array_schema.build())))
39            }
40            "string" => {
41                let mut schema_type = utoipa::openapi::schema::Type::String;
42
43                if let Some(format) = value.get("format").and_then(|f| f.as_str()) {
44                    match format {
45                        "date-time" => schema_type = utoipa::openapi::schema::Type::String,
46                        "date" => schema_type = utoipa::openapi::schema::Type::String,
47                        "email" => schema_type = utoipa::openapi::schema::Type::String,
48                        "uri" => schema_type = utoipa::openapi::schema::Type::String,
49                        _ => {}
50                    }
51                }
52
53                Ok(RefOr::T(Schema::Object(
54                    utoipa::openapi::ObjectBuilder::new().schema_type(schema_type).build(),
55                )))
56            }
57            "integer" => Ok(RefOr::T(Schema::Object(
58                utoipa::openapi::ObjectBuilder::new()
59                    .schema_type(utoipa::openapi::schema::Type::Integer)
60                    .build(),
61            ))),
62            "number" => Ok(RefOr::T(Schema::Object(
63                utoipa::openapi::ObjectBuilder::new()
64                    .schema_type(utoipa::openapi::schema::Type::Number)
65                    .build(),
66            ))),
67            "boolean" => Ok(RefOr::T(Schema::Object(
68                utoipa::openapi::ObjectBuilder::new()
69                    .schema_type(utoipa::openapi::schema::Type::Boolean)
70                    .build(),
71            ))),
72            _ => Err(format!("Unsupported schema type: {}", type_str)),
73        }
74    } else {
75        Ok(RefOr::T(Schema::Object(utoipa::openapi::ObjectBuilder::new().build())))
76    }
77}
78
79/// Convert JSON Schema to OpenAPI RequestBody
80pub fn json_schema_to_request_body(
81    schema: &serde_json::Value,
82) -> Result<utoipa::openapi::request_body::RequestBody, String> {
83    use utoipa::openapi::content::ContentBuilder;
84
85    let openapi_schema = json_value_to_schema(schema)?;
86
87    let content = ContentBuilder::new().schema(Some(openapi_schema)).build();
88
89    let mut request_body = utoipa::openapi::request_body::RequestBody::new();
90    request_body.content.insert("application/json".to_string(), content);
91
92    request_body.required = Some(utoipa::openapi::Required::True);
93
94    Ok(request_body)
95}
96
97/// Convert JSON Schema to OpenAPI Response
98pub fn json_schema_to_response(schema: &serde_json::Value) -> Result<utoipa::openapi::Response, String> {
99    use utoipa::openapi::content::ContentBuilder;
100
101    let openapi_schema = json_value_to_schema(schema)?;
102
103    let content = ContentBuilder::new().schema(Some(openapi_schema)).build();
104
105    let mut response = utoipa::openapi::Response::new("Successful response");
106    response.content.insert("application/json".to_string(), content);
107
108    Ok(response)
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn test_json_value_to_schema_string() {
117        let schema_json = serde_json::json!({
118            "type": "string"
119        });
120
121        let result = json_value_to_schema(&schema_json);
122        assert!(result.is_ok());
123    }
124
125    #[test]
126    fn test_json_value_to_schema_integer() {
127        let schema_json = serde_json::json!({
128            "type": "integer"
129        });
130
131        let result = json_value_to_schema(&schema_json);
132        assert!(result.is_ok());
133    }
134
135    #[test]
136    fn test_json_value_to_schema_number() {
137        let schema_json = serde_json::json!({
138            "type": "number"
139        });
140
141        let result = json_value_to_schema(&schema_json);
142        assert!(result.is_ok());
143    }
144
145    #[test]
146    fn test_json_value_to_schema_boolean() {
147        let schema_json = serde_json::json!({
148            "type": "boolean"
149        });
150
151        let result = json_value_to_schema(&schema_json);
152        assert!(result.is_ok());
153    }
154
155    #[test]
156    fn test_json_value_to_schema_object() {
157        let schema_json = serde_json::json!({
158            "type": "object",
159            "properties": {
160                "name": { "type": "string" },
161                "age": { "type": "integer" }
162            },
163            "required": ["name"]
164        });
165
166        let result = json_value_to_schema(&schema_json);
167        assert!(result.is_ok());
168
169        if let Ok(RefOr::T(Schema::Object(obj))) = result {
170            assert!(obj.properties.contains_key("name"));
171            assert!(obj.properties.contains_key("age"));
172            assert!(obj.required.contains(&"name".to_string()));
173        } else {
174            panic!("Expected Object schema");
175        }
176    }
177
178    #[test]
179    fn test_json_value_to_schema_array() {
180        let schema_json = serde_json::json!({
181            "type": "array",
182            "items": {
183                "type": "string"
184            }
185        });
186
187        let result = json_value_to_schema(&schema_json);
188        assert!(result.is_ok());
189
190        if let Ok(RefOr::T(Schema::Array(_))) = result {
191        } else {
192            panic!("Expected Array schema");
193        }
194    }
195
196    #[test]
197    fn test_json_value_to_schema_nested_object() {
198        let schema_json = serde_json::json!({
199            "type": "object",
200            "properties": {
201                "user": {
202                    "type": "object",
203                    "properties": {
204                        "name": { "type": "string" },
205                        "email": { "type": "string" }
206                    }
207                }
208            }
209        });
210
211        let result = json_value_to_schema(&schema_json);
212        assert!(result.is_ok());
213    }
214
215    #[test]
216    fn test_json_schema_to_request_body() {
217        let schema_json = serde_json::json!({
218            "type": "object",
219            "properties": {
220                "title": { "type": "string" },
221                "count": { "type": "integer" }
222            },
223            "required": ["title"]
224        });
225
226        let result = json_schema_to_request_body(&schema_json);
227        assert!(result.is_ok());
228
229        let request_body = result.unwrap();
230        assert!(request_body.content.contains_key("application/json"));
231        assert!(matches!(request_body.required, Some(utoipa::openapi::Required::True)));
232    }
233
234    #[test]
235    fn test_json_schema_to_request_body_array() {
236        let schema_json = serde_json::json!({
237            "type": "array",
238            "items": {
239                "type": "object",
240                "properties": {
241                    "id": { "type": "integer" }
242                }
243            }
244        });
245
246        let result = json_schema_to_request_body(&schema_json);
247        assert!(result.is_ok());
248
249        let request_body = result.unwrap();
250        assert!(request_body.content.contains_key("application/json"));
251    }
252
253    #[test]
254    fn test_json_schema_to_response() {
255        let schema_json = serde_json::json!({
256            "type": "object",
257            "properties": {
258                "id": { "type": "integer" },
259                "name": { "type": "string" }
260            }
261        });
262
263        let result = json_schema_to_response(&schema_json);
264        assert!(result.is_ok());
265
266        let response = result.unwrap();
267        assert!(response.content.contains_key("application/json"));
268        assert_eq!(response.description, "Successful response");
269    }
270
271    #[test]
272    fn test_json_schema_to_response_array() {
273        let schema_json = serde_json::json!({
274            "type": "array",
275            "items": {
276                "type": "string"
277            }
278        });
279
280        let result = json_schema_to_response(&schema_json);
281        assert!(result.is_ok());
282
283        let response = result.unwrap();
284        assert!(response.content.contains_key("application/json"));
285    }
286
287    #[test]
288    fn test_json_value_to_schema_string_with_format() {
289        let schema_json = serde_json::json!({
290            "type": "string",
291            "format": "date-time"
292        });
293
294        let result = json_value_to_schema(&schema_json);
295        assert!(result.is_ok());
296    }
297
298    #[test]
299    fn test_json_schema_to_request_body_empty_object() {
300        let schema_json = serde_json::json!({
301            "type": "object",
302            "properties": {}
303        });
304
305        let result = json_schema_to_request_body(&schema_json);
306        assert!(result.is_ok());
307    }
308
309    #[test]
310    fn test_circular_reference_simple_cycle() {
311        let schema_json = serde_json::json!({
312            "type": "object",
313            "properties": {
314                "id": { "type": "integer" },
315                "parent": { "$ref": "#/properties/id" }
316            }
317        });
318
319        let result = json_value_to_schema(&schema_json);
320        assert!(result.is_ok());
321    }
322
323    #[test]
324    fn test_self_referential_schema_direct() {
325        let schema_json = serde_json::json!({
326            "type": "object",
327            "properties": {
328                "value": { "type": "string" },
329                "self": { "$ref": "#" }
330            }
331        });
332
333        let result = json_value_to_schema(&schema_json);
334        assert!(result.is_ok());
335    }
336
337    #[test]
338    fn test_deeply_nested_object_10_levels() {
339        let schema_json = serde_json::json!({
340            "type": "object",
341            "properties": {
342                "l1": {
343                    "type": "object",
344                    "properties": {
345                        "l2": {
346                            "type": "object",
347                            "properties": {
348                                "l3": {
349                                    "type": "object",
350                                    "properties": {
351                                        "l4": {
352                                            "type": "object",
353                                            "properties": {
354                                                "l5": {
355                                                    "type": "object",
356                                                    "properties": {
357                                                        "l6": {
358                                                            "type": "object",
359                                                            "properties": {
360                                                                "l7": {
361                                                                    "type": "object",
362                                                                    "properties": {
363                                                                        "l8": {
364                                                                            "type": "object",
365                                                                            "properties": {
366                                                                                "l9": {
367                                                                                    "type": "object",
368                                                                                    "properties": {
369                                                                                        "l10": { "type": "string" }
370                                                                                    }
371                                                                                }
372                                                                            }
373                                                                        }
374                                                                    }
375                                                                }
376                                                            }
377                                                        }
378                                                    }
379                                                }
380                                            }
381                                        }
382                                    }
383                                }
384                            }
385                        }
386                    }
387                }
388            }
389        });
390
391        let result = json_value_to_schema(&schema_json);
392        assert!(result.is_ok(), "Deep nesting should not cause stack overflow");
393    }
394
395    #[test]
396    fn test_deeply_nested_array_5_levels() {
397        let schema_json = serde_json::json!({
398            "type": "array",
399            "items": {
400                "type": "array",
401                "items": {
402                    "type": "array",
403                    "items": {
404                        "type": "array",
405                        "items": {
406                            "type": "array",
407                            "items": { "type": "string" }
408                        }
409                    }
410                }
411            }
412        });
413
414        let result = json_value_to_schema(&schema_json);
415        assert!(result.is_ok());
416    }
417
418    #[test]
419    fn test_type_coercion_integer_to_number() {
420        let schema_json = serde_json::json!({
421            "type": "integer"
422        });
423
424        let result = json_value_to_schema(&schema_json);
425        assert!(result.is_ok());
426        if let Ok(RefOr::T(Schema::Object(obj))) = result {
427            assert!(matches!(
428                obj.schema_type,
429                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Integer)
430            ));
431        } else {
432            panic!("Expected Object schema with Type::Integer");
433        }
434    }
435
436    #[test]
437    fn test_type_coercion_number_vs_integer() {
438        let int_schema = serde_json::json!({ "type": "integer" });
439        let num_schema = serde_json::json!({ "type": "number" });
440
441        let int_result = json_value_to_schema(&int_schema);
442        let num_result = json_value_to_schema(&num_schema);
443
444        assert!(int_result.is_ok());
445        assert!(num_result.is_ok());
446
447        if let (Ok(RefOr::T(Schema::Object(int_obj))), Ok(RefOr::T(Schema::Object(num_obj)))) = (int_result, num_result)
448        {
449            assert!(matches!(
450                int_obj.schema_type,
451                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Integer)
452            ));
453            assert!(matches!(
454                num_obj.schema_type,
455                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Number)
456            ));
457        }
458    }
459
460    #[test]
461    fn test_nullable_property_in_object() {
462        let schema_json = serde_json::json!({
463            "type": "object",
464            "properties": {
465                "id": { "type": "integer" },
466                "optional_field": { "type": "string" }
467            },
468            "required": ["id"]
469        });
470
471        let result = json_value_to_schema(&schema_json);
472        assert!(result.is_ok());
473
474        if let Ok(RefOr::T(Schema::Object(obj))) = result {
475            assert!(obj.required.contains(&"id".to_string()));
476            assert!(!obj.required.contains(&"optional_field".to_string()));
477        } else {
478            panic!("Expected Object schema");
479        }
480    }
481
482    #[test]
483    fn test_required_array_with_multiple_fields() {
484        let schema_json = serde_json::json!({
485            "type": "object",
486            "properties": {
487                "id": { "type": "integer" },
488                "name": { "type": "string" },
489                "email": { "type": "string" },
490                "optional": { "type": "string" }
491            },
492            "required": ["id", "name", "email"]
493        });
494
495        let result = json_value_to_schema(&schema_json);
496        assert!(result.is_ok());
497
498        if let Ok(RefOr::T(Schema::Object(obj))) = result {
499            assert!(obj.required.contains(&"id".to_string()));
500            assert!(obj.required.contains(&"name".to_string()));
501            assert!(obj.required.contains(&"email".to_string()));
502            assert!(!obj.required.contains(&"optional".to_string()));
503        }
504    }
505
506    #[test]
507    fn test_format_uuid() {
508        let schema_json = serde_json::json!({
509            "type": "string",
510            "format": "uuid"
511        });
512
513        let result = json_value_to_schema(&schema_json);
514        assert!(result.is_ok());
515    }
516
517    #[test]
518    fn test_format_email() {
519        let schema_json = serde_json::json!({
520            "type": "string",
521            "format": "email"
522        });
523
524        let result = json_value_to_schema(&schema_json);
525        assert!(result.is_ok());
526    }
527
528    #[test]
529    fn test_format_date_time() {
530        let schema_json = serde_json::json!({
531            "type": "string",
532            "format": "date-time"
533        });
534
535        let result = json_value_to_schema(&schema_json);
536        assert!(result.is_ok());
537
538        if let Ok(RefOr::T(Schema::Object(obj))) = result {
539            assert!(matches!(
540                obj.schema_type,
541                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::String)
542            ));
543        }
544    }
545
546    #[test]
547    fn test_format_date() {
548        let schema_json = serde_json::json!({
549            "type": "string",
550            "format": "date"
551        });
552
553        let result = json_value_to_schema(&schema_json);
554        assert!(result.is_ok());
555    }
556
557    #[test]
558    fn test_format_uri() {
559        let schema_json = serde_json::json!({
560            "type": "string",
561            "format": "uri"
562        });
563
564        let result = json_value_to_schema(&schema_json);
565        assert!(result.is_ok());
566    }
567
568    #[test]
569    fn test_format_unknown_custom_format() {
570        let schema_json = serde_json::json!({
571            "type": "string",
572            "format": "custom-format"
573        });
574
575        let result = json_value_to_schema(&schema_json);
576        assert!(result.is_ok(), "Unknown formats should be gracefully handled");
577    }
578
579    #[test]
580    fn test_array_of_objects() {
581        let schema_json = serde_json::json!({
582            "type": "array",
583            "items": {
584                "type": "object",
585                "properties": {
586                    "id": { "type": "integer" },
587                    "name": { "type": "string" }
588                },
589                "required": ["id"]
590            }
591        });
592
593        let result = json_value_to_schema(&schema_json);
594        assert!(result.is_ok());
595
596        if let Ok(RefOr::T(Schema::Array(_))) = result {
597        } else {
598            panic!("Expected Array schema");
599        }
600    }
601
602    #[test]
603    fn test_array_of_arrays_of_objects() {
604        let schema_json = serde_json::json!({
605            "type": "array",
606            "items": {
607                "type": "array",
608                "items": {
609                    "type": "object",
610                    "properties": {
611                        "value": { "type": "string" }
612                    }
613                }
614            }
615        });
616
617        let result = json_value_to_schema(&schema_json);
618        assert!(result.is_ok());
619    }
620
621    #[test]
622    fn test_object_with_additional_properties_true() {
623        let schema_json = serde_json::json!({
624            "type": "object",
625            "properties": {
626                "id": { "type": "integer" }
627            },
628            "additionalProperties": true
629        });
630
631        let result = json_value_to_schema(&schema_json);
632        assert!(result.is_ok());
633    }
634
635    #[test]
636    fn test_object_with_additional_properties_false() {
637        let schema_json = serde_json::json!({
638            "type": "object",
639            "properties": {
640                "id": { "type": "integer" }
641            },
642            "additionalProperties": false
643        });
644
645        let result = json_value_to_schema(&schema_json);
646        assert!(result.is_ok(), "additionalProperties:false should not cause errors");
647    }
648
649    #[test]
650    fn test_object_with_additional_properties_schema() {
651        let schema_json = serde_json::json!({
652            "type": "object",
653            "properties": {
654                "id": { "type": "integer" }
655            },
656            "additionalProperties": { "type": "string" }
657        });
658
659        let result = json_value_to_schema(&schema_json);
660        assert!(result.is_ok());
661    }
662
663    #[test]
664    fn test_empty_schema() {
665        let schema_json = serde_json::json!({});
666
667        let result = json_value_to_schema(&schema_json);
668        assert!(result.is_ok(), "Empty schema should create basic object");
669    }
670
671    #[test]
672    fn test_schema_with_only_type_field() {
673        let schema_json = serde_json::json!({
674            "type": "object"
675        });
676
677        let result = json_value_to_schema(&schema_json);
678        assert!(result.is_ok());
679
680        if let Ok(RefOr::T(Schema::Object(obj))) = result {
681            assert!(obj.properties.is_empty());
682        }
683    }
684
685    #[test]
686    fn test_array_without_items_schema() {
687        let schema_json = serde_json::json!({
688            "type": "array"
689        });
690
691        let result = json_value_to_schema(&schema_json);
692        assert!(result.is_ok());
693    }
694
695    #[test]
696    fn test_object_with_mixed_property_types() {
697        let schema_json = serde_json::json!({
698            "type": "object",
699            "properties": {
700                "id": { "type": "integer" },
701                "name": { "type": "string" },
702                "active": { "type": "boolean" },
703                "score": { "type": "number" },
704                "tags": {
705                    "type": "array",
706                    "items": { "type": "string" }
707                },
708                "metadata": {
709                    "type": "object",
710                    "properties": {
711                        "created": { "type": "string", "format": "date-time" }
712                    }
713                }
714            }
715        });
716
717        let result = json_value_to_schema(&schema_json);
718        assert!(result.is_ok());
719
720        if let Ok(RefOr::T(Schema::Object(obj))) = result {
721            assert_eq!(obj.properties.len(), 6);
722            assert!(obj.properties.contains_key("id"));
723            assert!(obj.properties.contains_key("name"));
724            assert!(obj.properties.contains_key("active"));
725            assert!(obj.properties.contains_key("score"));
726            assert!(obj.properties.contains_key("tags"));
727            assert!(obj.properties.contains_key("metadata"));
728        }
729    }
730
731    #[test]
732    fn test_nullable_complex_types() {
733        let schema_json = serde_json::json!({
734            "type": "object",
735            "properties": {
736                "user": {
737                    "oneOf": [
738                        { "type": "object", "properties": { "id": { "type": "integer" } } },
739                        { "type": "null" }
740                    ]
741                }
742            }
743        });
744
745        let result = json_value_to_schema(&schema_json);
746        assert!(result.is_ok());
747    }
748
749    #[test]
750    fn test_unsupported_type_error() {
751        let schema_json = serde_json::json!({
752            "type": "unsupported_type"
753        });
754
755        let result = json_value_to_schema(&schema_json);
756        assert!(result.is_err());
757        if let Err(err) = result {
758            assert!(err.contains("Unsupported schema type"));
759        }
760    }
761
762    #[test]
763    fn test_required_with_non_string_elements() {
764        let schema_json = serde_json::json!({
765            "type": "object",
766            "properties": {
767                "a": { "type": "string" },
768                "b": { "type": "integer" }
769            },
770            "required": [123, null, "a"]
771        });
772
773        let result = json_value_to_schema(&schema_json);
774        assert!(result.is_ok(), "Non-string elements in required should be skipped");
775
776        if let Ok(RefOr::T(Schema::Object(obj))) = result {
777            assert!(obj.required.contains(&"a".to_string()));
778            assert_eq!(obj.required.len(), 1);
779        }
780    }
781
782    #[test]
783    fn test_properties_with_null_values() {
784        let schema_json = serde_json::json!({
785            "type": "object",
786            "properties": {
787                "valid": { "type": "string" },
788                "null_value": null
789            }
790        });
791
792        let result = json_value_to_schema(&schema_json);
793        assert!(result.is_ok());
794    }
795
796    #[test]
797    fn test_object_with_empty_required_array() {
798        let schema_json = serde_json::json!({
799            "type": "object",
800            "properties": {
801                "id": { "type": "integer" },
802                "name": { "type": "string" }
803            },
804            "required": []
805        });
806
807        let result = json_value_to_schema(&schema_json);
808        assert!(result.is_ok());
809
810        if let Ok(RefOr::T(Schema::Object(obj))) = result {
811            assert!(obj.required.is_empty());
812        }
813    }
814
815    #[test]
816    fn test_request_body_with_missing_items() {
817        let schema_json = serde_json::json!({
818            "type": "array"
819        });
820
821        let result = json_schema_to_request_body(&schema_json);
822        assert!(result.is_ok());
823    }
824
825    #[test]
826    fn test_response_with_all_scalar_types() {
827        let types = vec!["string", "integer", "number", "boolean"];
828
829        for type_name in types {
830            let schema_json = serde_json::json!({
831                "type": type_name
832            });
833
834            let result = json_schema_to_response(&schema_json);
835            assert!(
836                result.is_ok(),
837                "Response schema with type '{}' should succeed",
838                type_name
839            );
840
841            let response = result.unwrap();
842            assert!(response.content.contains_key("application/json"));
843        }
844    }
845
846    #[test]
847    fn test_string_format_datetime_creates_string_type() {
848        let schema_json = serde_json::json!({
849            "type": "string",
850            "format": "date-time"
851        });
852
853        let result = json_value_to_schema(&schema_json);
854        assert!(result.is_ok());
855
856        if let Ok(RefOr::T(Schema::Object(obj))) = result {
857            assert!(matches!(
858                obj.schema_type,
859                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::String)
860            ));
861        }
862    }
863
864    #[test]
865    fn test_string_format_email_creates_string_type() {
866        let schema_json = serde_json::json!({
867            "type": "string",
868            "format": "email"
869        });
870
871        let result = json_value_to_schema(&schema_json);
872        assert!(result.is_ok());
873
874        if let Ok(RefOr::T(Schema::Object(obj))) = result {
875            assert!(matches!(
876                obj.schema_type,
877                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::String)
878            ));
879        }
880    }
881
882    #[test]
883    fn test_string_format_uri_creates_string_type() {
884        let schema_json = serde_json::json!({
885            "type": "string",
886            "format": "uri"
887        });
888
889        let result = json_value_to_schema(&schema_json);
890        assert!(result.is_ok());
891
892        if let Ok(RefOr::T(Schema::Object(obj))) = result {
893            assert!(matches!(
894                obj.schema_type,
895                utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::String)
896            ));
897        }
898    }
899
900    #[test]
901    fn test_array_nested_with_mixed_object_types() {
902        let schema_json = serde_json::json!({
903            "type": "array",
904            "items": {
905                "type": "array",
906                "items": {
907                    "type": "object",
908                    "properties": {
909                        "id": { "type": "integer" },
910                        "tags": {
911                            "type": "array",
912                            "items": { "type": "string" }
913                        }
914                    },
915                    "required": ["id"]
916                }
917            }
918        });
919
920        let result = json_value_to_schema(&schema_json);
921        assert!(result.is_ok());
922    }
923
924    #[test]
925    fn test_object_with_deeply_nested_arrays() {
926        let schema_json = serde_json::json!({
927            "type": "object",
928            "properties": {
929                "level1": {
930                    "type": "array",
931                    "items": {
932                        "type": "array",
933                        "items": {
934                            "type": "array",
935                            "items": {
936                                "type": "string"
937                            }
938                        }
939                    }
940                }
941            }
942        });
943
944        let result = json_value_to_schema(&schema_json);
945        assert!(result.is_ok());
946    }
947
948    #[test]
949    fn test_object_with_many_properties() {
950        let mut properties = serde_json::Map::new();
951        for i in 0..50 {
952            properties.insert(
953                format!("prop_{}", i),
954                serde_json::json!({ "type": if i % 2 == 0 { "string" } else { "integer" } }),
955            );
956        }
957
958        let schema_json = serde_json::json!({
959            "type": "object",
960            "properties": properties
961        });
962
963        let result = json_value_to_schema(&schema_json);
964        assert!(result.is_ok());
965
966        if let Ok(RefOr::T(Schema::Object(obj))) = result {
967            assert_eq!(obj.properties.len(), 50);
968        }
969    }
970
971    #[test]
972    fn test_required_field_not_in_properties() {
973        let schema_json = serde_json::json!({
974            "type": "object",
975            "properties": {
976                "id": { "type": "integer" }
977            },
978            "required": ["id", "missing_field"]
979        });
980
981        let result = json_value_to_schema(&schema_json);
982        assert!(result.is_ok());
983
984        if let Ok(RefOr::T(Schema::Object(obj))) = result {
985            assert!(obj.required.contains(&"id".to_string()));
986            assert!(obj.required.contains(&"missing_field".to_string()));
987        }
988    }
989
990    #[test]
991    fn test_empty_object_with_required_fields() {
992        let schema_json = serde_json::json!({
993            "type": "object",
994            "properties": {},
995            "required": ["field1", "field2"]
996        });
997
998        let result = json_value_to_schema(&schema_json);
999        assert!(result.is_ok());
1000
1001        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1002            assert!(obj.required.contains(&"field1".to_string()));
1003            assert!(obj.required.contains(&"field2".to_string()));
1004        }
1005    }
1006
1007    #[test]
1008    fn test_array_items_missing_completely() {
1009        let schema_json = serde_json::json!({
1010            "type": "array"
1011        });
1012
1013        let result = json_value_to_schema(&schema_json);
1014        assert!(result.is_ok());
1015
1016        if let Ok(RefOr::T(Schema::Array(_arr))) = result {
1017        } else {
1018            panic!("Expected Array schema");
1019        }
1020    }
1021
1022    #[test]
1023    fn test_nested_object_mixed_required_across_levels() {
1024        let schema_json = serde_json::json!({
1025            "type": "object",
1026            "properties": {
1027                "level1": {
1028                    "type": "object",
1029                    "properties": {
1030                        "level2": {
1031                            "type": "object",
1032                            "properties": {
1033                                "value": { "type": "string" }
1034                            },
1035                            "required": ["value"]
1036                        }
1037                    },
1038                    "required": ["level2"]
1039                }
1040            },
1041            "required": ["level1"]
1042        });
1043
1044        let result = json_value_to_schema(&schema_json);
1045        assert!(result.is_ok());
1046
1047        if let Ok(RefOr::T(Schema::Object(outer))) = result {
1048            assert!(outer.required.contains(&"level1".to_string()));
1049        }
1050    }
1051
1052    #[test]
1053    fn test_string_format_all_known_formats() {
1054        let formats = vec!["date-time", "date", "email", "uri"];
1055
1056        for format in formats {
1057            let schema_json = serde_json::json!({
1058                "type": "string",
1059                "format": format
1060            });
1061
1062            let result = json_value_to_schema(&schema_json);
1063            assert!(result.is_ok(), "Format '{}' should be handled", format);
1064        }
1065    }
1066
1067    #[test]
1068    fn test_request_body_complex_nested_structure() {
1069        let schema_json = serde_json::json!({
1070            "type": "object",
1071            "properties": {
1072                "user": {
1073                    "type": "object",
1074                    "properties": {
1075                        "id": { "type": "integer" },
1076                        "profile": {
1077                            "type": "object",
1078                            "properties": {
1079                                "name": { "type": "string" },
1080                                "contacts": {
1081                                    "type": "array",
1082                                    "items": {
1083                                        "type": "object",
1084                                        "properties": {
1085                                            "email": { "type": "string" },
1086                                            "phone": { "type": "string" }
1087                                        }
1088                                    }
1089                                }
1090                            }
1091                        }
1092                    }
1093                }
1094            },
1095            "required": ["user"]
1096        });
1097
1098        let result = json_schema_to_request_body(&schema_json);
1099        assert!(result.is_ok());
1100
1101        let request_body = result.unwrap();
1102        assert!(request_body.content.contains_key("application/json"));
1103        assert!(matches!(request_body.required, Some(utoipa::openapi::Required::True)));
1104    }
1105
1106    #[test]
1107    fn test_response_array_of_complex_objects() {
1108        let schema_json = serde_json::json!({
1109            "type": "array",
1110            "items": {
1111                "type": "object",
1112                "properties": {
1113                    "id": { "type": "integer" },
1114                    "name": { "type": "string" },
1115                    "created_at": { "type": "string", "format": "date-time" }
1116                },
1117                "required": ["id", "name"]
1118            }
1119        });
1120
1121        let result = json_schema_to_response(&schema_json);
1122        assert!(result.is_ok());
1123
1124        let response = result.unwrap();
1125        assert!(response.content.contains_key("application/json"));
1126        assert_eq!(response.description, "Successful response");
1127    }
1128
1129    #[test]
1130    fn test_object_property_with_format_but_type_string() {
1131        let schema_json = serde_json::json!({
1132            "type": "object",
1133            "properties": {
1134                "timestamp": {
1135                    "type": "string",
1136                    "format": "date-time"
1137                },
1138                "email": {
1139                    "type": "string",
1140                    "format": "email"
1141                }
1142            }
1143        });
1144
1145        let result = json_value_to_schema(&schema_json);
1146        assert!(result.is_ok());
1147
1148        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1149            assert!(obj.properties.contains_key("timestamp"));
1150            assert!(obj.properties.contains_key("email"));
1151        }
1152    }
1153
1154    #[test]
1155    fn test_duplicate_required_fields() {
1156        let schema_json = serde_json::json!({
1157            "type": "object",
1158            "properties": {
1159                "id": { "type": "integer" },
1160                "name": { "type": "string" }
1161            },
1162            "required": ["id", "name", "id", "name"]
1163        });
1164
1165        let result = json_value_to_schema(&schema_json);
1166        assert!(result.is_ok());
1167
1168        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1169            assert!(obj.required.contains(&"id".to_string()));
1170            assert!(obj.required.contains(&"name".to_string()));
1171        }
1172    }
1173
1174    #[test]
1175    fn test_object_with_very_long_property_names() {
1176        let long_name = "very_long_property_name_that_is_256_characters_or_more_\
1177            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
1178            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
1179            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\
1180            aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1181
1182        let schema_json = serde_json::json!({
1183            "type": "object",
1184            "properties": {
1185                long_name: { "type": "string" }
1186            }
1187        });
1188
1189        let result = json_value_to_schema(&schema_json);
1190        assert!(result.is_ok());
1191
1192        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1193            assert!(obj.properties.contains_key(long_name));
1194        }
1195    }
1196
1197    #[test]
1198    fn test_arrays_of_all_primitive_types() {
1199        let types = vec!["string", "integer", "number", "boolean"];
1200
1201        for type_name in types {
1202            let schema_json = serde_json::json!({
1203                "type": "array",
1204                "items": { "type": type_name }
1205            });
1206
1207            let result = json_value_to_schema(&schema_json);
1208            assert!(result.is_ok(), "Array of {} should be handled correctly", type_name);
1209        }
1210    }
1211
1212    #[test]
1213    fn test_large_object_with_mixed_required_optional() {
1214        let mut properties = serde_json::Map::new();
1215        let mut required = Vec::new();
1216
1217        for i in 0..30 {
1218            properties.insert(format!("field_{}", i), serde_json::json!({ "type": "string" }));
1219            if i % 3 == 0 {
1220                required.push(format!("field_{}", i));
1221            }
1222        }
1223
1224        let schema_json = serde_json::json!({
1225            "type": "object",
1226            "properties": properties,
1227            "required": required
1228        });
1229
1230        let result = json_value_to_schema(&schema_json);
1231        assert!(result.is_ok());
1232
1233        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1234            assert!(obj.required.len() >= 9);
1235            assert!(obj.properties.len() == 30);
1236        }
1237    }
1238
1239    #[test]
1240    fn test_object_no_properties_with_required() {
1241        let schema_json = serde_json::json!({
1242            "type": "object",
1243            "required": ["name", "age"]
1244        });
1245
1246        let result = json_value_to_schema(&schema_json);
1247        assert!(result.is_ok());
1248
1249        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1250            assert!(obj.required.contains(&"name".to_string()));
1251            assert!(obj.required.contains(&"age".to_string()));
1252        }
1253    }
1254
1255    #[test]
1256    fn test_request_body_all_optional_fields() {
1257        let schema_json = serde_json::json!({
1258            "type": "object",
1259            "properties": {
1260                "name": { "type": "string" },
1261                "email": { "type": "string" },
1262                "age": { "type": "integer" }
1263            }
1264        });
1265
1266        let result = json_schema_to_request_body(&schema_json);
1267        assert!(result.is_ok());
1268
1269        let request_body = result.unwrap();
1270        assert!(request_body.content.contains_key("application/json"));
1271    }
1272
1273    #[test]
1274    fn test_integer_with_min_max_values() {
1275        let schema_json = serde_json::json!({
1276            "type": "integer",
1277            "minimum": 0,
1278            "maximum": 100
1279        });
1280
1281        let result = json_value_to_schema(&schema_json);
1282        assert!(result.is_ok());
1283    }
1284
1285    #[test]
1286    fn test_string_with_length_constraints() {
1287        let schema_json = serde_json::json!({
1288            "type": "string",
1289            "minLength": 1,
1290            "maxLength": 255
1291        });
1292
1293        let result = json_value_to_schema(&schema_json);
1294        assert!(result.is_ok());
1295    }
1296
1297    #[test]
1298    fn test_array_with_item_count_constraints() {
1299        let schema_json = serde_json::json!({
1300            "type": "array",
1301            "items": { "type": "string" },
1302            "minItems": 1,
1303            "maxItems": 10
1304        });
1305
1306        let result = json_value_to_schema(&schema_json);
1307        assert!(result.is_ok());
1308    }
1309
1310    #[test]
1311    fn test_object_with_pattern_properties() {
1312        let schema_json = serde_json::json!({
1313            "type": "object",
1314            "properties": {
1315                "id": { "type": "integer" }
1316            },
1317            "patternProperties": {
1318                "^S_": { "type": "string" }
1319            }
1320        });
1321
1322        let result = json_value_to_schema(&schema_json);
1323        assert!(result.is_ok());
1324    }
1325
1326    #[test]
1327    fn test_deeply_nested_object_15_levels() {
1328        let mut schema = serde_json::json!({ "type": "string" });
1329
1330        for i in 0..15 {
1331            schema = serde_json::json!({
1332                "type": "object",
1333                "properties": {
1334                    format!("level_{}", i): schema
1335                }
1336            });
1337        }
1338
1339        let result = json_value_to_schema(&schema);
1340        assert!(result.is_ok(), "15-level deep nesting should not cause stack overflow");
1341    }
1342
1343    #[test]
1344    fn test_object_with_unicode_property_names() {
1345        let schema_json = serde_json::json!({
1346            "type": "object",
1347            "properties": {
1348                "名前": { "type": "string" },
1349                "年齢": { "type": "integer" },
1350                "🚀": { "type": "string" }
1351            }
1352        });
1353
1354        let result = json_value_to_schema(&schema_json);
1355        assert!(result.is_ok());
1356
1357        if let Ok(RefOr::T(Schema::Object(obj))) = result {
1358            assert!(obj.properties.contains_key("名前"));
1359            assert!(obj.properties.contains_key("年齢"));
1360            assert!(obj.properties.contains_key("🚀"));
1361        }
1362    }
1363}