spikard 0.3.6 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -6
  3. data/ext/spikard_rb/Cargo.toml +2 -2
  4. data/lib/spikard/app.rb +33 -14
  5. data/lib/spikard/testing.rb +47 -12
  6. data/lib/spikard/version.rb +1 -1
  7. data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
  8. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
  9. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
  10. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
  11. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
  12. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
  13. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
  14. data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
  15. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
  16. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
  17. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
  18. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
  19. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
  20. data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
  21. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
  22. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
  23. data/vendor/crates/spikard-core/Cargo.toml +4 -4
  24. data/vendor/crates/spikard-core/src/debug.rs +64 -0
  25. data/vendor/crates/spikard-core/src/di/container.rs +3 -27
  26. data/vendor/crates/spikard-core/src/di/factory.rs +1 -5
  27. data/vendor/crates/spikard-core/src/di/graph.rs +8 -47
  28. data/vendor/crates/spikard-core/src/di/mod.rs +1 -1
  29. data/vendor/crates/spikard-core/src/di/resolved.rs +1 -7
  30. data/vendor/crates/spikard-core/src/di/value.rs +2 -4
  31. data/vendor/crates/spikard-core/src/errors.rs +30 -0
  32. data/vendor/crates/spikard-core/src/http.rs +262 -0
  33. data/vendor/crates/spikard-core/src/lib.rs +1 -1
  34. data/vendor/crates/spikard-core/src/lifecycle.rs +764 -0
  35. data/vendor/crates/spikard-core/src/metadata.rs +389 -0
  36. data/vendor/crates/spikard-core/src/parameters.rs +1962 -159
  37. data/vendor/crates/spikard-core/src/problem.rs +34 -0
  38. data/vendor/crates/spikard-core/src/request_data.rs +966 -1
  39. data/vendor/crates/spikard-core/src/router.rs +263 -2
  40. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +688 -0
  41. data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +26 -268
  42. data/vendor/crates/spikard-http/Cargo.toml +12 -16
  43. data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
  44. data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
  45. data/vendor/crates/spikard-http/src/auth.rs +65 -16
  46. data/vendor/crates/spikard-http/src/background.rs +1614 -3
  47. data/vendor/crates/spikard-http/src/cors.rs +515 -0
  48. data/vendor/crates/spikard-http/src/debug.rs +65 -0
  49. data/vendor/crates/spikard-http/src/di_handler.rs +1322 -77
  50. data/vendor/crates/spikard-http/src/handler_response.rs +711 -0
  51. data/vendor/crates/spikard-http/src/handler_trait.rs +607 -5
  52. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +6 -0
  53. data/vendor/crates/spikard-http/src/lib.rs +33 -28
  54. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +81 -0
  55. data/vendor/crates/spikard-http/src/lifecycle.rs +765 -0
  56. data/vendor/crates/spikard-http/src/middleware/mod.rs +372 -117
  57. data/vendor/crates/spikard-http/src/middleware/multipart.rs +836 -10
  58. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +409 -43
  59. data/vendor/crates/spikard-http/src/middleware/validation.rs +513 -65
  60. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +345 -0
  61. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1055 -0
  62. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +473 -3
  63. data/vendor/crates/spikard-http/src/query_parser.rs +455 -31
  64. data/vendor/crates/spikard-http/src/response.rs +321 -0
  65. data/vendor/crates/spikard-http/src/server/handler.rs +1572 -9
  66. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +136 -0
  67. data/vendor/crates/spikard-http/src/server/mod.rs +875 -178
  68. data/vendor/crates/spikard-http/src/server/request_extraction.rs +674 -23
  69. data/vendor/crates/spikard-http/src/server/routing_factory.rs +599 -0
  70. data/vendor/crates/spikard-http/src/sse.rs +983 -21
  71. data/vendor/crates/spikard-http/src/testing/form.rs +38 -0
  72. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -2
  73. data/vendor/crates/spikard-http/src/testing.rs +7 -7
  74. data/vendor/crates/spikard-http/src/websocket.rs +1055 -4
  75. data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
  76. data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
  77. data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
  78. data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
  79. data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
  80. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
  81. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
  82. data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
  83. data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
  84. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
  85. data/vendor/crates/spikard-rb/Cargo.toml +10 -4
  86. data/vendor/crates/spikard-rb/build.rs +196 -5
  87. data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
  88. data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +100 -109
  89. data/vendor/crates/spikard-rb/src/conversion.rs +121 -20
  90. data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
  91. data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +12 -46
  92. data/vendor/crates/spikard-rb/src/handler.rs +100 -107
  93. data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
  94. data/vendor/crates/spikard-rb/src/lib.rs +467 -1428
  95. data/vendor/crates/spikard-rb/src/lifecycle.rs +1 -0
  96. data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
  97. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +447 -0
  98. data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
  99. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
  100. data/vendor/crates/spikard-rb/src/server.rs +47 -22
  101. data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +187 -40
  102. data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
  103. data/vendor/crates/spikard-rb/src/testing/websocket.rs +635 -0
  104. data/vendor/crates/spikard-rb/src/websocket.rs +178 -37
  105. metadata +46 -13
  106. data/vendor/crates/spikard-http/src/parameters.rs +0 -1
  107. data/vendor/crates/spikard-http/src/problem.rs +0 -1
  108. data/vendor/crates/spikard-http/src/router.rs +0 -1
  109. data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
  110. data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
  111. data/vendor/crates/spikard-http/src/validation.rs +0 -1
  112. data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
  113. /data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +0 -0
@@ -305,4 +305,1059 @@ mod tests {
305
305
  let result = json_schema_to_request_body(&schema_json);
306
306
  assert!(result.is_ok());
307
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
+ }
308
1363
  }