spikard 0.4.0-x64-mingw-ucrt

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 (138) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +1 -0
  3. data/README.md +659 -0
  4. data/ext/spikard_rb/Cargo.toml +17 -0
  5. data/ext/spikard_rb/extconf.rb +10 -0
  6. data/ext/spikard_rb/src/lib.rs +6 -0
  7. data/lib/spikard/app.rb +405 -0
  8. data/lib/spikard/background.rb +27 -0
  9. data/lib/spikard/config.rb +396 -0
  10. data/lib/spikard/converters.rb +13 -0
  11. data/lib/spikard/handler_wrapper.rb +113 -0
  12. data/lib/spikard/provide.rb +214 -0
  13. data/lib/spikard/response.rb +173 -0
  14. data/lib/spikard/schema.rb +243 -0
  15. data/lib/spikard/sse.rb +111 -0
  16. data/lib/spikard/streaming_response.rb +44 -0
  17. data/lib/spikard/testing.rb +221 -0
  18. data/lib/spikard/upload_file.rb +131 -0
  19. data/lib/spikard/version.rb +5 -0
  20. data/lib/spikard/websocket.rb +59 -0
  21. data/lib/spikard.rb +43 -0
  22. data/sig/spikard.rbs +366 -0
  23. data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
  24. data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
  25. data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
  26. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +139 -0
  27. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +561 -0
  28. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
  29. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
  30. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +403 -0
  31. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +274 -0
  32. data/vendor/crates/spikard-bindings-shared/src/lib.rs +25 -0
  33. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +298 -0
  34. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +637 -0
  35. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +309 -0
  36. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
  37. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +355 -0
  38. data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +502 -0
  39. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +389 -0
  40. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +413 -0
  41. data/vendor/crates/spikard-core/Cargo.toml +40 -0
  42. data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
  43. data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
  44. data/vendor/crates/spikard-core/src/debug.rs +63 -0
  45. data/vendor/crates/spikard-core/src/di/container.rs +726 -0
  46. data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
  47. data/vendor/crates/spikard-core/src/di/error.rs +118 -0
  48. data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
  49. data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
  50. data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
  51. data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
  52. data/vendor/crates/spikard-core/src/di/value.rs +283 -0
  53. data/vendor/crates/spikard-core/src/errors.rs +39 -0
  54. data/vendor/crates/spikard-core/src/http.rs +153 -0
  55. data/vendor/crates/spikard-core/src/lib.rs +29 -0
  56. data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
  57. data/vendor/crates/spikard-core/src/metadata.rs +397 -0
  58. data/vendor/crates/spikard-core/src/parameters.rs +723 -0
  59. data/vendor/crates/spikard-core/src/problem.rs +310 -0
  60. data/vendor/crates/spikard-core/src/request_data.rs +189 -0
  61. data/vendor/crates/spikard-core/src/router.rs +249 -0
  62. data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
  63. data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
  64. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +689 -0
  65. data/vendor/crates/spikard-core/src/validation/mod.rs +459 -0
  66. data/vendor/crates/spikard-http/Cargo.toml +58 -0
  67. data/vendor/crates/spikard-http/examples/sse-notifications.rs +147 -0
  68. data/vendor/crates/spikard-http/examples/websocket-chat.rs +91 -0
  69. data/vendor/crates/spikard-http/src/auth.rs +247 -0
  70. data/vendor/crates/spikard-http/src/background.rs +1562 -0
  71. data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
  72. data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
  73. data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
  74. data/vendor/crates/spikard-http/src/cors.rs +490 -0
  75. data/vendor/crates/spikard-http/src/debug.rs +63 -0
  76. data/vendor/crates/spikard-http/src/di_handler.rs +1878 -0
  77. data/vendor/crates/spikard-http/src/handler_response.rs +532 -0
  78. data/vendor/crates/spikard-http/src/handler_trait.rs +861 -0
  79. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
  80. data/vendor/crates/spikard-http/src/lib.rs +524 -0
  81. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
  82. data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
  83. data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
  84. data/vendor/crates/spikard-http/src/middleware/multipart.rs +930 -0
  85. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +541 -0
  86. data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
  87. data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
  88. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -0
  89. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +867 -0
  90. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +678 -0
  91. data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
  92. data/vendor/crates/spikard-http/src/response.rs +399 -0
  93. data/vendor/crates/spikard-http/src/server/handler.rs +1557 -0
  94. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
  95. data/vendor/crates/spikard-http/src/server/mod.rs +806 -0
  96. data/vendor/crates/spikard-http/src/server/request_extraction.rs +630 -0
  97. data/vendor/crates/spikard-http/src/server/routing_factory.rs +497 -0
  98. data/vendor/crates/spikard-http/src/sse.rs +961 -0
  99. data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
  100. data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
  101. data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
  102. data/vendor/crates/spikard-http/src/testing.rs +377 -0
  103. data/vendor/crates/spikard-http/src/websocket.rs +831 -0
  104. data/vendor/crates/spikard-http/tests/background_behavior.rs +918 -0
  105. data/vendor/crates/spikard-http/tests/common/handlers.rs +308 -0
  106. data/vendor/crates/spikard-http/tests/common/mod.rs +21 -0
  107. data/vendor/crates/spikard-http/tests/di_integration.rs +202 -0
  108. data/vendor/crates/spikard-http/tests/doc_snippets.rs +4 -0
  109. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1135 -0
  110. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +688 -0
  111. data/vendor/crates/spikard-http/tests/server_config_builder.rs +324 -0
  112. data/vendor/crates/spikard-http/tests/sse_behavior.rs +728 -0
  113. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +724 -0
  114. data/vendor/crates/spikard-rb/Cargo.toml +43 -0
  115. data/vendor/crates/spikard-rb/build.rs +199 -0
  116. data/vendor/crates/spikard-rb/src/background.rs +63 -0
  117. data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
  118. data/vendor/crates/spikard-rb/src/config/server_config.rs +283 -0
  119. data/vendor/crates/spikard-rb/src/conversion.rs +459 -0
  120. data/vendor/crates/spikard-rb/src/di/builder.rs +105 -0
  121. data/vendor/crates/spikard-rb/src/di/mod.rs +413 -0
  122. data/vendor/crates/spikard-rb/src/handler.rs +612 -0
  123. data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
  124. data/vendor/crates/spikard-rb/src/lib.rs +1857 -0
  125. data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -0
  126. data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
  127. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +427 -0
  128. data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
  129. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +326 -0
  130. data/vendor/crates/spikard-rb/src/server.rs +283 -0
  131. data/vendor/crates/spikard-rb/src/sse.rs +231 -0
  132. data/vendor/crates/spikard-rb/src/testing/client.rs +404 -0
  133. data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
  134. data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -0
  135. data/vendor/crates/spikard-rb/src/testing/websocket.rs +221 -0
  136. data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
  137. data/vendor/crates/spikard-rb/tests/magnus_ffi_tests.rs +14 -0
  138. metadata +213 -0
