spikard 0.2.5 → 0.3.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 (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/ext/spikard_rb/Cargo.toml +3 -3
  4. data/lib/spikard/app.rb +61 -49
  5. data/lib/spikard/converters.rb +3 -75
  6. data/lib/spikard/handler_wrapper.rb +6 -9
  7. data/lib/spikard/provide.rb +14 -28
  8. data/lib/spikard/response.rb +75 -11
  9. data/lib/spikard/streaming_response.rb +24 -1
  10. data/lib/spikard/testing.rb +1 -1
  11. data/lib/spikard/version.rb +1 -1
  12. data/sig/spikard.rbs +14 -3
  13. data/vendor/bundle/ruby/3.3.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
  14. metadata +3 -80
  15. data/vendor/crates/spikard-core/Cargo.toml +0 -40
  16. data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
  17. data/vendor/crates/spikard-core/src/bindings/response.rs +0 -133
  18. data/vendor/crates/spikard-core/src/debug.rs +0 -63
  19. data/vendor/crates/spikard-core/src/di/container.rs +0 -726
  20. data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
  21. data/vendor/crates/spikard-core/src/di/error.rs +0 -118
  22. data/vendor/crates/spikard-core/src/di/factory.rs +0 -538
  23. data/vendor/crates/spikard-core/src/di/graph.rs +0 -545
  24. data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
  25. data/vendor/crates/spikard-core/src/di/resolved.rs +0 -411
  26. data/vendor/crates/spikard-core/src/di/value.rs +0 -283
  27. data/vendor/crates/spikard-core/src/http.rs +0 -153
  28. data/vendor/crates/spikard-core/src/lib.rs +0 -28
  29. data/vendor/crates/spikard-core/src/lifecycle.rs +0 -422
  30. data/vendor/crates/spikard-core/src/parameters.rs +0 -719
  31. data/vendor/crates/spikard-core/src/problem.rs +0 -310
  32. data/vendor/crates/spikard-core/src/request_data.rs +0 -189
  33. data/vendor/crates/spikard-core/src/router.rs +0 -249
  34. data/vendor/crates/spikard-core/src/schema_registry.rs +0 -183
  35. data/vendor/crates/spikard-core/src/type_hints.rs +0 -304
  36. data/vendor/crates/spikard-core/src/validation.rs +0 -699
  37. data/vendor/crates/spikard-http/Cargo.toml +0 -58
  38. data/vendor/crates/spikard-http/src/auth.rs +0 -247
  39. data/vendor/crates/spikard-http/src/background.rs +0 -249
  40. data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
  41. data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
  42. data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
  43. data/vendor/crates/spikard-http/src/cors.rs +0 -490
  44. data/vendor/crates/spikard-http/src/debug.rs +0 -63
  45. data/vendor/crates/spikard-http/src/di_handler.rs +0 -423
  46. data/vendor/crates/spikard-http/src/handler_response.rs +0 -190
  47. data/vendor/crates/spikard-http/src/handler_trait.rs +0 -228
  48. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -284
  49. data/vendor/crates/spikard-http/src/lib.rs +0 -529
  50. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -149
  51. data/vendor/crates/spikard-http/src/lifecycle.rs +0 -428
  52. data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -285
  53. data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -86
  54. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -147
  55. data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -287
  56. data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
  57. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -190
  58. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -308
  59. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -195
  60. data/vendor/crates/spikard-http/src/parameters.rs +0 -1
  61. data/vendor/crates/spikard-http/src/problem.rs +0 -1
  62. data/vendor/crates/spikard-http/src/query_parser.rs +0 -369
  63. data/vendor/crates/spikard-http/src/response.rs +0 -399
  64. data/vendor/crates/spikard-http/src/router.rs +0 -1
  65. data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
  66. data/vendor/crates/spikard-http/src/server/handler.rs +0 -80
  67. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -98
  68. data/vendor/crates/spikard-http/src/server/mod.rs +0 -805
  69. data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -119
  70. data/vendor/crates/spikard-http/src/sse.rs +0 -447
  71. data/vendor/crates/spikard-http/src/testing/form.rs +0 -14
  72. data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -60
  73. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -285
  74. data/vendor/crates/spikard-http/src/testing.rs +0 -377
  75. data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
  76. data/vendor/crates/spikard-http/src/validation.rs +0 -1
  77. data/vendor/crates/spikard-http/src/websocket.rs +0 -324
  78. data/vendor/crates/spikard-rb/Cargo.toml +0 -42
  79. data/vendor/crates/spikard-rb/build.rs +0 -8
  80. data/vendor/crates/spikard-rb/src/background.rs +0 -63
  81. data/vendor/crates/spikard-rb/src/config.rs +0 -294
  82. data/vendor/crates/spikard-rb/src/conversion.rs +0 -392
  83. data/vendor/crates/spikard-rb/src/di.rs +0 -409
  84. data/vendor/crates/spikard-rb/src/handler.rs +0 -534
  85. data/vendor/crates/spikard-rb/src/lib.rs +0 -2020
  86. data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -267
  87. data/vendor/crates/spikard-rb/src/server.rs +0 -283
  88. data/vendor/crates/spikard-rb/src/sse.rs +0 -231
  89. data/vendor/crates/spikard-rb/src/test_client.rs +0 -404
  90. data/vendor/crates/spikard-rb/src/test_sse.rs +0 -143
  91. data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
  92. data/vendor/crates/spikard-rb/src/websocket.rs +0 -233
@@ -1,490 +0,0 @@
1
- //! CORS (Cross-Origin Resource Sharing) handling
2
- //!
3
- //! Handles CORS preflight requests and adds CORS headers to responses
4
-
5
- use crate::CorsConfig;
6
- use axum::body::Body;
7
- use axum::http::{HeaderMap, HeaderValue, Response, StatusCode};
8
- use axum::response::IntoResponse;
9
-
10
- /// Check if an origin is allowed by the CORS configuration
11
- ///
12
- /// Supports exact matches and wildcard ("*") for any origin.
13
- /// Empty origins always return false for security.
14
- ///
15
- /// # Arguments
16
- /// * `origin` - The origin string from the HTTP request (e.g., "https://example.com")
17
- /// * `allowed_origins` - List of allowed origins configured for CORS
18
- ///
19
- /// # Returns
20
- /// `true` if the origin is allowed, `false` otherwise
21
- fn is_origin_allowed(origin: &str, allowed_origins: &[String]) -> bool {
22
- if origin.is_empty() {
23
- return false;
24
- }
25
-
26
- allowed_origins
27
- .iter()
28
- .any(|allowed| allowed == "*" || allowed == origin)
29
- }
30
-
31
- /// Check if a method is allowed by the CORS configuration
32
- ///
33
- /// Supports exact matches and wildcard ("*") for any method.
34
- /// Comparison is case-insensitive (e.g., "get" matches "GET").
35
- ///
36
- /// # Arguments
37
- /// * `method` - The HTTP method requested (e.g., "GET", "POST")
38
- /// * `allowed_methods` - List of allowed HTTP methods configured for CORS
39
- ///
40
- /// # Returns
41
- /// `true` if the method is allowed, `false` otherwise
42
- fn is_method_allowed(method: &str, allowed_methods: &[String]) -> bool {
43
- allowed_methods
44
- .iter()
45
- .any(|allowed| allowed == "*" || allowed.eq_ignore_ascii_case(method))
46
- }
47
-
48
- /// Check if all requested headers are allowed by CORS configuration
49
- ///
50
- /// Headers are case-insensitive. Supports wildcard ("*") for allowing any header.
51
- /// If a wildcard is configured, all requested headers are allowed.
52
- ///
53
- /// # Arguments
54
- /// * `requested` - Array of header names requested by the client
55
- /// * `allowed` - List of allowed header names configured for CORS
56
- ///
57
- /// # Returns
58
- /// `true` if all requested headers are allowed, `false` if any header is not allowed
59
- fn are_headers_allowed(requested: &[&str], allowed: &[String]) -> bool {
60
- if allowed.iter().any(|h| h == "*") {
61
- return true;
62
- }
63
-
64
- requested.iter().all(|req_header| {
65
- allowed
66
- .iter()
67
- .any(|allowed_header| allowed_header.eq_ignore_ascii_case(req_header))
68
- })
69
- }
70
-
71
- /// Handle CORS preflight (OPTIONS) request
72
- ///
73
- /// Validates the request against the CORS configuration and returns appropriate
74
- /// response or error. This function processes OPTIONS requests as defined in the
75
- /// CORS specification (RFC 7231).
76
- ///
77
- /// # Validation
78
- ///
79
- /// Checks the following conditions:
80
- /// 1. **Origin Header:** Must be present and match configured allowed origins
81
- /// 2. **Access-Control-Request-Method:** Must match configured allowed methods
82
- /// 3. **Access-Control-Request-Headers:** All requested headers must match configured allowed headers
83
- ///
84
- /// # Success Response
85
- ///
86
- /// Returns HTTP 204 (No Content) with the following response headers:
87
- /// - `Access-Control-Allow-Origin` - The origin that is allowed
88
- /// - `Access-Control-Allow-Methods` - Comma-separated list of allowed methods
89
- /// - `Access-Control-Allow-Headers` - Comma-separated list of allowed headers
90
- /// - `Access-Control-Max-Age` - Caching duration in seconds (if configured)
91
- /// - `Access-Control-Allow-Credentials` - "true" if credentials are allowed
92
- ///
93
- /// # Error Response
94
- ///
95
- /// Returns HTTP 403 (Forbidden) if validation fails for:
96
- /// - Origin not in allowed list
97
- /// - Requested method not allowed
98
- /// - Requested headers not allowed
99
- ///
100
- /// # Arguments
101
- /// * `headers` - Request headers containing CORS preflight information
102
- /// * `cors_config` - CORS configuration to validate against
103
- ///
104
- /// # Returns
105
- /// * `Ok(Response)` - 204 No Content with CORS headers
106
- /// * `Err(Response)` - 403 Forbidden or 500 Internal Server Error
107
- pub fn handle_preflight(headers: &HeaderMap, cors_config: &CorsConfig) -> Result<Response<Body>, Box<Response<Body>>> {
108
- let origin = headers.get("origin").and_then(|v| v.to_str().ok()).unwrap_or("");
109
-
110
- if origin.is_empty() || !is_origin_allowed(origin, &cors_config.allowed_origins) {
111
- return Err(Box::new(
112
- (
113
- StatusCode::FORBIDDEN,
114
- axum::Json(serde_json::json!({
115
- "detail": format!("CORS request from origin '{}' not allowed", origin)
116
- })),
117
- )
118
- .into_response(),
119
- ));
120
- }
121
-
122
- let requested_method = headers
123
- .get("access-control-request-method")
124
- .and_then(|v| v.to_str().ok())
125
- .unwrap_or("");
126
-
127
- if !requested_method.is_empty() && !is_method_allowed(requested_method, &cors_config.allowed_methods) {
128
- return Err(Box::new((StatusCode::FORBIDDEN).into_response()));
129
- }
130
-
131
- let requested_headers_str = headers
132
- .get("access-control-request-headers")
133
- .and_then(|v| v.to_str().ok());
134
-
135
- if let Some(req_headers) = requested_headers_str {
136
- let requested_headers: Vec<&str> = req_headers.split(',').map(|h| h.trim()).collect();
137
-
138
- if !are_headers_allowed(&requested_headers, &cors_config.allowed_headers) {
139
- return Err(Box::new((StatusCode::FORBIDDEN).into_response()));
140
- }
141
- }
142
-
143
- let mut response = Response::builder().status(StatusCode::NO_CONTENT);
144
-
145
- let headers_mut = match response.headers_mut() {
146
- Some(headers) => headers,
147
- None => {
148
- return Err(Box::new(
149
- (
150
- StatusCode::INTERNAL_SERVER_ERROR,
151
- axum::Json(serde_json::json!({
152
- "detail": "Failed to construct response headers"
153
- })),
154
- )
155
- .into_response(),
156
- ));
157
- }
158
- };
159
-
160
- headers_mut.insert(
161
- "access-control-allow-origin",
162
- HeaderValue::from_str(origin).unwrap_or_else(|_| HeaderValue::from_static("*")),
163
- );
164
-
165
- let methods = cors_config.allowed_methods.join(", ");
166
- headers_mut.insert(
167
- "access-control-allow-methods",
168
- HeaderValue::from_str(&methods).unwrap_or_else(|_| HeaderValue::from_static("*")),
169
- );
170
-
171
- let allowed_headers = cors_config.allowed_headers.join(", ");
172
- headers_mut.insert(
173
- "access-control-allow-headers",
174
- HeaderValue::from_str(&allowed_headers).unwrap_or_else(|_| HeaderValue::from_static("*")),
175
- );
176
-
177
- if let Some(max_age) = cors_config.max_age
178
- && let Ok(header_val) = HeaderValue::from_str(&max_age.to_string())
179
- {
180
- headers_mut.insert("access-control-max-age", header_val);
181
- }
182
-
183
- if let Some(true) = cors_config.allow_credentials {
184
- headers_mut.insert("access-control-allow-credentials", HeaderValue::from_static("true"));
185
- }
186
-
187
- match response.body(Body::empty()) {
188
- Ok(resp) => Ok(resp),
189
- Err(_) => Err(Box::new(
190
- (
191
- StatusCode::INTERNAL_SERVER_ERROR,
192
- axum::Json(serde_json::json!({
193
- "detail": "Failed to construct response body"
194
- })),
195
- )
196
- .into_response(),
197
- )),
198
- }
199
- }
200
-
201
- /// Add CORS headers to a successful response
202
- ///
203
- /// Adds appropriate CORS headers to the response based on the configuration.
204
- /// This function should be called for successful (non-error) responses to
205
- /// cross-origin requests.
206
- ///
207
- /// # Headers Added
208
- ///
209
- /// - `Access-Control-Allow-Origin` - The origin that is allowed (if valid)
210
- /// - `Access-Control-Expose-Headers` - Headers that are safe to expose to the client
211
- /// - `Access-Control-Allow-Credentials` - "true" if credentials are allowed
212
- ///
213
- /// # Arguments
214
- /// * `response` - Mutable reference to the response to modify
215
- /// * `origin` - The origin from the request (e.g., `<https://example.com>`)
216
- /// * `cors_config` - CORS configuration to apply
217
- ///
218
- /// # Example
219
- ///
220
- /// ```ignore
221
- /// let mut response = Response::new(Body::empty());
222
- /// add_cors_headers(&mut response, "https://example.com", &cors_config);
223
- /// ```
224
- pub fn add_cors_headers(response: &mut Response<Body>, origin: &str, cors_config: &CorsConfig) {
225
- let headers = response.headers_mut();
226
-
227
- if let Ok(origin_value) = HeaderValue::from_str(origin) {
228
- headers.insert("access-control-allow-origin", origin_value);
229
- }
230
-
231
- if let Some(ref expose_headers) = cors_config.expose_headers {
232
- let expose = expose_headers.join(", ");
233
- if let Ok(expose_value) = HeaderValue::from_str(&expose) {
234
- headers.insert("access-control-expose-headers", expose_value);
235
- }
236
- }
237
-
238
- if let Some(true) = cors_config.allow_credentials {
239
- headers.insert("access-control-allow-credentials", HeaderValue::from_static("true"));
240
- }
241
- }
242
-
243
- /// Validate a non-preflight CORS request
244
- ///
245
- /// Checks if the Origin header is present and allowed for non-preflight (actual) requests.
246
- /// Returns an error response if validation fails.
247
- ///
248
- /// # Validation
249
- ///
250
- /// - If no Origin header is present, the request is allowed (not a CORS request)
251
- /// - If Origin header is present, it must match the allowed origins
252
- ///
253
- /// # Arguments
254
- /// * `headers` - Request headers containing origin information
255
- /// * `cors_config` - CORS configuration to validate against
256
- ///
257
- /// # Returns
258
- /// * `Ok(())` - Request is allowed
259
- /// * `Err(Response)` - 403 Forbidden with error details
260
- ///
261
- /// # Note
262
- ///
263
- /// This function is for actual requests, not OPTIONS preflight requests.
264
- /// Use `handle_preflight` for OPTIONS requests.
265
- pub fn validate_cors_request(headers: &HeaderMap, cors_config: &CorsConfig) -> Result<(), Box<Response<Body>>> {
266
- let origin = headers.get("origin").and_then(|v| v.to_str().ok()).unwrap_or("");
267
-
268
- if !origin.is_empty() && !is_origin_allowed(origin, &cors_config.allowed_origins) {
269
- return Err(Box::new(
270
- (
271
- StatusCode::FORBIDDEN,
272
- axum::Json(serde_json::json!({
273
- "detail": format!("CORS request from origin '{}' not allowed", origin)
274
- })),
275
- )
276
- .into_response(),
277
- ));
278
- }
279
- Ok(())
280
- }
281
-
282
- #[cfg(test)]
283
- mod tests {
284
- use super::*;
285
-
286
- fn make_cors_config() -> CorsConfig {
287
- CorsConfig {
288
- allowed_origins: vec!["https://example.com".to_string()],
289
- allowed_methods: vec!["GET".to_string(), "POST".to_string()],
290
- allowed_headers: vec!["content-type".to_string(), "authorization".to_string()],
291
- expose_headers: Some(vec!["x-custom-header".to_string()]),
292
- max_age: Some(3600),
293
- allow_credentials: Some(true),
294
- }
295
- }
296
-
297
- #[test]
298
- fn test_is_origin_allowed_exact_match() {
299
- let allowed = vec!["https://example.com".to_string()];
300
- assert!(is_origin_allowed("https://example.com", &allowed));
301
- assert!(!is_origin_allowed("https://evil.com", &allowed));
302
- }
303
-
304
- #[test]
305
- fn test_is_origin_allowed_wildcard() {
306
- let allowed = vec!["*".to_string()];
307
- assert!(is_origin_allowed("https://example.com", &allowed));
308
- assert!(is_origin_allowed("https://any-domain.com", &allowed));
309
- }
310
-
311
- #[test]
312
- fn test_is_origin_allowed_empty_origin() {
313
- let allowed = vec!["*".to_string()];
314
- assert!(!is_origin_allowed("", &allowed));
315
- }
316
-
317
- #[test]
318
- fn test_is_method_allowed_case_insensitive() {
319
- let allowed = vec!["GET".to_string(), "POST".to_string()];
320
- assert!(is_method_allowed("GET", &allowed));
321
- assert!(is_method_allowed("get", &allowed));
322
- assert!(is_method_allowed("POST", &allowed));
323
- assert!(is_method_allowed("post", &allowed));
324
- assert!(!is_method_allowed("DELETE", &allowed));
325
- }
326
-
327
- #[test]
328
- fn test_is_method_allowed_wildcard() {
329
- let allowed = vec!["*".to_string()];
330
- assert!(is_method_allowed("GET", &allowed));
331
- assert!(is_method_allowed("DELETE", &allowed));
332
- assert!(is_method_allowed("PATCH", &allowed));
333
- }
334
-
335
- #[test]
336
- fn test_are_headers_allowed_case_insensitive() {
337
- let allowed = vec!["Content-Type".to_string(), "Authorization".to_string()];
338
- assert!(are_headers_allowed(&["content-type"], &allowed));
339
- assert!(are_headers_allowed(&["AUTHORIZATION"], &allowed));
340
- assert!(are_headers_allowed(&["content-type", "authorization"], &allowed));
341
- assert!(!are_headers_allowed(&["x-custom"], &allowed));
342
- }
343
-
344
- #[test]
345
- fn test_are_headers_allowed_wildcard() {
346
- let allowed = vec!["*".to_string()];
347
- assert!(are_headers_allowed(&["any-header"], &allowed));
348
- assert!(are_headers_allowed(&["multiple", "headers"], &allowed));
349
- }
350
-
351
- #[test]
352
- fn test_handle_preflight_success() {
353
- let config = make_cors_config();
354
- let mut headers = HeaderMap::new();
355
- headers.insert("origin", HeaderValue::from_static("https://example.com"));
356
- headers.insert("access-control-request-method", HeaderValue::from_static("POST"));
357
- headers.insert(
358
- "access-control-request-headers",
359
- HeaderValue::from_static("content-type"),
360
- );
361
-
362
- let result = handle_preflight(&headers, &config);
363
- assert!(result.is_ok());
364
-
365
- let response = result.unwrap();
366
- assert_eq!(response.status(), StatusCode::NO_CONTENT);
367
-
368
- let resp_headers = response.headers();
369
- assert_eq!(
370
- resp_headers.get("access-control-allow-origin").unwrap(),
371
- "https://example.com"
372
- );
373
- assert!(
374
- resp_headers
375
- .get("access-control-allow-methods")
376
- .unwrap()
377
- .to_str()
378
- .unwrap()
379
- .contains("POST")
380
- );
381
- assert_eq!(resp_headers.get("access-control-max-age").unwrap(), "3600");
382
- assert_eq!(resp_headers.get("access-control-allow-credentials").unwrap(), "true");
383
- }
384
-
385
- #[test]
386
- fn test_handle_preflight_origin_not_allowed() {
387
- let config = make_cors_config();
388
- let mut headers = HeaderMap::new();
389
- headers.insert("origin", HeaderValue::from_static("https://evil.com"));
390
- headers.insert("access-control-request-method", HeaderValue::from_static("GET"));
391
-
392
- let result = handle_preflight(&headers, &config);
393
- assert!(result.is_err());
394
-
395
- let response = *result.unwrap_err();
396
- assert_eq!(response.status(), StatusCode::FORBIDDEN);
397
- }
398
-
399
- #[test]
400
- fn test_handle_preflight_method_not_allowed() {
401
- let config = make_cors_config();
402
- let mut headers = HeaderMap::new();
403
- headers.insert("origin", HeaderValue::from_static("https://example.com"));
404
- headers.insert("access-control-request-method", HeaderValue::from_static("DELETE"));
405
-
406
- let result = handle_preflight(&headers, &config);
407
- assert!(result.is_err());
408
-
409
- let response = *result.unwrap_err();
410
- assert_eq!(response.status(), StatusCode::FORBIDDEN);
411
- }
412
-
413
- #[test]
414
- fn test_handle_preflight_header_not_allowed() {
415
- let config = make_cors_config();
416
- let mut headers = HeaderMap::new();
417
- headers.insert("origin", HeaderValue::from_static("https://example.com"));
418
- headers.insert("access-control-request-method", HeaderValue::from_static("POST"));
419
- headers.insert(
420
- "access-control-request-headers",
421
- HeaderValue::from_static("x-forbidden-header"),
422
- );
423
-
424
- let result = handle_preflight(&headers, &config);
425
- assert!(result.is_err());
426
-
427
- let response = *result.unwrap_err();
428
- assert_eq!(response.status(), StatusCode::FORBIDDEN);
429
- }
430
-
431
- #[test]
432
- fn test_handle_preflight_empty_origin() {
433
- let config = make_cors_config();
434
- let headers = HeaderMap::new();
435
-
436
- let result = handle_preflight(&headers, &config);
437
- assert!(result.is_err());
438
-
439
- let response = *result.unwrap_err();
440
- assert_eq!(response.status(), StatusCode::FORBIDDEN);
441
- }
442
-
443
- #[test]
444
- fn test_add_cors_headers() {
445
- let config = make_cors_config();
446
- let mut response = Response::new(Body::empty());
447
-
448
- add_cors_headers(&mut response, "https://example.com", &config);
449
-
450
- let headers = response.headers();
451
- assert_eq!(
452
- headers.get("access-control-allow-origin").unwrap(),
453
- "https://example.com"
454
- );
455
- assert_eq!(headers.get("access-control-expose-headers").unwrap(), "x-custom-header");
456
- assert_eq!(headers.get("access-control-allow-credentials").unwrap(), "true");
457
- }
458
-
459
- #[test]
460
- fn test_validate_cors_request_allowed() {
461
- let config = make_cors_config();
462
- let mut headers = HeaderMap::new();
463
- headers.insert("origin", HeaderValue::from_static("https://example.com"));
464
-
465
- let result = validate_cors_request(&headers, &config);
466
- assert!(result.is_ok());
467
- }
468
-
469
- #[test]
470
- fn test_validate_cors_request_not_allowed() {
471
- let config = make_cors_config();
472
- let mut headers = HeaderMap::new();
473
- headers.insert("origin", HeaderValue::from_static("https://evil.com"));
474
-
475
- let result = validate_cors_request(&headers, &config);
476
- assert!(result.is_err());
477
-
478
- let response = *result.unwrap_err();
479
- assert_eq!(response.status(), StatusCode::FORBIDDEN);
480
- }
481
-
482
- #[test]
483
- fn test_validate_cors_request_no_origin() {
484
- let config = make_cors_config();
485
- let headers = HeaderMap::new();
486
-
487
- let result = validate_cors_request(&headers, &config);
488
- assert!(result.is_ok());
489
- }
490
- }
@@ -1,63 +0,0 @@
1
- //! Debug logging utilities for spikard-http
2
- //!
3
- //! This module provides debug logging that can be enabled via:
4
- //! - Building in debug mode (cfg(debug_assertions))
5
- //! - Setting SPIKARD_DEBUG=1 environment variable
6
-
7
- use std::sync::atomic::{AtomicBool, Ordering};
8
-
9
- static DEBUG_ENABLED: AtomicBool = AtomicBool::new(false);
10
-
11
- /// Initialize debug logging based on environment and build mode
12
- pub fn init() {
13
- let enabled = cfg!(debug_assertions) || std::env::var("SPIKARD_DEBUG").is_ok() || std::env::var("DEBUG").is_ok();
14
-
15
- eprintln!(
16
- "[spikard-http::debug] init() called, cfg!(debug_assertions)={}, DEBUG={}, enabled={}",
17
- cfg!(debug_assertions),
18
- std::env::var("DEBUG").is_ok(),
19
- enabled
20
- );
21
-
22
- DEBUG_ENABLED.store(enabled, Ordering::Relaxed);
23
-
24
- if enabled {
25
- eprintln!("[spikard-http] Debug logging enabled");
26
- }
27
- }
28
-
29
- /// Check if debug logging is enabled
30
- #[inline]
31
- pub fn is_enabled() -> bool {
32
- DEBUG_ENABLED.load(Ordering::Relaxed)
33
- }
34
-
35
- /// Log a debug message if debugging is enabled
36
- #[macro_export]
37
- macro_rules! debug_log {
38
- ($($arg:tt)*) => {
39
- if $crate::debug::is_enabled() {
40
- eprintln!("[spikard-http] {}", format!($($arg)*));
41
- }
42
- };
43
- }
44
-
45
- /// Log a debug message with a specific module/component name
46
- #[macro_export]
47
- macro_rules! debug_log_module {
48
- ($module:expr, $($arg:tt)*) => {
49
- if $crate::debug::is_enabled() {
50
- eprintln!("[spikard-http::{}] {}", $module, format!($($arg)*));
51
- }
52
- };
53
- }
54
-
55
- /// Log a debug value with pretty-printing
56
- #[macro_export]
57
- macro_rules! debug_log_value {
58
- ($name:expr, $value:expr) => {
59
- if $crate::debug::is_enabled() {
60
- eprintln!("[spikard-http] {} = {:?}", $name, $value);
61
- }
62
- };
63
- }