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
@@ -29,6 +29,8 @@ pub struct RequestData {
29
29
  pub path_params: Arc<HashMap<String, String>>,
30
30
  /// Query parameters parsed as JSON
31
31
  pub query_params: Value,
32
+ /// Validated parameters produced by ParameterValidator (query/path/header/cookie combined).
33
+ pub validated_params: Option<Value>,
32
34
  /// Raw query parameters as key-value pairs
33
35
  pub raw_query_params: Arc<HashMap<String, Vec<String>>>,
34
36
  /// Parsed request body as JSON
@@ -57,9 +59,10 @@ impl Serialize for RequestData {
57
59
  S: serde::Serializer,
58
60
  {
59
61
  use serde::ser::SerializeStruct;
60
- let mut state = serializer.serialize_struct("RequestData", 9)?;
62
+ let mut state = serializer.serialize_struct("RequestData", 10)?;
61
63
  state.serialize_field("path_params", &*self.path_params)?;
62
64
  state.serialize_field("query_params", &self.query_params)?;
65
+ state.serialize_field("validated_params", &self.validated_params)?;
63
66
  state.serialize_field("raw_query_params", &*self.raw_query_params)?;
64
67
  state.serialize_field("body", &self.body)?;
65
68
  #[cfg(feature = "di")]
@@ -84,6 +87,7 @@ impl<'de> Deserialize<'de> for RequestData {
84
87
  enum Field {
85
88
  PathParams,
86
89
  QueryParams,
90
+ ValidatedParams,
87
91
  RawQueryParams,
88
92
  Body,
89
93
  RawBody,
@@ -108,6 +112,7 @@ impl<'de> Deserialize<'de> for RequestData {
108
112
  {
109
113
  let mut path_params = None;
110
114
  let mut query_params = None;
115
+ let mut validated_params = None;
111
116
  let mut raw_query_params = None;
112
117
  let mut body = None;
113
118
  let mut raw_body = None;
@@ -124,6 +129,9 @@ impl<'de> Deserialize<'de> for RequestData {
124
129
  Field::QueryParams => {
125
130
  query_params = Some(map.next_value()?);
126
131
  }
132
+ Field::ValidatedParams => {
133
+ validated_params = Some(map.next_value()?);
134
+ }
127
135
  Field::RawQueryParams => {
128
136
  raw_query_params = Some(Arc::new(map.next_value()?));
129
137
  }
@@ -159,6 +167,7 @@ impl<'de> Deserialize<'de> for RequestData {
159
167
  Ok(RequestData {
160
168
  path_params: path_params.ok_or_else(|| serde::de::Error::missing_field("path_params"))?,
161
169
  query_params: query_params.ok_or_else(|| serde::de::Error::missing_field("query_params"))?,
170
+ validated_params,
162
171
  raw_query_params: raw_query_params
163
172
  .ok_or_else(|| serde::de::Error::missing_field("raw_query_params"))?,
164
173
  body: body.ok_or_else(|| serde::de::Error::missing_field("body"))?,
@@ -176,6 +185,7 @@ impl<'de> Deserialize<'de> for RequestData {
176
185
  const FIELDS: &[&str] = &[
177
186
  "path_params",
178
187
  "query_params",
188
+ "validated_params",
179
189
  "raw_query_params",
180
190
  "body",
181
191
  "raw_body",
@@ -187,3 +197,958 @@ impl<'de> Deserialize<'de> for RequestData {
187
197
  deserializer.deserialize_struct("RequestData", FIELDS, RequestDataVisitor)
188
198
  }
189
199
  }
200
+
201
+ #[cfg(test)]
202
+ mod tests {
203
+ use super::*;
204
+ use serde_json::{Value, json};
205
+
206
+ #[derive(Default)]
207
+ struct RequestDataCollections {
208
+ raw_query_params: HashMap<String, Vec<String>>,
209
+ headers: HashMap<String, String>,
210
+ cookies: HashMap<String, String>,
211
+ path_params: HashMap<String, String>,
212
+ }
213
+
214
+ fn create_request_data(
215
+ path: &str,
216
+ method: &str,
217
+ body: Value,
218
+ query_params: Value,
219
+ collections: RequestDataCollections,
220
+ ) -> RequestData {
221
+ RequestData {
222
+ path_params: Arc::new(collections.path_params),
223
+ query_params,
224
+ validated_params: None,
225
+ raw_query_params: Arc::new(collections.raw_query_params),
226
+ body,
227
+ raw_body: None,
228
+ headers: Arc::new(collections.headers),
229
+ cookies: Arc::new(collections.cookies),
230
+ method: method.to_string(),
231
+ path: path.to_string(),
232
+ #[cfg(feature = "di")]
233
+ dependencies: None,
234
+ }
235
+ }
236
+
237
+ #[test]
238
+ fn test_request_data_minimal() {
239
+ let data = create_request_data(
240
+ "/api/users",
241
+ "GET",
242
+ json!(null),
243
+ json!({}),
244
+ RequestDataCollections::default(),
245
+ );
246
+
247
+ assert_eq!(data.path, "/api/users");
248
+ assert_eq!(data.method, "GET");
249
+ assert_eq!(data.body, json!(null));
250
+ }
251
+
252
+ #[test]
253
+ fn test_request_data_with_json_body() {
254
+ let body = json!({
255
+ "name": "test_user",
256
+ "email": "test@example.com",
257
+ "age": 30
258
+ });
259
+
260
+ let data = create_request_data(
261
+ "/api/users",
262
+ "POST",
263
+ body.clone(),
264
+ json!({}),
265
+ RequestDataCollections::default(),
266
+ );
267
+
268
+ assert_eq!(data.body, body);
269
+ assert_eq!(data.body["name"], "test_user");
270
+ assert_eq!(data.body["email"], "test@example.com");
271
+ assert_eq!(data.body["age"], 30);
272
+ }
273
+
274
+ #[test]
275
+ fn test_request_data_with_query_params() {
276
+ let mut raw_query_params = HashMap::new();
277
+ raw_query_params.insert("page".to_string(), vec!["1".to_string()]);
278
+ raw_query_params.insert("limit".to_string(), vec!["10".to_string()]);
279
+ raw_query_params.insert("tags".to_string(), vec!["rust".to_string(), "web".to_string()]);
280
+
281
+ let query_params = json!({
282
+ "page": "1",
283
+ "limit": "10",
284
+ "tags": ["rust", "web"]
285
+ });
286
+
287
+ let data = create_request_data(
288
+ "/api/users",
289
+ "GET",
290
+ json!(null),
291
+ query_params,
292
+ RequestDataCollections {
293
+ raw_query_params,
294
+ ..Default::default()
295
+ },
296
+ );
297
+
298
+ assert_eq!(data.raw_query_params.get("page"), Some(&vec!["1".to_string()]));
299
+ assert_eq!(data.raw_query_params.get("limit"), Some(&vec!["10".to_string()]));
300
+ assert_eq!(
301
+ data.raw_query_params.get("tags"),
302
+ Some(&vec!["rust".to_string(), "web".to_string()])
303
+ );
304
+ }
305
+
306
+ #[test]
307
+ fn test_request_data_with_headers() {
308
+ let mut headers = HashMap::new();
309
+ headers.insert("content-type".to_string(), "application/json".to_string());
310
+ headers.insert("authorization".to_string(), "Bearer token123".to_string());
311
+ headers.insert("user-agent".to_string(), "Mozilla/5.0".to_string());
312
+
313
+ let data = create_request_data(
314
+ "/api/users",
315
+ "GET",
316
+ json!(null),
317
+ json!({}),
318
+ RequestDataCollections {
319
+ headers,
320
+ ..Default::default()
321
+ },
322
+ );
323
+
324
+ assert_eq!(data.headers.get("content-type"), Some(&"application/json".to_string()));
325
+ assert_eq!(data.headers.get("authorization"), Some(&"Bearer token123".to_string()));
326
+ assert_eq!(data.headers.get("user-agent"), Some(&"Mozilla/5.0".to_string()));
327
+ }
328
+
329
+ #[test]
330
+ fn test_request_data_with_cookies() {
331
+ let mut cookies = HashMap::new();
332
+ cookies.insert("session_id".to_string(), "abc123xyz".to_string());
333
+ cookies.insert("user_id".to_string(), "user_42".to_string());
334
+ cookies.insert("theme".to_string(), "dark".to_string());
335
+
336
+ let data = create_request_data(
337
+ "/api/users",
338
+ "GET",
339
+ json!(null),
340
+ json!({}),
341
+ RequestDataCollections {
342
+ cookies,
343
+ ..Default::default()
344
+ },
345
+ );
346
+
347
+ assert_eq!(data.cookies.get("session_id"), Some(&"abc123xyz".to_string()));
348
+ assert_eq!(data.cookies.get("user_id"), Some(&"user_42".to_string()));
349
+ assert_eq!(data.cookies.get("theme"), Some(&"dark".to_string()));
350
+ }
351
+
352
+ #[test]
353
+ fn test_request_data_with_path_params() {
354
+ let mut path_params = HashMap::new();
355
+ path_params.insert("user_id".to_string(), "123".to_string());
356
+ path_params.insert("post_id".to_string(), "456".to_string());
357
+
358
+ let data = create_request_data(
359
+ "/api/users/123/posts/456",
360
+ "GET",
361
+ json!(null),
362
+ json!({}),
363
+ RequestDataCollections {
364
+ path_params,
365
+ ..Default::default()
366
+ },
367
+ );
368
+
369
+ assert_eq!(data.path_params.get("user_id"), Some(&"123".to_string()));
370
+ assert_eq!(data.path_params.get("post_id"), Some(&"456".to_string()));
371
+ }
372
+
373
+ #[test]
374
+ fn test_request_data_all_fields_populated() {
375
+ let mut path_params = HashMap::new();
376
+ path_params.insert("id".to_string(), "user_99".to_string());
377
+
378
+ let mut raw_query_params = HashMap::new();
379
+ raw_query_params.insert("filter".to_string(), vec!["active".to_string()]);
380
+
381
+ let mut headers = HashMap::new();
382
+ headers.insert("content-type".to_string(), "application/json".to_string());
383
+
384
+ let mut cookies = HashMap::new();
385
+ cookies.insert("session".to_string(), "xyz789".to_string());
386
+
387
+ let body = json!({
388
+ "name": "John",
389
+ "email": "john@example.com"
390
+ });
391
+
392
+ let query_params = json!({
393
+ "filter": "active"
394
+ });
395
+
396
+ let data = create_request_data(
397
+ "/api/users/user_99",
398
+ "PUT",
399
+ body,
400
+ query_params,
401
+ RequestDataCollections {
402
+ raw_query_params,
403
+ headers,
404
+ cookies,
405
+ path_params,
406
+ },
407
+ );
408
+
409
+ assert_eq!(data.path, "/api/users/user_99");
410
+ assert_eq!(data.method, "PUT");
411
+ assert_eq!(data.path_params.get("id"), Some(&"user_99".to_string()));
412
+ assert_eq!(data.body["name"], "John");
413
+ assert_eq!(data.headers.get("content-type"), Some(&"application/json".to_string()));
414
+ assert_eq!(data.cookies.get("session"), Some(&"xyz789".to_string()));
415
+ }
416
+
417
+ #[test]
418
+ fn test_request_data_empty_collections() {
419
+ let data = create_request_data(
420
+ "/api/test",
421
+ "GET",
422
+ json!(null),
423
+ json!({}),
424
+ RequestDataCollections::default(),
425
+ );
426
+
427
+ assert!(data.path_params.is_empty());
428
+ assert!(data.raw_query_params.is_empty());
429
+ assert!(data.headers.is_empty());
430
+ assert!(data.cookies.is_empty());
431
+ }
432
+
433
+ #[test]
434
+ fn test_request_data_clone() {
435
+ let mut path_params = HashMap::new();
436
+ path_params.insert("id".to_string(), "123".to_string());
437
+
438
+ let data1 = create_request_data(
439
+ "/api/users/123",
440
+ "GET",
441
+ json!({"name": "test"}),
442
+ json!({}),
443
+ RequestDataCollections {
444
+ path_params,
445
+ ..Default::default()
446
+ },
447
+ );
448
+
449
+ let data2 = data1.clone();
450
+
451
+ assert_eq!(data1.path, data2.path);
452
+ assert_eq!(data1.method, data2.method);
453
+ assert_eq!(data1.body, data2.body);
454
+ assert_eq!(data1.path_params, data2.path_params);
455
+ }
456
+
457
+ #[test]
458
+ fn test_request_data_various_http_methods() {
459
+ let methods = vec!["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
460
+
461
+ for method in methods {
462
+ let data = create_request_data(
463
+ "/api/test",
464
+ method,
465
+ json!(null),
466
+ json!({}),
467
+ RequestDataCollections::default(),
468
+ );
469
+
470
+ assert_eq!(data.method, method);
471
+ }
472
+ }
473
+
474
+ #[test]
475
+ fn test_request_data_raw_body_none() {
476
+ let data = create_request_data(
477
+ "/api/test",
478
+ "GET",
479
+ json!(null),
480
+ json!({}),
481
+ RequestDataCollections::default(),
482
+ );
483
+
484
+ assert!(data.raw_body.is_none());
485
+ }
486
+
487
+ #[cfg(not(feature = "di"))]
488
+ #[test]
489
+ fn test_request_data_raw_body_some() {
490
+ let raw_body_bytes = vec![1, 2, 3, 4, 5];
491
+ let mut data = create_request_data(
492
+ "/api/test",
493
+ "POST",
494
+ json!(null),
495
+ json!({}),
496
+ RequestDataCollections::default(),
497
+ );
498
+
499
+ data.raw_body = Some(raw_body_bytes.clone());
500
+ assert_eq!(data.raw_body, Some(raw_body_bytes));
501
+ }
502
+
503
+ #[cfg(not(feature = "di"))]
504
+ #[test]
505
+ fn request_data_serializes_and_deserializes() {
506
+ let mut headers = HashMap::new();
507
+ headers.insert("content-type".to_string(), "application/json".to_string());
508
+
509
+ let mut cookies = HashMap::new();
510
+ cookies.insert("session".to_string(), "abc".to_string());
511
+
512
+ let mut raw_query_params = HashMap::new();
513
+ raw_query_params.insert("tags".to_string(), vec!["rust".to_string(), "http".to_string()]);
514
+
515
+ let mut path_params = HashMap::new();
516
+ path_params.insert("id".to_string(), "123".to_string());
517
+
518
+ let mut data = create_request_data(
519
+ "/api/items/123",
520
+ "POST",
521
+ json!({"name": "demo"}),
522
+ json!({"tags": ["rust", "http"]}),
523
+ RequestDataCollections {
524
+ raw_query_params,
525
+ headers,
526
+ cookies,
527
+ path_params,
528
+ },
529
+ );
530
+ data.raw_body = Some(vec![9, 8, 7]);
531
+
532
+ let encoded = serde_json::to_string(&data).expect("serialize RequestData");
533
+ let decoded: RequestData = serde_json::from_str(&encoded).expect("deserialize RequestData");
534
+
535
+ assert_eq!(decoded.path, "/api/items/123");
536
+ assert_eq!(decoded.method, "POST");
537
+ assert_eq!(decoded.body["name"], "demo");
538
+ assert_eq!(decoded.query_params["tags"][0], "rust");
539
+ assert_eq!(
540
+ decoded.raw_query_params.get("tags").unwrap(),
541
+ &vec!["rust".to_string(), "http".to_string()]
542
+ );
543
+ assert_eq!(decoded.headers.get("content-type").unwrap(), "application/json");
544
+ assert_eq!(decoded.cookies.get("session").unwrap(), "abc");
545
+ assert_eq!(decoded.path_params.get("id").unwrap(), "123");
546
+ assert_eq!(decoded.raw_body, Some(vec![9, 8, 7]));
547
+ }
548
+
549
+ #[test]
550
+ fn test_request_data_multiple_query_param_values() {
551
+ let mut raw_query_params = HashMap::new();
552
+ raw_query_params.insert(
553
+ "colors".to_string(),
554
+ vec!["red".to_string(), "green".to_string(), "blue".to_string()],
555
+ );
556
+
557
+ let data = create_request_data(
558
+ "/api/items",
559
+ "GET",
560
+ json!(null),
561
+ json!({}),
562
+ RequestDataCollections {
563
+ raw_query_params,
564
+ ..Default::default()
565
+ },
566
+ );
567
+
568
+ let colors = data.raw_query_params.get("colors").unwrap();
569
+ assert_eq!(colors.len(), 3);
570
+ assert!(colors.contains(&"red".to_string()));
571
+ assert!(colors.contains(&"green".to_string()));
572
+ assert!(colors.contains(&"blue".to_string()));
573
+ }
574
+
575
+ #[test]
576
+ fn test_request_data_complex_json_body() {
577
+ let body = json!({
578
+ "user": {
579
+ "name": "Alice",
580
+ "profile": {
581
+ "age": 28,
582
+ "location": "San Francisco"
583
+ }
584
+ },
585
+ "tags": ["admin", "active"],
586
+ "metadata": {
587
+ "created_at": "2024-01-01",
588
+ "updated_at": "2024-12-01"
589
+ }
590
+ });
591
+
592
+ let data = create_request_data("/api/users", "POST", body, json!({}), RequestDataCollections::default());
593
+
594
+ assert_eq!(data.body["user"]["name"], "Alice");
595
+ assert_eq!(data.body["user"]["profile"]["age"], 28);
596
+ assert_eq!(data.body["tags"][0], "admin");
597
+ assert_eq!(data.body["metadata"]["created_at"], "2024-01-01");
598
+ }
599
+
600
+ #[test]
601
+ fn test_request_data_special_characters_in_paths() {
602
+ let data = create_request_data(
603
+ "/api/users/john@example.com/posts",
604
+ "GET",
605
+ json!(null),
606
+ json!({}),
607
+ RequestDataCollections::default(),
608
+ );
609
+
610
+ assert_eq!(data.path, "/api/users/john@example.com/posts");
611
+ }
612
+
613
+ #[test]
614
+ fn test_request_data_special_characters_in_params() {
615
+ let mut path_params = HashMap::new();
616
+ path_params.insert("email".to_string(), "test@example.com".to_string());
617
+ path_params.insert("slug".to_string(), "my-post-title".to_string());
618
+
619
+ let data = create_request_data(
620
+ "/api/users/test@example.com",
621
+ "GET",
622
+ json!(null),
623
+ json!({}),
624
+ RequestDataCollections {
625
+ path_params,
626
+ ..Default::default()
627
+ },
628
+ );
629
+
630
+ assert_eq!(data.path_params.get("email"), Some(&"test@example.com".to_string()));
631
+ assert_eq!(data.path_params.get("slug"), Some(&"my-post-title".to_string()));
632
+ }
633
+
634
+ #[test]
635
+ fn test_request_data_null_and_empty_values() {
636
+ let body = json!({
637
+ "name": null,
638
+ "email": "",
639
+ "age": 0,
640
+ "active": false
641
+ });
642
+
643
+ let data = create_request_data("/api/users", "POST", body, json!({}), RequestDataCollections::default());
644
+
645
+ assert!(data.body["name"].is_null());
646
+ assert_eq!(data.body["email"], "");
647
+ assert_eq!(data.body["age"], 0);
648
+ assert_eq!(data.body["active"], false);
649
+ }
650
+
651
+ #[test]
652
+ fn test_request_data_serialization() {
653
+ let mut path_params = HashMap::new();
654
+ path_params.insert("id".to_string(), "123".to_string());
655
+
656
+ let mut headers = HashMap::new();
657
+ headers.insert("content-type".to_string(), "application/json".to_string());
658
+
659
+ let data = create_request_data(
660
+ "/api/users/123",
661
+ "GET",
662
+ json!({"name": "test"}),
663
+ json!({"page": "1"}),
664
+ RequestDataCollections {
665
+ headers,
666
+ path_params,
667
+ ..Default::default()
668
+ },
669
+ );
670
+
671
+ let serialized = serde_json::to_string(&data);
672
+ assert!(serialized.is_ok());
673
+ }
674
+
675
+ #[test]
676
+ fn test_request_data_delete_request() {
677
+ let mut path_params = HashMap::new();
678
+ path_params.insert("id".to_string(), "999".to_string());
679
+
680
+ let data = create_request_data(
681
+ "/api/users/999",
682
+ "DELETE",
683
+ json!(null),
684
+ json!({}),
685
+ RequestDataCollections {
686
+ path_params,
687
+ ..Default::default()
688
+ },
689
+ );
690
+
691
+ assert_eq!(data.method, "DELETE");
692
+ assert_eq!(data.path_params.get("id"), Some(&"999".to_string()));
693
+ }
694
+
695
+ #[test]
696
+ fn test_request_data_array_body() {
697
+ let body = json!([
698
+ {"id": 1, "name": "item1"},
699
+ {"id": 2, "name": "item2"},
700
+ {"id": 3, "name": "item3"}
701
+ ]);
702
+
703
+ let data = create_request_data("/api/items", "POST", body, json!({}), RequestDataCollections::default());
704
+
705
+ assert!(data.body.is_array());
706
+ assert_eq!(data.body.as_array().unwrap().len(), 3);
707
+ assert_eq!(data.body[0]["id"], 1);
708
+ assert_eq!(data.body[2]["name"], "item3");
709
+ }
710
+
711
+ #[test]
712
+ fn test_request_data_case_sensitive_keys() {
713
+ let mut headers = HashMap::new();
714
+ headers.insert("Content-Type".to_string(), "application/json".to_string());
715
+ headers.insert("content-type".to_string(), "text/plain".to_string());
716
+
717
+ let data = create_request_data(
718
+ "/api/test",
719
+ "GET",
720
+ json!(null),
721
+ json!({}),
722
+ RequestDataCollections {
723
+ headers,
724
+ ..Default::default()
725
+ },
726
+ );
727
+
728
+ assert_eq!(data.headers.get("Content-Type"), Some(&"application/json".to_string()));
729
+ assert_eq!(data.headers.get("content-type"), Some(&"text/plain".to_string()));
730
+ }
731
+
732
+ #[test]
733
+ fn test_request_data_serialization_with_all_fields() {
734
+ let mut path_params = HashMap::new();
735
+ path_params.insert("id".to_string(), "42".to_string());
736
+
737
+ let mut raw_query_params = HashMap::new();
738
+ raw_query_params.insert("page".to_string(), vec!["2".to_string()]);
739
+
740
+ let mut headers = HashMap::new();
741
+ headers.insert("authorization".to_string(), "Bearer xyz".to_string());
742
+
743
+ let mut cookies = HashMap::new();
744
+ cookies.insert("token".to_string(), "abc123".to_string());
745
+
746
+ let data = create_request_data(
747
+ "/api/resource/42",
748
+ "PUT",
749
+ json!({"status": "updated"}),
750
+ json!({"page": "2"}),
751
+ RequestDataCollections {
752
+ raw_query_params,
753
+ headers,
754
+ cookies,
755
+ path_params,
756
+ },
757
+ );
758
+
759
+ let serialized = serde_json::to_string(&data).expect("serialization failed");
760
+ assert!(!serialized.is_empty());
761
+ assert!(serialized.contains("\"id\":\"42\""));
762
+ assert!(serialized.contains("PUT"));
763
+ }
764
+
765
+ #[test]
766
+ fn test_request_data_deserialization() {
767
+ let json_str = r#"{
768
+ "path_params": {"user_id": "123"},
769
+ "query_params": {"page": "1"},
770
+ "raw_query_params": {"page": ["1"]},
771
+ "body": {"name": "test"},
772
+ "raw_body": null,
773
+ "headers": {"content-type": "application/json"},
774
+ "cookies": {"session": "abc"},
775
+ "method": "POST",
776
+ "path": "/api/users"
777
+ }"#;
778
+
779
+ let deserialized: RequestData = serde_json::from_str(json_str).expect("deserialization failed");
780
+
781
+ assert_eq!(deserialized.method, "POST");
782
+ assert_eq!(deserialized.path, "/api/users");
783
+ assert_eq!(deserialized.path_params.get("user_id"), Some(&"123".to_string()));
784
+ assert_eq!(deserialized.body["name"], "test");
785
+ assert_eq!(
786
+ deserialized.headers.get("content-type"),
787
+ Some(&"application/json".to_string())
788
+ );
789
+ assert_eq!(deserialized.cookies.get("session"), Some(&"abc".to_string()));
790
+ }
791
+
792
+ #[test]
793
+ fn test_request_data_roundtrip_serialization() {
794
+ let mut path_params = HashMap::new();
795
+ path_params.insert("item_id".to_string(), "789".to_string());
796
+
797
+ let mut raw_query_params = HashMap::new();
798
+ raw_query_params.insert("sort".to_string(), vec!["desc".to_string()]);
799
+
800
+ let mut headers = HashMap::new();
801
+ headers.insert("x-custom-header".to_string(), "value123".to_string());
802
+
803
+ let mut cookies = HashMap::new();
804
+ cookies.insert("prefer".to_string(), "dark_mode".to_string());
805
+
806
+ let original = create_request_data(
807
+ "/api/items/789",
808
+ "DELETE",
809
+ json!({"reason": "archived"}),
810
+ json!({"sort": "desc"}),
811
+ RequestDataCollections {
812
+ raw_query_params,
813
+ headers,
814
+ cookies,
815
+ path_params,
816
+ },
817
+ );
818
+
819
+ let serialized = serde_json::to_string(&original).expect("serialization failed");
820
+ let deserialized: RequestData = serde_json::from_str(&serialized).expect("deserialization failed");
821
+
822
+ assert_eq!(deserialized.method, original.method);
823
+ assert_eq!(deserialized.path, original.path);
824
+ assert_eq!(deserialized.body, original.body);
825
+ assert_eq!(deserialized.path_params, original.path_params);
826
+ }
827
+
828
+ #[test]
829
+ fn test_request_data_large_json_body() {
830
+ let mut large_obj = serde_json::Map::new();
831
+ for i in 0..100 {
832
+ large_obj.insert(
833
+ format!("field_{}", i),
834
+ json!({"value": i, "name": format!("item_{}", i)}),
835
+ );
836
+ }
837
+ let body = json!(large_obj);
838
+
839
+ let data = create_request_data(
840
+ "/api/batch",
841
+ "POST",
842
+ body.clone(),
843
+ json!({}),
844
+ RequestDataCollections::default(),
845
+ );
846
+
847
+ assert!(data.body.is_object());
848
+ assert_eq!(data.body.as_object().unwrap().len(), 100);
849
+ }
850
+
851
+ #[test]
852
+ fn test_request_data_deeply_nested_json() {
853
+ let body = json!({
854
+ "level1": {
855
+ "level2": {
856
+ "level3": {
857
+ "level4": {
858
+ "level5": {
859
+ "value": "deep"
860
+ }
861
+ }
862
+ }
863
+ }
864
+ }
865
+ });
866
+
867
+ let data = create_request_data("/api/nested", "GET", body, json!({}), RequestDataCollections::default());
868
+
869
+ assert_eq!(
870
+ data.body["level1"]["level2"]["level3"]["level4"]["level5"]["value"],
871
+ "deep"
872
+ );
873
+ }
874
+
875
+ #[test]
876
+ fn test_request_data_unicode_in_fields() {
877
+ let mut path_params = HashMap::new();
878
+ path_params.insert("name".to_string(), "用户".to_string());
879
+
880
+ let mut cookies = HashMap::new();
881
+ cookies.insert("msg".to_string(), "こんにちは".to_string());
882
+
883
+ let body = json!({
884
+ "greeting": "مرحبا",
885
+ "emoji": "🚀"
886
+ });
887
+
888
+ let data = create_request_data(
889
+ "/api/users/用户",
890
+ "POST",
891
+ body,
892
+ json!({}),
893
+ RequestDataCollections {
894
+ cookies,
895
+ path_params,
896
+ ..Default::default()
897
+ },
898
+ );
899
+
900
+ assert_eq!(data.path_params.get("name"), Some(&"用户".to_string()));
901
+ assert_eq!(data.cookies.get("msg"), Some(&"こんにちは".to_string()));
902
+ assert_eq!(data.body["emoji"], "🚀");
903
+ }
904
+
905
+ #[test]
906
+ fn test_request_data_multiple_headers_with_same_prefix() {
907
+ let mut headers = HashMap::new();
908
+ headers.insert("x-custom-1".to_string(), "value1".to_string());
909
+ headers.insert("x-custom-2".to_string(), "value2".to_string());
910
+ headers.insert("x-custom-3".to_string(), "value3".to_string());
911
+
912
+ let data = create_request_data(
913
+ "/api/test",
914
+ "GET",
915
+ json!(null),
916
+ json!({}),
917
+ RequestDataCollections {
918
+ headers,
919
+ ..Default::default()
920
+ },
921
+ );
922
+
923
+ assert_eq!(data.headers.get("x-custom-1"), Some(&"value1".to_string()));
924
+ assert_eq!(data.headers.get("x-custom-2"), Some(&"value2".to_string()));
925
+ assert_eq!(data.headers.get("x-custom-3"), Some(&"value3".to_string()));
926
+ assert_eq!(data.headers.len(), 3);
927
+ }
928
+
929
+ #[test]
930
+ fn test_request_data_numeric_values_in_json() {
931
+ let body = json!({
932
+ "int": 42,
933
+ "float": 3.2,
934
+ "negative": -100,
935
+ "zero": 0,
936
+ "large": 9223372036854775807i64
937
+ });
938
+
939
+ let data = create_request_data(
940
+ "/api/numbers",
941
+ "POST",
942
+ body,
943
+ json!({}),
944
+ RequestDataCollections::default(),
945
+ );
946
+
947
+ assert_eq!(data.body["int"], 42);
948
+ assert_eq!(data.body["float"], 3.2);
949
+ assert_eq!(data.body["negative"], -100);
950
+ assert_eq!(data.body["zero"], 0);
951
+ }
952
+
953
+ #[test]
954
+ fn test_request_data_boolean_values_in_json() {
955
+ let body = json!({
956
+ "is_active": true,
957
+ "is_admin": false
958
+ });
959
+
960
+ let data = create_request_data("/api/config", "GET", body, json!({}), RequestDataCollections::default());
961
+
962
+ assert_eq!(data.body["is_active"], true);
963
+ assert_eq!(data.body["is_admin"], false);
964
+ }
965
+
966
+ #[test]
967
+ fn test_request_data_missing_optional_raw_body() {
968
+ let data = create_request_data(
969
+ "/api/test",
970
+ "GET",
971
+ json!(null),
972
+ json!({}),
973
+ RequestDataCollections::default(),
974
+ );
975
+
976
+ assert!(data.raw_body.is_none());
977
+ }
978
+
979
+ #[test]
980
+ fn test_request_data_path_with_query_string_format() {
981
+ let data = create_request_data(
982
+ "/api/search?q=test&limit=10",
983
+ "GET",
984
+ json!(null),
985
+ json!({"q": "test", "limit": "10"}),
986
+ RequestDataCollections::default(),
987
+ );
988
+
989
+ assert_eq!(data.path, "/api/search?q=test&limit=10");
990
+ }
991
+
992
+ #[test]
993
+ fn test_request_data_root_path() {
994
+ let data = create_request_data("/", "GET", json!(null), json!({}), RequestDataCollections::default());
995
+
996
+ assert_eq!(data.path, "/");
997
+ }
998
+
999
+ #[test]
1000
+ fn test_request_data_empty_string_values() {
1001
+ let mut headers = HashMap::new();
1002
+ headers.insert("empty-header".to_string(), "".to_string());
1003
+
1004
+ let mut path_params = HashMap::new();
1005
+ path_params.insert("id".to_string(), "".to_string());
1006
+
1007
+ let data = create_request_data(
1008
+ "/api/test",
1009
+ "GET",
1010
+ json!(null),
1011
+ json!({}),
1012
+ RequestDataCollections {
1013
+ headers,
1014
+ path_params,
1015
+ ..Default::default()
1016
+ },
1017
+ );
1018
+
1019
+ assert_eq!(data.headers.get("empty-header"), Some(&"".to_string()));
1020
+ assert_eq!(data.path_params.get("id"), Some(&"".to_string()));
1021
+ }
1022
+
1023
+ #[test]
1024
+ fn test_request_data_deserialization_missing_field() {
1025
+ let json_str = r#"{
1026
+ "path_params": {"user_id": "123"},
1027
+ "query_params": {"page": "1"},
1028
+ "raw_query_params": {"page": ["1"]},
1029
+ "body": {"name": "test"},
1030
+ "raw_body": null,
1031
+ "headers": {"content-type": "application/json"},
1032
+ "cookies": {"session": "abc"}
1033
+ }"#;
1034
+
1035
+ let result: Result<RequestData, _> = serde_json::from_str(json_str);
1036
+ assert!(result.is_err());
1037
+ }
1038
+
1039
+ #[test]
1040
+ fn test_request_data_deserialization_extra_fields_rejected() {
1041
+ let json_str = r#"{
1042
+ "path_params": {"user_id": "123"},
1043
+ "query_params": {"page": "1"},
1044
+ "raw_query_params": {"page": ["1"]},
1045
+ "body": {"name": "test"},
1046
+ "raw_body": null,
1047
+ "headers": {"content-type": "application/json"},
1048
+ "cookies": {"session": "abc"},
1049
+ "method": "GET",
1050
+ "path": "/api/test",
1051
+ "extra_field": "not allowed"
1052
+ }"#;
1053
+
1054
+ let result: Result<RequestData, _> = serde_json::from_str(json_str);
1055
+ assert!(result.is_err());
1056
+ }
1057
+
1058
+ #[test]
1059
+ fn test_request_data_multiple_raw_body_values() {
1060
+ let mut raw_query_params = HashMap::new();
1061
+ raw_query_params.insert(
1062
+ "data".to_string(),
1063
+ vec!["val1".to_string(), "val2".to_string(), "val3".to_string()],
1064
+ );
1065
+
1066
+ let data = create_request_data(
1067
+ "/api/test",
1068
+ "POST",
1069
+ json!(null),
1070
+ json!({}),
1071
+ RequestDataCollections {
1072
+ raw_query_params,
1073
+ ..Default::default()
1074
+ },
1075
+ );
1076
+
1077
+ let values = data.raw_query_params.get("data").unwrap();
1078
+ assert_eq!(values.len(), 3);
1079
+ assert_eq!(values[0], "val1");
1080
+ assert_eq!(values[1], "val2");
1081
+ assert_eq!(values[2], "val3");
1082
+ }
1083
+
1084
+ #[test]
1085
+ fn test_request_data_debug_output() {
1086
+ let data = create_request_data(
1087
+ "/api/test",
1088
+ "GET",
1089
+ json!({"key": "value"}),
1090
+ json!({}),
1091
+ RequestDataCollections::default(),
1092
+ );
1093
+
1094
+ let debug_str = format!("{:?}", data);
1095
+ assert!(debug_str.contains("RequestData"));
1096
+ assert!(debug_str.contains("/api/test"));
1097
+ }
1098
+
1099
+ #[test]
1100
+ fn test_request_data_clone_independence() {
1101
+ let mut path_params1 = HashMap::new();
1102
+ path_params1.insert("id".to_string(), "original".to_string());
1103
+
1104
+ let data1 = create_request_data(
1105
+ "/api/users/1",
1106
+ "GET",
1107
+ json!({"name": "original"}),
1108
+ json!({}),
1109
+ RequestDataCollections {
1110
+ path_params: path_params1,
1111
+ ..Default::default()
1112
+ },
1113
+ );
1114
+
1115
+ let data2 = data1.clone();
1116
+
1117
+ assert_eq!(data1.path_params.get("id"), data2.path_params.get("id"));
1118
+ assert_eq!(data1.body["name"], data2.body["name"]);
1119
+ }
1120
+
1121
+ #[test]
1122
+ fn deserialize_errors_on_missing_required_field() {
1123
+ let value = json!({
1124
+ "path_params": {},
1125
+ "query_params": {},
1126
+ "raw_query_params": {},
1127
+ "body": null,
1128
+ "raw_body": null,
1129
+ "headers": {},
1130
+ "cookies": {},
1131
+ "method": "GET"
1132
+ });
1133
+
1134
+ let err = serde_json::from_value::<RequestData>(value).unwrap_err();
1135
+ assert!(err.to_string().contains("missing field"));
1136
+ }
1137
+
1138
+ #[test]
1139
+ fn deserialize_errors_on_invalid_raw_body_type() {
1140
+ let value = json!({
1141
+ "path_params": {},
1142
+ "query_params": {},
1143
+ "raw_query_params": {},
1144
+ "body": null,
1145
+ "raw_body": "not-bytes",
1146
+ "headers": {},
1147
+ "cookies": {},
1148
+ "method": "GET",
1149
+ "path": "/"
1150
+ });
1151
+
1152
+ assert!(serde_json::from_value::<RequestData>(value).is_err());
1153
+ }
1154
+ }