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
@@ -43,6 +43,13 @@ pub struct Claims {
43
43
  /// Validates JWT tokens from the Authorization header (Bearer scheme).
44
44
  /// On success, the validated claims are available to downstream handlers.
45
45
  /// On failure, returns 401 Unauthorized with RFC 9457 Problem Details.
46
+ ///
47
+ /// Coverage: Tested via integration tests (`auth_integration.rs`)
48
+ ///
49
+ /// # Errors
50
+ /// Returns an error response when the Authorization header is missing, malformed,
51
+ /// the token is invalid, or configuration is incorrect.
52
+ #[cfg(not(tarpaulin_include))]
46
53
  pub async fn jwt_auth_middleware(
47
54
  config: JwtConfig,
48
55
  headers: HeaderMap,
@@ -107,20 +114,18 @@ pub async fn jwt_auth_middleware(
107
114
  let detail = match e.kind() {
108
115
  jsonwebtoken::errors::ErrorKind::ExpiredSignature => "Token has expired".to_string(),
109
116
  jsonwebtoken::errors::ErrorKind::InvalidToken => "Token is invalid".to_string(),
110
- jsonwebtoken::errors::ErrorKind::InvalidSignature => "Token signature is invalid".to_string(),
111
- jsonwebtoken::errors::ErrorKind::Base64(_) => "Token signature is invalid".to_string(),
112
- jsonwebtoken::errors::ErrorKind::InvalidAudience => "Token audience is invalid".to_string(),
113
- jsonwebtoken::errors::ErrorKind::InvalidIssuer => {
114
- if let Some(ref expected_iss) = config.issuer {
115
- format!("Token issuer is invalid, expected '{}'", expected_iss)
116
- } else {
117
- "Token issuer is invalid".to_string()
118
- }
117
+ jsonwebtoken::errors::ErrorKind::InvalidSignature | jsonwebtoken::errors::ErrorKind::Base64(_) => {
118
+ "Token signature is invalid".to_string()
119
119
  }
120
+ jsonwebtoken::errors::ErrorKind::InvalidAudience => "Token audience is invalid".to_string(),
121
+ jsonwebtoken::errors::ErrorKind::InvalidIssuer => config.issuer.as_ref().map_or_else(
122
+ || "Token issuer is invalid".to_string(),
123
+ |expected_iss| format!("Token issuer is invalid, expected '{expected_iss}'"),
124
+ ),
120
125
  jsonwebtoken::errors::ErrorKind::ImmatureSignature => {
121
126
  "JWT not valid yet, not before claim is in the future".to_string()
122
127
  }
123
- _ => format!("Token validation failed: {}", e),
128
+ _ => format!("Token validation failed: {e}"),
124
129
  };
125
130
 
126
131
  let problem =
@@ -146,7 +151,7 @@ fn parse_algorithm(alg: &str) -> Result<Algorithm, String> {
146
151
  "PS256" => Ok(Algorithm::PS256),
147
152
  "PS384" => Ok(Algorithm::PS384),
148
153
  "PS512" => Ok(Algorithm::PS512),
149
- _ => Err(format!("Unsupported algorithm: {}", alg)),
154
+ _ => Err(format!("Unsupported algorithm: {alg}")),
150
155
  }
151
156
  }
152
157
 
@@ -156,6 +161,12 @@ fn parse_algorithm(alg: &str) -> Result<Algorithm, String> {
156
161
  /// Checks header first, then query parameter as fallback.
157
162
  /// On success, the request proceeds to the next handler.
158
163
  /// On failure, returns 401 Unauthorized with RFC 9457 Problem Details.
164
+ ///
165
+ /// Coverage: Tested via integration tests (`auth_integration.rs`)
166
+ ///
167
+ /// # Errors
168
+ /// Returns an error response when the API key is missing or invalid.
169
+ #[cfg(not(tarpaulin_include))]
159
170
  pub async fn api_key_auth_middleware(
160
171
  config: ApiKeyConfig,
161
172
  headers: HeaderMap,
@@ -168,11 +179,7 @@ pub async fn api_key_auth_middleware(
168
179
 
169
180
  let api_key_from_header = headers.get(&config.header_name).and_then(|v| v.to_str().ok());
170
181
 
171
- let api_key = if let Some(key) = api_key_from_header {
172
- Some(key)
173
- } else {
174
- extract_api_key_from_query(&uri)
175
- };
182
+ let api_key = api_key_from_header.map_or_else(|| extract_api_key_from_query(&uri), Some);
176
183
 
177
184
  let api_key = api_key.ok_or_else(|| {
178
185
  let problem =
@@ -244,4 +251,46 @@ mod tests {
244
251
  assert!(json.contains("user123"));
245
252
  assert!(json.contains("1234567890"));
246
253
  }
254
+
255
+ #[test]
256
+ fn test_extract_api_key_from_query_api_key() {
257
+ let uri: axum::http::Uri = "/api/endpoint?api_key=secret123".parse().unwrap();
258
+ let result = extract_api_key_from_query(&uri);
259
+ assert_eq!(result, Some("secret123"));
260
+ }
261
+
262
+ #[test]
263
+ fn test_extract_api_key_from_query_api_key_camel_case() {
264
+ let uri: axum::http::Uri = "/api/endpoint?apiKey=mykey456".parse().unwrap();
265
+ let result = extract_api_key_from_query(&uri);
266
+ assert_eq!(result, Some("mykey456"));
267
+ }
268
+
269
+ #[test]
270
+ fn test_extract_api_key_from_query_key() {
271
+ let uri: axum::http::Uri = "/api/endpoint?key=testkey789".parse().unwrap();
272
+ let result = extract_api_key_from_query(&uri);
273
+ assert_eq!(result, Some("testkey789"));
274
+ }
275
+
276
+ #[test]
277
+ fn test_extract_api_key_from_query_no_key() {
278
+ let uri: axum::http::Uri = "/api/endpoint?foo=bar&baz=qux".parse().unwrap();
279
+ let result = extract_api_key_from_query(&uri);
280
+ assert_eq!(result, None);
281
+ }
282
+
283
+ #[test]
284
+ fn test_extract_api_key_from_query_empty_string() {
285
+ let uri: axum::http::Uri = "/api/endpoint".parse().unwrap();
286
+ let result = extract_api_key_from_query(&uri);
287
+ assert_eq!(result, None);
288
+ }
289
+
290
+ #[test]
291
+ fn test_extract_api_key_from_query_multiple_params() {
292
+ let uri: axum::http::Uri = "/api/endpoint?foo=bar&api_key=found&baz=qux".parse().unwrap();
293
+ let result = extract_api_key_from_query(&uri);
294
+ assert_eq!(result, Some("found"));
295
+ }
247
296
  }