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.
- checksums.yaml +4 -4
- data/README.md +21 -6
- data/ext/spikard_rb/Cargo.toml +2 -2
- data/lib/spikard/app.rb +33 -14
- data/lib/spikard/testing.rb +47 -12
- data/lib/spikard/version.rb +1 -1
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -0
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -0
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -0
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -0
- data/vendor/crates/spikard-core/Cargo.toml +4 -4
- data/vendor/crates/spikard-core/src/debug.rs +64 -0
- data/vendor/crates/spikard-core/src/di/container.rs +3 -27
- data/vendor/crates/spikard-core/src/di/factory.rs +1 -5
- data/vendor/crates/spikard-core/src/di/graph.rs +8 -47
- data/vendor/crates/spikard-core/src/di/mod.rs +1 -1
- data/vendor/crates/spikard-core/src/di/resolved.rs +1 -7
- data/vendor/crates/spikard-core/src/di/value.rs +2 -4
- data/vendor/crates/spikard-core/src/errors.rs +30 -0
- data/vendor/crates/spikard-core/src/http.rs +262 -0
- data/vendor/crates/spikard-core/src/lib.rs +1 -1
- data/vendor/crates/spikard-core/src/lifecycle.rs +764 -0
- data/vendor/crates/spikard-core/src/metadata.rs +389 -0
- data/vendor/crates/spikard-core/src/parameters.rs +1962 -159
- data/vendor/crates/spikard-core/src/problem.rs +34 -0
- data/vendor/crates/spikard-core/src/request_data.rs +966 -1
- data/vendor/crates/spikard-core/src/router.rs +263 -2
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +688 -0
- data/vendor/crates/spikard-core/src/{validation.rs → validation/mod.rs} +26 -268
- data/vendor/crates/spikard-http/Cargo.toml +12 -16
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -0
- data/vendor/crates/spikard-http/src/auth.rs +65 -16
- data/vendor/crates/spikard-http/src/background.rs +1614 -3
- data/vendor/crates/spikard-http/src/cors.rs +515 -0
- data/vendor/crates/spikard-http/src/debug.rs +65 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1322 -77
- data/vendor/crates/spikard-http/src/handler_response.rs +711 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +607 -5
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +6 -0
- data/vendor/crates/spikard-http/src/lib.rs +33 -28
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +81 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +765 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +372 -117
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +836 -10
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +409 -43
- data/vendor/crates/spikard-http/src/middleware/validation.rs +513 -65
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +345 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1055 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +473 -3
- data/vendor/crates/spikard-http/src/query_parser.rs +455 -31
- data/vendor/crates/spikard-http/src/response.rs +321 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1572 -9
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +136 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +875 -178
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +674 -23
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +599 -0
- data/vendor/crates/spikard-http/src/sse.rs +983 -21
- data/vendor/crates/spikard-http/src/testing/form.rs +38 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -2
- data/vendor/crates/spikard-http/src/testing.rs +7 -7
- data/vendor/crates/spikard-http/src/websocket.rs +1055 -4
- data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +26 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +192 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -0
- data/vendor/crates/spikard-rb/Cargo.toml +10 -4
- data/vendor/crates/spikard-rb/build.rs +196 -5
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/{config.rs → config/server_config.rs} +100 -109
- data/vendor/crates/spikard-rb/src/conversion.rs +121 -20
- data/vendor/crates/spikard-rb/src/di/builder.rs +100 -0
- data/vendor/crates/spikard-rb/src/{di.rs → di/mod.rs} +12 -46
- data/vendor/crates/spikard-rb/src/handler.rs +100 -107
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +467 -1428
- data/vendor/crates/spikard-rb/src/lifecycle.rs +1 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +447 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -0
- data/vendor/crates/spikard-rb/src/server.rs +47 -22
- data/vendor/crates/spikard-rb/src/{test_client.rs → testing/client.rs} +187 -40
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +635 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +178 -37
- metadata +46 -13
- data/vendor/crates/spikard-http/src/parameters.rs +0 -1
- data/vendor/crates/spikard-http/src/problem.rs +0 -1
- data/vendor/crates/spikard-http/src/router.rs +0 -1
- data/vendor/crates/spikard-http/src/schema_registry.rs +0 -1
- data/vendor/crates/spikard-http/src/type_hints.rs +0 -1
- data/vendor/crates/spikard-http/src/validation.rs +0 -1
- data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
- /data/vendor/crates/spikard-rb/src/{test_sse.rs → testing/sse.rs} +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
//! Lifecycle hook base implementations
|
|
2
|
+
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
/// Lifecycle hook types supported across all bindings
|
|
6
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
7
|
+
pub enum LifecycleHookType {
|
|
8
|
+
/// Called at the start of request processing
|
|
9
|
+
OnRequest,
|
|
10
|
+
/// Called before validation
|
|
11
|
+
PreValidation,
|
|
12
|
+
/// Called before handler execution
|
|
13
|
+
PreHandler,
|
|
14
|
+
/// Called after handler execution
|
|
15
|
+
OnResponse,
|
|
16
|
+
/// Called when an error occurs
|
|
17
|
+
OnError,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Result type for lifecycle hooks
|
|
21
|
+
#[derive(Clone)]
|
|
22
|
+
pub enum HookResult {
|
|
23
|
+
/// Continue with normal processing
|
|
24
|
+
Continue,
|
|
25
|
+
/// Short-circuit and return this response
|
|
26
|
+
ShortCircuit(serde_json::Value),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Trait for implementing lifecycle hooks in language bindings
|
|
30
|
+
pub trait LifecycleHook: Send + Sync {
|
|
31
|
+
/// Execute the lifecycle hook
|
|
32
|
+
fn execute(&self, context: serde_json::Value) -> Result<HookResult, String>;
|
|
33
|
+
|
|
34
|
+
/// Get the hook type
|
|
35
|
+
fn hook_type(&self) -> LifecycleHookType;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Base configuration for lifecycle hooks
|
|
39
|
+
pub struct LifecycleConfig {
|
|
40
|
+
/// Registered hooks by type
|
|
41
|
+
hooks: std::collections::HashMap<LifecycleHookType, Vec<Arc<dyn LifecycleHook>>>,
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
impl LifecycleConfig {
|
|
45
|
+
/// Create a new lifecycle configuration
|
|
46
|
+
pub fn new() -> Self {
|
|
47
|
+
Self {
|
|
48
|
+
hooks: std::collections::HashMap::new(),
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Register a lifecycle hook
|
|
53
|
+
pub fn register(&mut self, hook: Arc<dyn LifecycleHook>) {
|
|
54
|
+
self.hooks.entry(hook.hook_type()).or_default().push(hook);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// Get hooks for a specific type
|
|
58
|
+
pub fn get_hooks(&self, hook_type: LifecycleHookType) -> Vec<Arc<dyn LifecycleHook>> {
|
|
59
|
+
self.hooks.get(&hook_type).cloned().unwrap_or_default()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
impl Default for LifecycleConfig {
|
|
64
|
+
fn default() -> Self {
|
|
65
|
+
Self::new()
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[cfg(test)]
|
|
70
|
+
mod tests {
|
|
71
|
+
use super::*;
|
|
72
|
+
use serde_json::json;
|
|
73
|
+
|
|
74
|
+
struct TestHook {
|
|
75
|
+
hook_type: LifecycleHookType,
|
|
76
|
+
result: HookResult,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl LifecycleHook for TestHook {
|
|
80
|
+
fn execute(&self, _context: serde_json::Value) -> Result<HookResult, String> {
|
|
81
|
+
Ok(self.result.clone())
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn hook_type(&self) -> LifecycleHookType {
|
|
85
|
+
self.hook_type
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[test]
|
|
90
|
+
fn test_lifecycle_hook_type_equality() {
|
|
91
|
+
assert_eq!(LifecycleHookType::OnRequest, LifecycleHookType::OnRequest);
|
|
92
|
+
assert_ne!(LifecycleHookType::OnRequest, LifecycleHookType::OnResponse);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[test]
|
|
96
|
+
fn test_lifecycle_hook_type_hash() {
|
|
97
|
+
use std::collections::HashSet;
|
|
98
|
+
|
|
99
|
+
let mut set = HashSet::new();
|
|
100
|
+
set.insert(LifecycleHookType::OnRequest);
|
|
101
|
+
set.insert(LifecycleHookType::PreHandler);
|
|
102
|
+
set.insert(LifecycleHookType::OnRequest);
|
|
103
|
+
|
|
104
|
+
assert_eq!(set.len(), 2);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
#[test]
|
|
108
|
+
fn test_hook_result_continue() {
|
|
109
|
+
let result = HookResult::Continue;
|
|
110
|
+
match result {
|
|
111
|
+
HookResult::Continue => {}
|
|
112
|
+
HookResult::ShortCircuit(_) => panic!("Expected Continue"),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
#[test]
|
|
117
|
+
fn test_hook_result_short_circuit() {
|
|
118
|
+
let response = json!({ "status": "error" });
|
|
119
|
+
let result = HookResult::ShortCircuit(response.clone());
|
|
120
|
+
match result {
|
|
121
|
+
HookResult::Continue => panic!("Expected ShortCircuit"),
|
|
122
|
+
HookResult::ShortCircuit(r) => {
|
|
123
|
+
assert_eq!(r, response);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
fn test_lifecycle_config_new() {
|
|
130
|
+
let config = LifecycleConfig::new();
|
|
131
|
+
assert_eq!(config.get_hooks(LifecycleHookType::OnRequest).len(), 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[test]
|
|
135
|
+
fn test_lifecycle_config_default() {
|
|
136
|
+
let config = LifecycleConfig::default();
|
|
137
|
+
assert_eq!(config.get_hooks(LifecycleHookType::OnRequest).len(), 0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[test]
|
|
141
|
+
fn test_register_single_hook() {
|
|
142
|
+
let mut config = LifecycleConfig::new();
|
|
143
|
+
let hook = Arc::new(TestHook {
|
|
144
|
+
hook_type: LifecycleHookType::OnRequest,
|
|
145
|
+
result: HookResult::Continue,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
config.register(hook);
|
|
149
|
+
|
|
150
|
+
let hooks = config.get_hooks(LifecycleHookType::OnRequest);
|
|
151
|
+
assert_eq!(hooks.len(), 1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
#[test]
|
|
155
|
+
fn test_register_multiple_hooks_same_type() {
|
|
156
|
+
let mut config = LifecycleConfig::new();
|
|
157
|
+
|
|
158
|
+
for i in 0..3 {
|
|
159
|
+
let hook = Arc::new(TestHook {
|
|
160
|
+
hook_type: LifecycleHookType::OnRequest,
|
|
161
|
+
result: if i == 0 {
|
|
162
|
+
HookResult::Continue
|
|
163
|
+
} else {
|
|
164
|
+
HookResult::ShortCircuit(json!({ "index": i }))
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
config.register(hook);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
let hooks = config.get_hooks(LifecycleHookType::OnRequest);
|
|
171
|
+
assert_eq!(hooks.len(), 3);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[test]
|
|
175
|
+
fn test_register_hooks_different_types() {
|
|
176
|
+
let mut config = LifecycleConfig::new();
|
|
177
|
+
|
|
178
|
+
let hook_on_request = Arc::new(TestHook {
|
|
179
|
+
hook_type: LifecycleHookType::OnRequest,
|
|
180
|
+
result: HookResult::Continue,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
let hook_on_error = Arc::new(TestHook {
|
|
184
|
+
hook_type: LifecycleHookType::OnError,
|
|
185
|
+
result: HookResult::Continue,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
config.register(hook_on_request);
|
|
189
|
+
config.register(hook_on_error);
|
|
190
|
+
|
|
191
|
+
assert_eq!(config.get_hooks(LifecycleHookType::OnRequest).len(), 1);
|
|
192
|
+
assert_eq!(config.get_hooks(LifecycleHookType::OnError).len(), 1);
|
|
193
|
+
assert_eq!(config.get_hooks(LifecycleHookType::PreHandler).len(), 0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[test]
|
|
197
|
+
fn test_get_hooks_empty() {
|
|
198
|
+
let config = LifecycleConfig::new();
|
|
199
|
+
let hooks = config.get_hooks(LifecycleHookType::PreValidation);
|
|
200
|
+
assert!(hooks.is_empty());
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
#[test]
|
|
204
|
+
fn test_get_hooks_multiple_calls() {
|
|
205
|
+
let mut config = LifecycleConfig::new();
|
|
206
|
+
|
|
207
|
+
let hook1 = Arc::new(TestHook {
|
|
208
|
+
hook_type: LifecycleHookType::OnResponse,
|
|
209
|
+
result: HookResult::Continue,
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
let hook2 = Arc::new(TestHook {
|
|
213
|
+
hook_type: LifecycleHookType::OnResponse,
|
|
214
|
+
result: HookResult::Continue,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
config.register(hook1);
|
|
218
|
+
config.register(hook2);
|
|
219
|
+
|
|
220
|
+
let hooks1 = config.get_hooks(LifecycleHookType::OnResponse);
|
|
221
|
+
let hooks2 = config.get_hooks(LifecycleHookType::OnResponse);
|
|
222
|
+
|
|
223
|
+
assert_eq!(hooks1.len(), 2);
|
|
224
|
+
assert_eq!(hooks2.len(), 2);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn test_hook_execute() {
|
|
229
|
+
let hook = TestHook {
|
|
230
|
+
hook_type: LifecycleHookType::OnRequest,
|
|
231
|
+
result: HookResult::Continue,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
let context = json!({ "test": "data" });
|
|
235
|
+
let result = hook.execute(context);
|
|
236
|
+
|
|
237
|
+
assert!(result.is_ok());
|
|
238
|
+
match result.unwrap() {
|
|
239
|
+
HookResult::Continue => {}
|
|
240
|
+
HookResult::ShortCircuit(_) => panic!("Expected Continue"),
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
#[test]
|
|
245
|
+
fn test_hook_type_retrieval() {
|
|
246
|
+
let hook = TestHook {
|
|
247
|
+
hook_type: LifecycleHookType::PreValidation,
|
|
248
|
+
result: HookResult::Continue,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
assert_eq!(hook.hook_type(), LifecycleHookType::PreValidation);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
#[test]
|
|
255
|
+
fn test_all_hook_types() {
|
|
256
|
+
let hook_types = vec![
|
|
257
|
+
LifecycleHookType::OnRequest,
|
|
258
|
+
LifecycleHookType::PreValidation,
|
|
259
|
+
LifecycleHookType::PreHandler,
|
|
260
|
+
LifecycleHookType::OnResponse,
|
|
261
|
+
LifecycleHookType::OnError,
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
let mut config = LifecycleConfig::new();
|
|
265
|
+
|
|
266
|
+
for hook_type in hook_types.iter() {
|
|
267
|
+
let hook = Arc::new(TestHook {
|
|
268
|
+
hook_type: *hook_type,
|
|
269
|
+
result: HookResult::Continue,
|
|
270
|
+
});
|
|
271
|
+
config.register(hook);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
for hook_type in hook_types {
|
|
275
|
+
let hooks = config.get_hooks(hook_type);
|
|
276
|
+
assert_eq!(hooks.len(), 1);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
#[test]
|
|
281
|
+
fn test_hook_result_clone() {
|
|
282
|
+
let original = HookResult::ShortCircuit(json!({ "key": "value" }));
|
|
283
|
+
let cloned = original.clone();
|
|
284
|
+
|
|
285
|
+
match cloned {
|
|
286
|
+
HookResult::ShortCircuit(response) => {
|
|
287
|
+
assert_eq!(response["key"], "value");
|
|
288
|
+
}
|
|
289
|
+
_ => panic!("Expected ShortCircuit"),
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|