@@ -0,0 +1,867 @@
1
+ //! JSON Schema to OpenAPI schema conversion utilities
2
+
3
+ use 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
7
+ pub 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
80
+ pub 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
98
+ pub 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)]
112
+ mod 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
+ // Edge Case Tests: Circular References & Self-References
310
+
311
+ #[test]
312
+ fn test_circular_reference_simple_cycle() {
313
+ let schema_json = serde_json::json!({
314
+ "type": "object",
315
+ "properties": {
316
+ "id": { "type": "integer" },
317
+ "parent": { "$ref": "#/properties/id" }
318
+ }
319
+ });
320
+
321
+ let result = json_value_to_schema(&schema_json);
322
+ assert!(result.is_ok());
323
+ }
324
+
325
+ #[test]
326
+ fn test_self_referential_schema_direct() {
327
+ let schema_json = serde_json::json!({
328
+ "type": "object",
329
+ "properties": {
330
+ "value": { "type": "string" },
331
+ "self": { "$ref": "#" }
332
+ }
333
+ });
334
+
335
+ let result = json_value_to_schema(&schema_json);
336
+ assert!(result.is_ok());
337
+ }
338
+
339
+ // Edge Case Tests: Deep Nesting
340
+
341
+ #[test]
342
+ fn test_deeply_nested_object_10_levels() {
343
+ let schema_json = serde_json::json!({
344
+ "type": "object",
345
+ "properties": {
346
+ "l1": {
347
+ "type": "object",
348
+ "properties": {
349
+ "l2": {
350
+ "type": "object",
351
+ "properties": {
352
+ "l3": {
353
+ "type": "object",
354
+ "properties": {
355
+ "l4": {
356
+ "type": "object",
357
+ "properties": {
358
+ "l5": {
359
+ "type": "object",
360
+ "properties": {
361
+ "l6": {
362
+ "type": "object",
363
+ "properties": {
364
+ "l7": {
365
+ "type": "object",
366
+ "properties": {
367
+ "l8": {
368
+ "type": "object",
369
+ "properties": {
370
+ "l9": {
371
+ "type": "object",
372
+ "properties": {
373
+ "l10": { "type": "string" }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+ }
381
+ }
382
+ }
383
+ }
384
+ }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ }
390
+ }
391
+ }
392
+ }
393
+ });
394
+
395
+ let result = json_value_to_schema(&schema_json);
396
+ assert!(result.is_ok(), "Deep nesting should not cause stack overflow");
397
+ }
398
+
399
+ #[test]
400
+ fn test_deeply_nested_array_5_levels() {
401
+ let schema_json = serde_json::json!({
402
+ "type": "array",
403
+ "items": {
404
+ "type": "array",
405
+ "items": {
406
+ "type": "array",
407
+ "items": {
408
+ "type": "array",
409
+ "items": {
410
+ "type": "array",
411
+ "items": { "type": "string" }
412
+ }
413
+ }
414
+ }
415
+ }
416
+ });
417
+
418
+ let result = json_value_to_schema(&schema_json);
419
+ assert!(result.is_ok());
420
+ }
421
+
422
+ // Edge Case Tests: Type Coercion
423
+
424
+ #[test]
425
+ fn test_type_coercion_integer_to_number() {
426
+ let schema_json = serde_json::json!({
427
+ "type": "integer"
428
+ });
429
+
430
+ let result = json_value_to_schema(&schema_json);
431
+ assert!(result.is_ok());
432
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
433
+ assert!(matches!(
434
+ obj.schema_type,
435
+ utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Integer)
436
+ ));
437
+ } else {
438
+ panic!("Expected Object schema with Type::Integer");
439
+ }
440
+ }
441
+
442
+ #[test]
443
+ fn test_type_coercion_number_vs_integer() {
444
+ let int_schema = serde_json::json!({ "type": "integer" });
445
+ let num_schema = serde_json::json!({ "type": "number" });
446
+
447
+ let int_result = json_value_to_schema(&int_schema);
448
+ let num_result = json_value_to_schema(&num_schema);
449
+
450
+ assert!(int_result.is_ok());
451
+ assert!(num_result.is_ok());
452
+
453
+ if let (Ok(RefOr::T(Schema::Object(int_obj))), Ok(RefOr::T(Schema::Object(num_obj)))) = (int_result, num_result)
454
+ {
455
+ assert!(matches!(
456
+ int_obj.schema_type,
457
+ utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Integer)
458
+ ));
459
+ assert!(matches!(
460
+ num_obj.schema_type,
461
+ utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::Number)
462
+ ));
463
+ }
464
+ }
465
+
466
+ // Edge Case Tests: Nullable & Required
467
+
468
+ #[test]
469
+ fn test_nullable_property_in_object() {
470
+ let schema_json = serde_json::json!({
471
+ "type": "object",
472
+ "properties": {
473
+ "id": { "type": "integer" },
474
+ "optional_field": { "type": "string" }
475
+ },
476
+ "required": ["id"]
477
+ });
478
+
479
+ let result = json_value_to_schema(&schema_json);
480
+ assert!(result.is_ok());
481
+
482
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
483
+ assert!(obj.required.contains(&"id".to_string()));
484
+ assert!(!obj.required.contains(&"optional_field".to_string()));
485
+ } else {
486
+ panic!("Expected Object schema");
487
+ }
488
+ }
489
+
490
+ #[test]
491
+ fn test_required_array_with_multiple_fields() {
492
+ let schema_json = serde_json::json!({
493
+ "type": "object",
494
+ "properties": {
495
+ "id": { "type": "integer" },
496
+ "name": { "type": "string" },
497
+ "email": { "type": "string" },
498
+ "optional": { "type": "string" }
499
+ },
500
+ "required": ["id", "name", "email"]
501
+ });
502
+
503
+ let result = json_value_to_schema(&schema_json);
504
+ assert!(result.is_ok());
505
+
506
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
507
+ assert!(obj.required.contains(&"id".to_string()));
508
+ assert!(obj.required.contains(&"name".to_string()));
509
+ assert!(obj.required.contains(&"email".to_string()));
510
+ assert!(!obj.required.contains(&"optional".to_string()));
511
+ }
512
+ }
513
+
514
+ // Edge Case Tests: Format Preservation
515
+
516
+ #[test]
517
+ fn test_format_uuid() {
518
+ let schema_json = serde_json::json!({
519
+ "type": "string",
520
+ "format": "uuid"
521
+ });
522
+
523
+ let result = json_value_to_schema(&schema_json);
524
+ assert!(result.is_ok());
525
+ }
526
+
527
+ #[test]
528
+ fn test_format_email() {
529
+ let schema_json = serde_json::json!({
530
+ "type": "string",
531
+ "format": "email"
532
+ });
533
+
534
+ let result = json_value_to_schema(&schema_json);
535
+ assert!(result.is_ok());
536
+ }
537
+
538
+ #[test]
539
+ fn test_format_date_time() {
540
+ let schema_json = serde_json::json!({
541
+ "type": "string",
542
+ "format": "date-time"
543
+ });
544
+
545
+ let result = json_value_to_schema(&schema_json);
546
+ assert!(result.is_ok());
547
+
548
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
549
+ assert!(matches!(
550
+ obj.schema_type,
551
+ utoipa::openapi::schema::SchemaType::Type(utoipa::openapi::schema::Type::String)
552
+ ));
553
+ }
554
+ }
555
+
556
+ #[test]
557
+ fn test_format_date() {
558
+ let schema_json = serde_json::json!({
559
+ "type": "string",
560
+ "format": "date"
561
+ });
562
+
563
+ let result = json_value_to_schema(&schema_json);
564
+ assert!(result.is_ok());
565
+ }
566
+
567
+ #[test]
568
+ fn test_format_uri() {
569
+ let schema_json = serde_json::json!({
570
+ "type": "string",
571
+ "format": "uri"
572
+ });
573
+
574
+ let result = json_value_to_schema(&schema_json);
575
+ assert!(result.is_ok());
576
+ }
577
+
578
+ #[test]
579
+ fn test_format_unknown_custom_format() {
580
+ let schema_json = serde_json::json!({
581
+ "type": "string",
582
+ "format": "custom-format"
583
+ });
584
+
585
+ let result = json_value_to_schema(&schema_json);
586
+ assert!(result.is_ok(), "Unknown formats should be gracefully handled");
587
+ }
588
+
589
+ // Edge Case Tests: Array of Complex Objects
590
+
591
+ #[test]
592
+ fn test_array_of_objects() {
593
+ let schema_json = serde_json::json!({
594
+ "type": "array",
595
+ "items": {
596
+ "type": "object",
597
+ "properties": {
598
+ "id": { "type": "integer" },
599
+ "name": { "type": "string" }
600
+ },
601
+ "required": ["id"]
602
+ }
603
+ });
604
+
605
+ let result = json_value_to_schema(&schema_json);
606
+ assert!(result.is_ok());
607
+
608
+ if let Ok(RefOr::T(Schema::Array(_))) = result {
609
+ } else {
610
+ panic!("Expected Array schema");
611
+ }
612
+ }
613
+
614
+ #[test]
615
+ fn test_array_of_arrays_of_objects() {
616
+ let schema_json = serde_json::json!({
617
+ "type": "array",
618
+ "items": {
619
+ "type": "array",
620
+ "items": {
621
+ "type": "object",
622
+ "properties": {
623
+ "value": { "type": "string" }
624
+ }
625
+ }
626
+ }
627
+ });
628
+
629
+ let result = json_value_to_schema(&schema_json);
630
+ assert!(result.is_ok());
631
+ }
632
+
633
+ // Edge Case Tests: Additional Properties
634
+
635
+ #[test]
636
+ fn test_object_with_additional_properties_true() {
637
+ let schema_json = serde_json::json!({
638
+ "type": "object",
639
+ "properties": {
640
+ "id": { "type": "integer" }
641
+ },
642
+ "additionalProperties": true
643
+ });
644
+
645
+ let result = json_value_to_schema(&schema_json);
646
+ assert!(result.is_ok());
647
+ }
648
+
649
+ #[test]
650
+ fn test_object_with_additional_properties_false() {
651
+ let schema_json = serde_json::json!({
652
+ "type": "object",
653
+ "properties": {
654
+ "id": { "type": "integer" }
655
+ },
656
+ "additionalProperties": false
657
+ });
658
+
659
+ let result = json_value_to_schema(&schema_json);
660
+ assert!(result.is_ok(), "additionalProperties:false should not cause errors");
661
+ }
662
+
663
+ #[test]
664
+ fn test_object_with_additional_properties_schema() {
665
+ let schema_json = serde_json::json!({
666
+ "type": "object",
667
+ "properties": {
668
+ "id": { "type": "integer" }
669
+ },
670
+ "additionalProperties": { "type": "string" }
671
+ });
672
+
673
+ let result = json_value_to_schema(&schema_json);
674
+ assert!(result.is_ok());
675
+ }
676
+
677
+ // Edge Case Tests: Empty Schemas
678
+
679
+ #[test]
680
+ fn test_empty_schema() {
681
+ let schema_json = serde_json::json!({});
682
+
683
+ let result = json_value_to_schema(&schema_json);
684
+ assert!(result.is_ok(), "Empty schema should create basic object");
685
+ }
686
+
687
+ #[test]
688
+ fn test_schema_with_only_type_field() {
689
+ let schema_json = serde_json::json!({
690
+ "type": "object"
691
+ });
692
+
693
+ let result = json_value_to_schema(&schema_json);
694
+ assert!(result.is_ok());
695
+
696
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
697
+ assert!(obj.properties.is_empty());
698
+ }
699
+ }
700
+
701
+ #[test]
702
+ fn test_array_without_items_schema() {
703
+ let schema_json = serde_json::json!({
704
+ "type": "array"
705
+ });
706
+
707
+ let result = json_value_to_schema(&schema_json);
708
+ assert!(result.is_ok());
709
+ }
710
+
711
+ // Edge Case Tests: Mixed Composite Schemas
712
+
713
+ #[test]
714
+ fn test_object_with_mixed_property_types() {
715
+ let schema_json = serde_json::json!({
716
+ "type": "object",
717
+ "properties": {
718
+ "id": { "type": "integer" },
719
+ "name": { "type": "string" },
720
+ "active": { "type": "boolean" },
721
+ "score": { "type": "number" },
722
+ "tags": {
723
+ "type": "array",
724
+ "items": { "type": "string" }
725
+ },
726
+ "metadata": {
727
+ "type": "object",
728
+ "properties": {
729
+ "created": { "type": "string", "format": "date-time" }
730
+ }
731
+ }
732
+ }
733
+ });
734
+
735
+ let result = json_value_to_schema(&schema_json);
736
+ assert!(result.is_ok());
737
+
738
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
739
+ assert_eq!(obj.properties.len(), 6);
740
+ assert!(obj.properties.contains_key("id"));
741
+ assert!(obj.properties.contains_key("name"));
742
+ assert!(obj.properties.contains_key("active"));
743
+ assert!(obj.properties.contains_key("score"));
744
+ assert!(obj.properties.contains_key("tags"));
745
+ assert!(obj.properties.contains_key("metadata"));
746
+ }
747
+ }
748
+
749
+ #[test]
750
+ fn test_nullable_complex_types() {
751
+ let schema_json = serde_json::json!({
752
+ "type": "object",
753
+ "properties": {
754
+ "user": {
755
+ "oneOf": [
756
+ { "type": "object", "properties": { "id": { "type": "integer" } } },
757
+ { "type": "null" }
758
+ ]
759
+ }
760
+ }
761
+ });
762
+
763
+ let result = json_value_to_schema(&schema_json);
764
+ assert!(result.is_ok());
765
+ }
766
+
767
+ // Edge Case Tests: Unsupported Types
768
+
769
+ #[test]
770
+ fn test_unsupported_type_error() {
771
+ let schema_json = serde_json::json!({
772
+ "type": "unsupported_type"
773
+ });
774
+
775
+ let result = json_value_to_schema(&schema_json);
776
+ assert!(result.is_err());
777
+ if let Err(err) = result {
778
+ assert!(err.contains("Unsupported schema type"));
779
+ }
780
+ }
781
+
782
+ // Edge Case Tests: Required Array Validation
783
+
784
+ #[test]
785
+ fn test_required_with_non_string_elements() {
786
+ let schema_json = serde_json::json!({
787
+ "type": "object",
788
+ "properties": {
789
+ "a": { "type": "string" },
790
+ "b": { "type": "integer" }
791
+ },
792
+ "required": [123, null, "a"]
793
+ });
794
+
795
+ let result = json_value_to_schema(&schema_json);
796
+ assert!(result.is_ok(), "Non-string elements in required should be skipped");
797
+
798
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
799
+ assert!(obj.required.contains(&"a".to_string()));
800
+ assert_eq!(obj.required.len(), 1);
801
+ }
802
+ }
803
+
804
+ #[test]
805
+ fn test_properties_with_null_values() {
806
+ let schema_json = serde_json::json!({
807
+ "type": "object",
808
+ "properties": {
809
+ "valid": { "type": "string" },
810
+ "null_value": null
811
+ }
812
+ });
813
+
814
+ let result = json_value_to_schema(&schema_json);
815
+ assert!(result.is_ok());
816
+ }
817
+
818
+ #[test]
819
+ fn test_object_with_empty_required_array() {
820
+ let schema_json = serde_json::json!({
821
+ "type": "object",
822
+ "properties": {
823
+ "id": { "type": "integer" },
824
+ "name": { "type": "string" }
825
+ },
826
+ "required": []
827
+ });
828
+
829
+ let result = json_value_to_schema(&schema_json);
830
+ assert!(result.is_ok());
831
+
832
+ if let Ok(RefOr::T(Schema::Object(obj))) = result {
833
+ assert!(obj.required.is_empty());
834
+ }
835
+ }
836
+
837
+ #[test]
838
+ fn test_request_body_with_missing_items() {
839
+ let schema_json = serde_json::json!({
840
+ "type": "array"
841
+ });
842
+
843
+ let result = json_schema_to_request_body(&schema_json);
844
+ assert!(result.is_ok());
845
+ }
846
+
847
+ #[test]
848
+ fn test_response_with_all_scalar_types() {
849
+ let types = vec!["string", "integer", "number", "boolean"];
850
+
851
+ for type_name in types {
852
+ let schema_json = serde_json::json!({
853
+ "type": type_name
854
+ });
855
+
856
+ let result = json_schema_to_response(&schema_json);
857
+ assert!(
858
+ result.is_ok(),
859
+ "Response schema with type '{}' should succeed",
860
+ type_name
861
+ );
862
+
863
+ let response = result.unwrap();
864
+ assert!(response.content.contains_key("application/json"));
865
+ }
866
+ }
867
+ }