spikard 0.6.2 → 0.7.2
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.
- checksums.yaml +4 -4
- data/README.md +90 -508
- data/ext/spikard_rb/Cargo.lock +3287 -0
- data/ext/spikard_rb/Cargo.toml +1 -1
- data/ext/spikard_rb/extconf.rb +3 -3
- data/lib/spikard/app.rb +72 -49
- data/lib/spikard/background.rb +38 -7
- data/lib/spikard/testing.rb +42 -4
- data/lib/spikard/version.rb +1 -1
- data/sig/spikard.rbs +4 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +2 -2
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +191 -0
- data/vendor/crates/spikard-core/Cargo.toml +1 -1
- data/vendor/crates/spikard-core/src/http.rs +1 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +63 -0
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +136 -0
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +37 -0
- data/vendor/crates/spikard-core/tests/error_mapper.rs +761 -0
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +106 -0
- data/vendor/crates/spikard-core/tests/parameters_full.rs +701 -0
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +301 -0
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +67 -0
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +250 -0
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +45 -0
- data/vendor/crates/spikard-http/Cargo.toml +1 -1
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +502 -0
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +648 -0
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +58 -0
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +1207 -0
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +2262 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +155 -2
- data/vendor/crates/spikard-http/src/testing.rs +171 -0
- data/vendor/crates/spikard-http/src/websocket.rs +79 -6
- data/vendor/crates/spikard-http/tests/auth_integration.rs +647 -0
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +633 -0
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +162 -0
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +389 -0
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +513 -0
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +244 -0
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +200 -0
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +82 -0
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +464 -0
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +286 -0
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +118 -0
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +99 -0
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +206 -0
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +281 -0
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +121 -0
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +584 -0
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +130 -0
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +167 -0
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +87 -0
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +156 -0
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +82 -0
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +440 -0
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +152 -0
- data/vendor/crates/spikard-rb/Cargo.toml +1 -1
- data/vendor/crates/spikard-rb/src/gvl.rs +80 -0
- data/vendor/crates/spikard-rb/src/handler.rs +12 -9
- data/vendor/crates/spikard-rb/src/lib.rs +137 -124
- data/vendor/crates/spikard-rb/src/request.rs +342 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +1 -8
- data/vendor/crates/spikard-rb/src/server.rs +1 -8
- data/vendor/crates/spikard-rb/src/testing/client.rs +168 -9
- data/vendor/crates/spikard-rb/src/websocket.rs +119 -30
- data/vendor/crates/spikard-rb-macros/Cargo.toml +14 -0
- data/vendor/crates/spikard-rb-macros/src/lib.rs +52 -0
- metadata +44 -1
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
//! Comprehensive parameter validation tests
|
|
2
|
+
//!
|
|
3
|
+
//! These tests cover header, cookie, and query parameter validation scenarios
|
|
4
|
+
//! using the ParameterValidator from spikard-core.
|
|
5
|
+
|
|
6
|
+
use serde_json::json;
|
|
7
|
+
use spikard_core::parameters::ParameterValidator;
|
|
8
|
+
use std::collections::HashMap;
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
#[test]
|
|
13
|
+
fn test_required_header_present() {
|
|
14
|
+
let schema = json!({
|
|
15
|
+
"type": "object",
|
|
16
|
+
"properties": {
|
|
17
|
+
"X-API-Key": {
|
|
18
|
+
"type": "string",
|
|
19
|
+
"source": "header"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": ["X-API-Key"]
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
26
|
+
|
|
27
|
+
let mut headers = HashMap::new();
|
|
28
|
+
headers.insert("x-api-key".to_string(), "secret-key-123".to_string());
|
|
29
|
+
|
|
30
|
+
let result =
|
|
31
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &headers, &HashMap::new());
|
|
32
|
+
|
|
33
|
+
assert!(result.is_ok(), "Validation should succeed with required header present");
|
|
34
|
+
let extracted = result.unwrap();
|
|
35
|
+
assert_eq!(extracted["X-API-Key"], "secret-key-123");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[test]
|
|
39
|
+
fn test_required_header_missing() {
|
|
40
|
+
let schema = json!({
|
|
41
|
+
"type": "object",
|
|
42
|
+
"properties": {
|
|
43
|
+
"X-API-Key": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"source": "header"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": ["X-API-Key"]
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
52
|
+
|
|
53
|
+
let result = validator.validate_and_extract(
|
|
54
|
+
&json!({}),
|
|
55
|
+
&HashMap::new(),
|
|
56
|
+
&HashMap::new(),
|
|
57
|
+
&HashMap::new(),
|
|
58
|
+
&HashMap::new(),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
assert!(
|
|
62
|
+
result.is_err(),
|
|
63
|
+
"Validation should fail when required header is missing"
|
|
64
|
+
);
|
|
65
|
+
let err = result.unwrap_err();
|
|
66
|
+
assert_eq!(err.errors.len(), 1);
|
|
67
|
+
assert_eq!(err.errors[0].error_type, "missing");
|
|
68
|
+
assert_eq!(err.errors[0].loc, vec!["headers", "x-api-key"]);
|
|
69
|
+
assert_eq!(err.errors[0].msg, "Field required");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[test]
|
|
73
|
+
fn test_header_type_validation() {
|
|
74
|
+
let schema = json!({
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"X-Request-ID": {
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"source": "header"
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": ["X-Request-ID"]
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
86
|
+
|
|
87
|
+
let mut headers = HashMap::new();
|
|
88
|
+
headers.insert("x-request-id".to_string(), "not-a-number".to_string());
|
|
89
|
+
|
|
90
|
+
let result =
|
|
91
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &headers, &HashMap::new());
|
|
92
|
+
|
|
93
|
+
assert!(result.is_err(), "Validation should fail with invalid integer header");
|
|
94
|
+
let err = result.unwrap_err();
|
|
95
|
+
assert_eq!(err.errors.len(), 1);
|
|
96
|
+
assert_eq!(err.errors[0].error_type, "int_parsing");
|
|
97
|
+
assert_eq!(err.errors[0].loc, vec!["headers", "x-request-id"]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
#[test]
|
|
101
|
+
fn test_header_format_validation() {
|
|
102
|
+
let schema = json!({
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"X-Trace-ID": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"format": "uuid",
|
|
108
|
+
"source": "header"
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"required": ["X-Trace-ID"]
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
115
|
+
|
|
116
|
+
let mut headers = HashMap::new();
|
|
117
|
+
headers.insert("x-trace-id".to_string(), "invalid-uuid".to_string());
|
|
118
|
+
|
|
119
|
+
let result =
|
|
120
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &headers, &HashMap::new());
|
|
121
|
+
|
|
122
|
+
assert!(result.is_err(), "Validation should fail with invalid UUID format");
|
|
123
|
+
let err = result.unwrap_err();
|
|
124
|
+
assert_eq!(err.errors[0].error_type, "uuid_parsing");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
#[test]
|
|
128
|
+
fn test_optional_header_missing() {
|
|
129
|
+
let schema = json!({
|
|
130
|
+
"type": "object",
|
|
131
|
+
"properties": {
|
|
132
|
+
"X-Optional-Header": {
|
|
133
|
+
"type": "string",
|
|
134
|
+
"source": "header",
|
|
135
|
+
"optional": true
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
"required": []
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
142
|
+
|
|
143
|
+
let result = validator.validate_and_extract(
|
|
144
|
+
&json!({}),
|
|
145
|
+
&HashMap::new(),
|
|
146
|
+
&HashMap::new(),
|
|
147
|
+
&HashMap::new(),
|
|
148
|
+
&HashMap::new(),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
assert!(
|
|
152
|
+
result.is_ok(),
|
|
153
|
+
"Validation should succeed when optional header is missing"
|
|
154
|
+
);
|
|
155
|
+
let extracted = result.unwrap();
|
|
156
|
+
assert_eq!(extracted.as_object().unwrap().len(), 0);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#[test]
|
|
160
|
+
fn test_multiple_headers_validation() {
|
|
161
|
+
let schema = json!({
|
|
162
|
+
"type": "object",
|
|
163
|
+
"properties": {
|
|
164
|
+
"X-API-Key": {
|
|
165
|
+
"type": "string",
|
|
166
|
+
"source": "header"
|
|
167
|
+
},
|
|
168
|
+
"X-Request-ID": {
|
|
169
|
+
"type": "integer",
|
|
170
|
+
"source": "header"
|
|
171
|
+
},
|
|
172
|
+
"X-Version": {
|
|
173
|
+
"type": "string",
|
|
174
|
+
"source": "header"
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"required": ["X-API-Key", "X-Request-ID", "X-Version"]
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
181
|
+
|
|
182
|
+
let mut headers = HashMap::new();
|
|
183
|
+
headers.insert("x-api-key".to_string(), "key123".to_string());
|
|
184
|
+
headers.insert("x-request-id".to_string(), "not-a-number".to_string());
|
|
185
|
+
|
|
186
|
+
let result =
|
|
187
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &headers, &HashMap::new());
|
|
188
|
+
|
|
189
|
+
assert!(result.is_err(), "Validation should fail with multiple errors");
|
|
190
|
+
let err = result.unwrap_err();
|
|
191
|
+
assert_eq!(err.errors.len(), 2);
|
|
192
|
+
|
|
193
|
+
let error_types: Vec<&str> = err.errors.iter().map(|e| e.error_type.as_str()).collect();
|
|
194
|
+
assert!(error_types.contains(&"int_parsing"));
|
|
195
|
+
assert!(error_types.contains(&"missing"));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn test_required_cookie_present() {
|
|
200
|
+
let schema = json!({
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {
|
|
203
|
+
"session_id": {
|
|
204
|
+
"type": "string",
|
|
205
|
+
"source": "cookie"
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
"required": ["session_id"]
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
212
|
+
|
|
213
|
+
let mut cookies = HashMap::new();
|
|
214
|
+
cookies.insert("session_id".to_string(), "abc123xyz789".to_string());
|
|
215
|
+
|
|
216
|
+
let result =
|
|
217
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &HashMap::new(), &cookies);
|
|
218
|
+
|
|
219
|
+
assert!(result.is_ok(), "Validation should succeed with required cookie present");
|
|
220
|
+
let extracted = result.unwrap();
|
|
221
|
+
assert_eq!(extracted["session_id"], "abc123xyz789");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
#[test]
|
|
225
|
+
fn test_required_cookie_missing() {
|
|
226
|
+
let schema = json!({
|
|
227
|
+
"type": "object",
|
|
228
|
+
"properties": {
|
|
229
|
+
"session_id": {
|
|
230
|
+
"type": "string",
|
|
231
|
+
"source": "cookie"
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
"required": ["session_id"]
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
238
|
+
|
|
239
|
+
let result = validator.validate_and_extract(
|
|
240
|
+
&json!({}),
|
|
241
|
+
&HashMap::new(),
|
|
242
|
+
&HashMap::new(),
|
|
243
|
+
&HashMap::new(),
|
|
244
|
+
&HashMap::new(),
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
assert!(
|
|
248
|
+
result.is_err(),
|
|
249
|
+
"Validation should fail when required cookie is missing"
|
|
250
|
+
);
|
|
251
|
+
let err = result.unwrap_err();
|
|
252
|
+
assert_eq!(err.errors[0].error_type, "missing");
|
|
253
|
+
assert_eq!(err.errors[0].loc, vec!["cookie", "session_id"]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn test_cookie_value_validation() {
|
|
258
|
+
let schema = json!({
|
|
259
|
+
"type": "object",
|
|
260
|
+
"properties": {
|
|
261
|
+
"user_role": {
|
|
262
|
+
"type": "string",
|
|
263
|
+
"enum": ["admin", "user", "guest"],
|
|
264
|
+
"source": "cookie"
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
"required": ["user_role"]
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
271
|
+
|
|
272
|
+
let mut cookies = HashMap::new();
|
|
273
|
+
cookies.insert("user_role".to_string(), "invalid_role".to_string());
|
|
274
|
+
|
|
275
|
+
let result =
|
|
276
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &HashMap::new(), &cookies);
|
|
277
|
+
|
|
278
|
+
assert!(result.is_err(), "Validation should fail with invalid enum value");
|
|
279
|
+
let err = result.unwrap_err();
|
|
280
|
+
assert_eq!(err.errors[0].error_type, "enum");
|
|
281
|
+
assert_eq!(err.errors[0].loc, vec!["cookie", "user_role"]);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
#[test]
|
|
285
|
+
fn test_cookie_type_coercion() {
|
|
286
|
+
let schema = json!({
|
|
287
|
+
"type": "object",
|
|
288
|
+
"properties": {
|
|
289
|
+
"preferences": {
|
|
290
|
+
"type": "integer",
|
|
291
|
+
"source": "cookie"
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
"required": ["preferences"]
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
298
|
+
|
|
299
|
+
let mut cookies = HashMap::new();
|
|
300
|
+
cookies.insert("preferences".to_string(), "42".to_string());
|
|
301
|
+
|
|
302
|
+
let result =
|
|
303
|
+
validator.validate_and_extract(&json!({}), &HashMap::new(), &HashMap::new(), &HashMap::new(), &cookies);
|
|
304
|
+
|
|
305
|
+
assert!(result.is_ok(), "Validation should succeed with valid integer coercion");
|
|
306
|
+
let extracted = result.unwrap();
|
|
307
|
+
assert_eq!(extracted["preferences"], 42);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#[test]
|
|
311
|
+
fn test_query_param_type_coercion() {
|
|
312
|
+
let schema = json!({
|
|
313
|
+
"type": "object",
|
|
314
|
+
"properties": {
|
|
315
|
+
"page": {
|
|
316
|
+
"type": "integer",
|
|
317
|
+
"source": "query"
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"required": ["page"]
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
324
|
+
|
|
325
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
326
|
+
raw_query_params.insert("page".to_string(), vec!["5".to_string()]);
|
|
327
|
+
|
|
328
|
+
let result = validator.validate_and_extract(
|
|
329
|
+
&json!({"page": 5}),
|
|
330
|
+
&raw_query_params,
|
|
331
|
+
&HashMap::new(),
|
|
332
|
+
&HashMap::new(),
|
|
333
|
+
&HashMap::new(),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
assert!(result.is_ok(), "Validation should succeed with type coercion");
|
|
337
|
+
let extracted = result.unwrap();
|
|
338
|
+
assert_eq!(extracted["page"], 5);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
#[test]
|
|
342
|
+
fn test_query_param_invalid_type() {
|
|
343
|
+
let schema = json!({
|
|
344
|
+
"type": "object",
|
|
345
|
+
"properties": {
|
|
346
|
+
"page": {
|
|
347
|
+
"type": "integer",
|
|
348
|
+
"source": "query"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"required": ["page"]
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
355
|
+
|
|
356
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
357
|
+
raw_query_params.insert("page".to_string(), vec!["abc".to_string()]);
|
|
358
|
+
|
|
359
|
+
let result = validator.validate_and_extract(
|
|
360
|
+
&json!({"page": "abc"}),
|
|
361
|
+
&raw_query_params,
|
|
362
|
+
&HashMap::new(),
|
|
363
|
+
&HashMap::new(),
|
|
364
|
+
&HashMap::new(),
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
assert!(result.is_err(), "Validation should fail with invalid integer");
|
|
368
|
+
let err = result.unwrap_err();
|
|
369
|
+
assert_eq!(err.errors[0].error_type, "int_parsing");
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
#[test]
|
|
373
|
+
fn test_query_param_enum_validation() {
|
|
374
|
+
let schema = json!({
|
|
375
|
+
"type": "object",
|
|
376
|
+
"properties": {
|
|
377
|
+
"status": {
|
|
378
|
+
"type": "string",
|
|
379
|
+
"enum": ["active", "inactive", "pending"],
|
|
380
|
+
"source": "query"
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
"required": ["status"]
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
387
|
+
|
|
388
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
389
|
+
raw_query_params.insert("status".to_string(), vec!["invalid".to_string()]);
|
|
390
|
+
|
|
391
|
+
let result = validator.validate_and_extract(
|
|
392
|
+
&json!({"status": "invalid"}),
|
|
393
|
+
&raw_query_params,
|
|
394
|
+
&HashMap::new(),
|
|
395
|
+
&HashMap::new(),
|
|
396
|
+
&HashMap::new(),
|
|
397
|
+
);
|
|
398
|
+
|
|
399
|
+
assert!(result.is_err(), "Validation should fail with invalid enum value");
|
|
400
|
+
let err = result.unwrap_err();
|
|
401
|
+
assert_eq!(err.errors[0].error_type, "enum");
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#[test]
|
|
405
|
+
fn test_query_param_array_validation() {
|
|
406
|
+
let schema = json!({
|
|
407
|
+
"type": "object",
|
|
408
|
+
"properties": {
|
|
409
|
+
"ids": {
|
|
410
|
+
"type": "array",
|
|
411
|
+
"items": {"type": "integer"},
|
|
412
|
+
"source": "query"
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
"required": []
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
419
|
+
|
|
420
|
+
let query_params = json!({
|
|
421
|
+
"ids": [1, 2, 3]
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
let result = validator.validate_and_extract(
|
|
425
|
+
&query_params,
|
|
426
|
+
&HashMap::new(),
|
|
427
|
+
&HashMap::new(),
|
|
428
|
+
&HashMap::new(),
|
|
429
|
+
&HashMap::new(),
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
assert!(result.is_ok(), "Validation should succeed with valid array");
|
|
433
|
+
let extracted = result.unwrap();
|
|
434
|
+
assert_eq!(extracted["ids"], json!([1, 2, 3]));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
#[test]
|
|
438
|
+
fn test_query_param_boolean_coercion_true() {
|
|
439
|
+
let schema = json!({
|
|
440
|
+
"type": "object",
|
|
441
|
+
"properties": {
|
|
442
|
+
"include_deleted": {
|
|
443
|
+
"type": "boolean",
|
|
444
|
+
"source": "query"
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
"required": []
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
451
|
+
|
|
452
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
453
|
+
raw_query_params.insert("include_deleted".to_string(), vec!["true".to_string()]);
|
|
454
|
+
|
|
455
|
+
let result = validator.validate_and_extract(
|
|
456
|
+
&json!({"include_deleted": true}),
|
|
457
|
+
&raw_query_params,
|
|
458
|
+
&HashMap::new(),
|
|
459
|
+
&HashMap::new(),
|
|
460
|
+
&HashMap::new(),
|
|
461
|
+
);
|
|
462
|
+
|
|
463
|
+
assert!(result.is_ok(), "Validation should succeed with boolean coercion");
|
|
464
|
+
let extracted = result.unwrap();
|
|
465
|
+
assert_eq!(extracted["include_deleted"], true);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
#[test]
|
|
469
|
+
fn test_query_param_boolean_coercion_false() {
|
|
470
|
+
let schema = json!({
|
|
471
|
+
"type": "object",
|
|
472
|
+
"properties": {
|
|
473
|
+
"include_deleted": {
|
|
474
|
+
"type": "boolean",
|
|
475
|
+
"source": "query"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
"required": []
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
482
|
+
|
|
483
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
484
|
+
raw_query_params.insert("include_deleted".to_string(), vec!["false".to_string()]);
|
|
485
|
+
|
|
486
|
+
let result = validator.validate_and_extract(
|
|
487
|
+
&json!({"include_deleted": false}),
|
|
488
|
+
&raw_query_params,
|
|
489
|
+
&HashMap::new(),
|
|
490
|
+
&HashMap::new(),
|
|
491
|
+
&HashMap::new(),
|
|
492
|
+
);
|
|
493
|
+
|
|
494
|
+
assert!(result.is_ok(), "Validation should succeed with boolean false");
|
|
495
|
+
let extracted = result.unwrap();
|
|
496
|
+
assert_eq!(extracted["include_deleted"], false);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
#[test]
|
|
500
|
+
fn test_query_param_string_format_date() {
|
|
501
|
+
let schema = json!({
|
|
502
|
+
"type": "object",
|
|
503
|
+
"properties": {
|
|
504
|
+
"created_after": {
|
|
505
|
+
"type": "string",
|
|
506
|
+
"format": "date",
|
|
507
|
+
"source": "query"
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
"required": ["created_after"]
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
514
|
+
|
|
515
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
516
|
+
raw_query_params.insert("created_after".to_string(), vec!["2024-12-25".to_string()]);
|
|
517
|
+
|
|
518
|
+
let result = validator.validate_and_extract(
|
|
519
|
+
&json!({"created_after": "2024-12-25"}),
|
|
520
|
+
&raw_query_params,
|
|
521
|
+
&HashMap::new(),
|
|
522
|
+
&HashMap::new(),
|
|
523
|
+
&HashMap::new(),
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
assert!(result.is_ok(), "Validation should succeed with valid date format");
|
|
527
|
+
let extracted = result.unwrap();
|
|
528
|
+
assert_eq!(extracted["created_after"], "2024-12-25");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
#[test]
|
|
532
|
+
fn test_query_param_string_format_invalid_date() {
|
|
533
|
+
let schema = json!({
|
|
534
|
+
"type": "object",
|
|
535
|
+
"properties": {
|
|
536
|
+
"created_after": {
|
|
537
|
+
"type": "string",
|
|
538
|
+
"format": "date",
|
|
539
|
+
"source": "query"
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
"required": ["created_after"]
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
546
|
+
|
|
547
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
548
|
+
raw_query_params.insert("created_after".to_string(), vec!["not-a-date".to_string()]);
|
|
549
|
+
|
|
550
|
+
let result = validator.validate_and_extract(
|
|
551
|
+
&json!({"created_after": "not-a-date"}),
|
|
552
|
+
&raw_query_params,
|
|
553
|
+
&HashMap::new(),
|
|
554
|
+
&HashMap::new(),
|
|
555
|
+
&HashMap::new(),
|
|
556
|
+
);
|
|
557
|
+
|
|
558
|
+
assert!(result.is_err(), "Validation should fail with invalid date format");
|
|
559
|
+
let err = result.unwrap_err();
|
|
560
|
+
assert_eq!(err.errors[0].error_type, "date_parsing");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
#[test]
|
|
564
|
+
fn test_path_param_string() {
|
|
565
|
+
let schema = json!({
|
|
566
|
+
"type": "object",
|
|
567
|
+
"properties": {
|
|
568
|
+
"user_id": {
|
|
569
|
+
"type": "string",
|
|
570
|
+
"source": "path"
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
"required": ["user_id"]
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
577
|
+
|
|
578
|
+
let mut path_params = HashMap::new();
|
|
579
|
+
path_params.insert("user_id".to_string(), "alice".to_string());
|
|
580
|
+
|
|
581
|
+
let result = validator.validate_and_extract(
|
|
582
|
+
&json!({}),
|
|
583
|
+
&HashMap::new(),
|
|
584
|
+
&path_params,
|
|
585
|
+
&HashMap::new(),
|
|
586
|
+
&HashMap::new(),
|
|
587
|
+
);
|
|
588
|
+
|
|
589
|
+
assert!(result.is_ok(), "Validation should succeed with path parameter");
|
|
590
|
+
let extracted = result.unwrap();
|
|
591
|
+
assert_eq!(extracted["user_id"], "alice");
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
#[test]
|
|
595
|
+
fn test_path_param_integer() {
|
|
596
|
+
let schema = json!({
|
|
597
|
+
"type": "object",
|
|
598
|
+
"properties": {
|
|
599
|
+
"post_id": {
|
|
600
|
+
"type": "integer",
|
|
601
|
+
"source": "path"
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
"required": ["post_id"]
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
608
|
+
|
|
609
|
+
let mut path_params = HashMap::new();
|
|
610
|
+
path_params.insert("post_id".to_string(), "123".to_string());
|
|
611
|
+
|
|
612
|
+
let result = validator.validate_and_extract(
|
|
613
|
+
&json!({}),
|
|
614
|
+
&HashMap::new(),
|
|
615
|
+
&path_params,
|
|
616
|
+
&HashMap::new(),
|
|
617
|
+
&HashMap::new(),
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
assert!(result.is_ok(), "Validation should succeed with integer path parameter");
|
|
621
|
+
let extracted = result.unwrap();
|
|
622
|
+
assert_eq!(extracted["post_id"], 123);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
#[test]
|
|
626
|
+
fn test_combined_header_query_cookie_validation() {
|
|
627
|
+
let schema = json!({
|
|
628
|
+
"type": "object",
|
|
629
|
+
"properties": {
|
|
630
|
+
"X-API-Key": {
|
|
631
|
+
"type": "string",
|
|
632
|
+
"source": "header"
|
|
633
|
+
},
|
|
634
|
+
"limit": {
|
|
635
|
+
"type": "integer",
|
|
636
|
+
"source": "query"
|
|
637
|
+
},
|
|
638
|
+
"session_id": {
|
|
639
|
+
"type": "string",
|
|
640
|
+
"source": "cookie"
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
"required": ["X-API-Key", "limit", "session_id"]
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
647
|
+
|
|
648
|
+
let mut headers = HashMap::new();
|
|
649
|
+
headers.insert("x-api-key".to_string(), "key123".to_string());
|
|
650
|
+
|
|
651
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
652
|
+
raw_query_params.insert("limit".to_string(), vec!["10".to_string()]);
|
|
653
|
+
|
|
654
|
+
let mut cookies = HashMap::new();
|
|
655
|
+
cookies.insert("session_id".to_string(), "sess456".to_string());
|
|
656
|
+
|
|
657
|
+
let result = validator.validate_and_extract(
|
|
658
|
+
&json!({"limit": 10}),
|
|
659
|
+
&raw_query_params,
|
|
660
|
+
&HashMap::new(),
|
|
661
|
+
&headers,
|
|
662
|
+
&cookies,
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
assert!(result.is_ok(), "Validation should succeed with all parameters");
|
|
666
|
+
let extracted = result.unwrap();
|
|
667
|
+
assert_eq!(extracted["X-API-Key"], "key123");
|
|
668
|
+
assert_eq!(extracted["limit"], 10);
|
|
669
|
+
assert_eq!(extracted["session_id"], "sess456");
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
#[test]
|
|
673
|
+
fn test_number_float_coercion() {
|
|
674
|
+
let schema = json!({
|
|
675
|
+
"type": "object",
|
|
676
|
+
"properties": {
|
|
677
|
+
"price": {
|
|
678
|
+
"type": "number",
|
|
679
|
+
"source": "query"
|
|
680
|
+
}
|
|
681
|
+
},
|
|
682
|
+
"required": ["price"]
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
let validator = ParameterValidator::new(schema).expect("Failed to create validator");
|
|
686
|
+
|
|
687
|
+
let mut raw_query_params: HashMap<String, Vec<String>> = HashMap::new();
|
|
688
|
+
raw_query_params.insert("price".to_string(), vec!["19.99".to_string()]);
|
|
689
|
+
|
|
690
|
+
let result = validator.validate_and_extract(
|
|
691
|
+
&json!({"price": 19.99}),
|
|
692
|
+
&raw_query_params,
|
|
693
|
+
&HashMap::new(),
|
|
694
|
+
&HashMap::new(),
|
|
695
|
+
&HashMap::new(),
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
assert!(result.is_ok(), "Validation should succeed with float coercion");
|
|
699
|
+
let extracted = result.unwrap();
|
|
700
|
+
assert_eq!(extracted["price"], 19.99);
|
|
701
|
+
}
|