spikard 0.5.0 → 0.6.1
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/LICENSE +1 -1
- data/README.md +674 -674
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +13 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +405 -405
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -13
- data/lib/spikard/handler_wrapper.rb +113 -113
- data/lib/spikard/provide.rb +214 -214
- data/lib/spikard/response.rb +173 -173
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -44
- data/lib/spikard/testing.rb +256 -256
- data/lib/spikard/upload_file.rb +131 -131
- data/lib/spikard/version.rb +5 -5
- data/lib/spikard/websocket.rb +59 -59
- data/lib/spikard.rb +43 -43
- data/sig/spikard.rbs +366 -366
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -63
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +132 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +752 -752
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +194 -194
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +246 -246
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +401 -401
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +238 -238
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +24 -24
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +292 -292
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +616 -616
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +305 -305
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -248
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +351 -351
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +454 -454
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +383 -383
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +280 -280
- data/vendor/crates/spikard-core/Cargo.toml +40 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -133
- data/vendor/crates/spikard-core/src/debug.rs +127 -127
- data/vendor/crates/spikard-core/src/di/container.rs +702 -702
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -273
- data/vendor/crates/spikard-core/src/di/error.rs +118 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +534 -534
- data/vendor/crates/spikard-core/src/di/graph.rs +506 -506
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +405 -405
- data/vendor/crates/spikard-core/src/di/value.rs +281 -281
- data/vendor/crates/spikard-core/src/errors.rs +69 -69
- data/vendor/crates/spikard-core/src/http.rs +415 -415
- data/vendor/crates/spikard-core/src/lib.rs +29 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +1186 -1186
- data/vendor/crates/spikard-core/src/metadata.rs +389 -389
- data/vendor/crates/spikard-core/src/parameters.rs +2525 -2525
- data/vendor/crates/spikard-core/src/problem.rs +344 -344
- data/vendor/crates/spikard-core/src/request_data.rs +1154 -1154
- data/vendor/crates/spikard-core/src/router.rs +510 -510
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -304
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +696 -688
- data/vendor/crates/spikard-core/src/validation/mod.rs +457 -457
- data/vendor/crates/spikard-http/Cargo.toml +62 -64
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +148 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +92 -92
- data/vendor/crates/spikard-http/src/auth.rs +296 -296
- data/vendor/crates/spikard-http/src/background.rs +1860 -1860
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -8
- data/vendor/crates/spikard-http/src/cors.rs +1005 -1005
- data/vendor/crates/spikard-http/src/debug.rs +128 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +1668 -1668
- data/vendor/crates/spikard-http/src/handler_response.rs +901 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +838 -830
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +290 -290
- data/vendor/crates/spikard-http/src/lib.rs +534 -534
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +230 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +1193 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +560 -540
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +912 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +513 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +768 -735
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +1363 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +665 -665
- data/vendor/crates/spikard-http/src/query_parser.rs +793 -793
- data/vendor/crates/spikard-http/src/response.rs +720 -720
- data/vendor/crates/spikard-http/src/server/handler.rs +1650 -1650
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +234 -234
- data/vendor/crates/spikard-http/src/server/mod.rs +1593 -1502
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +789 -770
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +629 -599
- data/vendor/crates/spikard-http/src/sse.rs +1409 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +52 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +64 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +311 -283
- data/vendor/crates/spikard-http/src/testing.rs +406 -377
- data/vendor/crates/spikard-http/src/websocket.rs +1404 -1375
- data/vendor/crates/spikard-http/tests/background_behavior.rs +832 -832
- data/vendor/crates/spikard-http/tests/common/handlers.rs +309 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +26 -26
- data/vendor/crates/spikard-http/tests/di_integration.rs +192 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +5 -5
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1093 -1093
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +656 -656
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +314 -314
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +620 -620
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +663 -663
- data/vendor/crates/spikard-rb/Cargo.toml +48 -48
- data/vendor/crates/spikard-rb/build.rs +199 -199
- data/vendor/crates/spikard-rb/src/background.rs +63 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +285 -285
- data/vendor/crates/spikard-rb/src/conversion.rs +554 -554
- data/vendor/crates/spikard-rb/src/di/builder.rs +100 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +375 -375
- data/vendor/crates/spikard-rb/src/handler.rs +618 -618
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -3
- data/vendor/crates/spikard-rb/src/lib.rs +1806 -1810
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -275
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +442 -447
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +324 -324
- data/vendor/crates/spikard-rb/src/server.rs +305 -308
- data/vendor/crates/spikard-rb/src/sse.rs +231 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +538 -551
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -143
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +608 -635
- data/vendor/crates/spikard-rb/src/websocket.rs +377 -374
- metadata +15 -1
|
@@ -1,246 +1,246 @@
|
|
|
1
|
-
//! Dependency injection base traits
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides language-agnostic DI abstractions that eliminate
|
|
4
|
-
//! duplicate value/factory patterns across Node, Ruby, and PHP bindings.
|
|
5
|
-
|
|
6
|
-
use std::any::Any;
|
|
7
|
-
use std::future::Future;
|
|
8
|
-
use std::pin::Pin;
|
|
9
|
-
use std::sync::Arc;
|
|
10
|
-
|
|
11
|
-
use http::Request;
|
|
12
|
-
use spikard_core::RequestData;
|
|
13
|
-
use spikard_core::di::{Dependency, DependencyError, ResolvedDependencies};
|
|
14
|
-
|
|
15
|
-
/// Type alias for the common dependency resolution future type
|
|
16
|
-
type DependencyFuture<'a> =
|
|
17
|
-
Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + 'a>>;
|
|
18
|
-
|
|
19
|
-
/// Adapter trait for value dependencies across language bindings
|
|
20
|
-
///
|
|
21
|
-
/// Language bindings should implement this trait to wrap their
|
|
22
|
-
/// language-specific value storage (e.g., Py<PyAny>, Opaque<Value>, etc.)
|
|
23
|
-
pub trait ValueDependencyAdapter: Send + Sync {
|
|
24
|
-
/// Get the dependency key
|
|
25
|
-
fn key(&self) -> &str;
|
|
26
|
-
|
|
27
|
-
/// Resolve the stored value
|
|
28
|
-
///
|
|
29
|
-
/// Returns an Arc<dyn Any> that can be downcast to the concrete type
|
|
30
|
-
fn resolve_value(&self) -> DependencyFuture<'_>;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/// Adapter trait for factory dependencies across language bindings
|
|
34
|
-
///
|
|
35
|
-
/// Language bindings should implement this trait to wrap their
|
|
36
|
-
/// language-specific callable storage (e.g., Py<PyAny>, ThreadsafeFunction, etc.)
|
|
37
|
-
pub trait FactoryDependencyAdapter: Send + Sync {
|
|
38
|
-
/// Get the dependency key
|
|
39
|
-
fn key(&self) -> &str;
|
|
40
|
-
|
|
41
|
-
/// Invoke the factory with resolved dependencies
|
|
42
|
-
///
|
|
43
|
-
/// The factory receives already-resolved dependencies and returns
|
|
44
|
-
/// a new instance wrapped in Arc<dyn Any>
|
|
45
|
-
fn invoke_factory(
|
|
46
|
-
&self,
|
|
47
|
-
request: &Request<()>,
|
|
48
|
-
request_data: &RequestData,
|
|
49
|
-
resolved: &ResolvedDependencies,
|
|
50
|
-
) -> DependencyFuture<'_>;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/// Bridge between language-specific adapters and core DI system
|
|
54
|
-
///
|
|
55
|
-
/// This struct implements the core `Dependency` trait while delegating
|
|
56
|
-
/// to the language-specific adapter implementation
|
|
57
|
-
pub struct ValueDependencyBridge<T: ValueDependencyAdapter> {
|
|
58
|
-
adapter: Arc<T>,
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
impl<T: ValueDependencyAdapter + 'static> ValueDependencyBridge<T> {
|
|
62
|
-
/// Create a new value dependency bridge
|
|
63
|
-
pub fn new(adapter: T) -> Self {
|
|
64
|
-
Self {
|
|
65
|
-
adapter: Arc::new(adapter),
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
impl<T: ValueDependencyAdapter + 'static> Dependency for ValueDependencyBridge<T> {
|
|
71
|
-
fn resolve(
|
|
72
|
-
&self,
|
|
73
|
-
_request: &Request<()>,
|
|
74
|
-
_request_data: &RequestData,
|
|
75
|
-
_resolved: &ResolvedDependencies,
|
|
76
|
-
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
77
|
-
self.adapter.resolve_value()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
fn key(&self) -> &str {
|
|
81
|
-
self.adapter.key()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
fn depends_on(&self) -> Vec<String> {
|
|
85
|
-
vec![]
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/// Bridge between language-specific factory adapters and core DI system
|
|
90
|
-
pub struct FactoryDependencyBridge<T: FactoryDependencyAdapter> {
|
|
91
|
-
adapter: Arc<T>,
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
impl<T: FactoryDependencyAdapter + 'static> FactoryDependencyBridge<T> {
|
|
95
|
-
/// Create a new factory dependency bridge
|
|
96
|
-
pub fn new(adapter: T) -> Self {
|
|
97
|
-
Self {
|
|
98
|
-
adapter: Arc::new(adapter),
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
impl<T: FactoryDependencyAdapter + 'static> Dependency for FactoryDependencyBridge<T> {
|
|
104
|
-
fn resolve(
|
|
105
|
-
&self,
|
|
106
|
-
request: &Request<()>,
|
|
107
|
-
request_data: &RequestData,
|
|
108
|
-
resolved: &ResolvedDependencies,
|
|
109
|
-
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
110
|
-
self.adapter.invoke_factory(request, request_data, resolved)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
fn key(&self) -> &str {
|
|
114
|
-
self.adapter.key()
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
fn depends_on(&self) -> Vec<String> {
|
|
118
|
-
vec![]
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
#[cfg(test)]
|
|
123
|
-
mod tests {
|
|
124
|
-
use super::*;
|
|
125
|
-
use std::collections::HashMap;
|
|
126
|
-
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
127
|
-
|
|
128
|
-
struct MockValueAdapter {
|
|
129
|
-
key: String,
|
|
130
|
-
value: i32,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
impl ValueDependencyAdapter for MockValueAdapter {
|
|
134
|
-
fn key(&self) -> &str {
|
|
135
|
-
&self.key
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
fn resolve_value(
|
|
139
|
-
&self,
|
|
140
|
-
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
141
|
-
let value = self.value;
|
|
142
|
-
Box::pin(async move { Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>) })
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
struct MockFactoryAdapter {
|
|
147
|
-
key: String,
|
|
148
|
-
call_count: Arc<AtomicUsize>,
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
impl FactoryDependencyAdapter for MockFactoryAdapter {
|
|
152
|
-
fn key(&self) -> &str {
|
|
153
|
-
&self.key
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
fn invoke_factory(
|
|
157
|
-
&self,
|
|
158
|
-
_request: &Request<()>,
|
|
159
|
-
_request_data: &RequestData,
|
|
160
|
-
_resolved: &ResolvedDependencies,
|
|
161
|
-
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
162
|
-
let count = self.call_count.clone();
|
|
163
|
-
count.fetch_add(1, Ordering::SeqCst);
|
|
164
|
-
Box::pin(async move {
|
|
165
|
-
let value = count.load(Ordering::SeqCst);
|
|
166
|
-
Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>)
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
#[tokio::test]
|
|
172
|
-
async fn test_value_dependency_bridge() {
|
|
173
|
-
let adapter = MockValueAdapter {
|
|
174
|
-
key: "test_key".to_string(),
|
|
175
|
-
value: 42,
|
|
176
|
-
};
|
|
177
|
-
let bridge = ValueDependencyBridge::new(adapter);
|
|
178
|
-
|
|
179
|
-
assert_eq!(bridge.key(), "test_key");
|
|
180
|
-
assert_eq!(bridge.depends_on(), Vec::<String>::new());
|
|
181
|
-
|
|
182
|
-
let request = Request::builder().body(()).unwrap();
|
|
183
|
-
let request_data = RequestData {
|
|
184
|
-
path_params: Arc::new(HashMap::new()),
|
|
185
|
-
query_params: serde_json::Value::Null,
|
|
186
|
-
validated_params: None,
|
|
187
|
-
raw_query_params: Arc::new(HashMap::new()),
|
|
188
|
-
body: serde_json::Value::Null,
|
|
189
|
-
raw_body: None,
|
|
190
|
-
headers: Arc::new(HashMap::new()),
|
|
191
|
-
cookies: Arc::new(HashMap::new()),
|
|
192
|
-
method: "GET".to_string(),
|
|
193
|
-
path: "/".to_string(),
|
|
194
|
-
dependencies: None,
|
|
195
|
-
};
|
|
196
|
-
let resolved = ResolvedDependencies::new();
|
|
197
|
-
|
|
198
|
-
let result = bridge.resolve(&request, &request_data, &resolved).await;
|
|
199
|
-
assert!(result.is_ok());
|
|
200
|
-
|
|
201
|
-
let value = result.unwrap();
|
|
202
|
-
let downcast = value.downcast_ref::<i32>();
|
|
203
|
-
assert_eq!(*downcast.unwrap(), 42);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
#[tokio::test]
|
|
207
|
-
async fn test_factory_dependency_bridge() {
|
|
208
|
-
let call_count = Arc::new(AtomicUsize::new(0));
|
|
209
|
-
let adapter = MockFactoryAdapter {
|
|
210
|
-
key: "factory_key".to_string(),
|
|
211
|
-
call_count: call_count.clone(),
|
|
212
|
-
};
|
|
213
|
-
let bridge = FactoryDependencyBridge::new(adapter);
|
|
214
|
-
|
|
215
|
-
assert_eq!(bridge.key(), "factory_key");
|
|
216
|
-
assert_eq!(bridge.depends_on(), Vec::<String>::new());
|
|
217
|
-
|
|
218
|
-
let request = Request::builder().body(()).unwrap();
|
|
219
|
-
let request_data = RequestData {
|
|
220
|
-
path_params: Arc::new(HashMap::new()),
|
|
221
|
-
query_params: serde_json::Value::Null,
|
|
222
|
-
validated_params: None,
|
|
223
|
-
raw_query_params: Arc::new(HashMap::new()),
|
|
224
|
-
body: serde_json::Value::Null,
|
|
225
|
-
raw_body: None,
|
|
226
|
-
headers: Arc::new(HashMap::new()),
|
|
227
|
-
cookies: Arc::new(HashMap::new()),
|
|
228
|
-
method: "GET".to_string(),
|
|
229
|
-
path: "/".to_string(),
|
|
230
|
-
dependencies: None,
|
|
231
|
-
};
|
|
232
|
-
let resolved = ResolvedDependencies::new();
|
|
233
|
-
|
|
234
|
-
let result1 = bridge.resolve(&request, &request_data, &resolved).await;
|
|
235
|
-
assert!(result1.is_ok());
|
|
236
|
-
let value1 = result1.unwrap();
|
|
237
|
-
let count1 = *value1.downcast_ref::<usize>().unwrap();
|
|
238
|
-
assert_eq!(count1, 1);
|
|
239
|
-
|
|
240
|
-
let result2 = bridge.resolve(&request, &request_data, &resolved).await;
|
|
241
|
-
assert!(result2.is_ok());
|
|
242
|
-
let value2 = result2.unwrap();
|
|
243
|
-
let count2 = *value2.downcast_ref::<usize>().unwrap();
|
|
244
|
-
assert_eq!(count2, 2);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
1
|
+
//! Dependency injection base traits
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides language-agnostic DI abstractions that eliminate
|
|
4
|
+
//! duplicate value/factory patterns across Node, Ruby, and PHP bindings.
|
|
5
|
+
|
|
6
|
+
use std::any::Any;
|
|
7
|
+
use std::future::Future;
|
|
8
|
+
use std::pin::Pin;
|
|
9
|
+
use std::sync::Arc;
|
|
10
|
+
|
|
11
|
+
use http::Request;
|
|
12
|
+
use spikard_core::RequestData;
|
|
13
|
+
use spikard_core::di::{Dependency, DependencyError, ResolvedDependencies};
|
|
14
|
+
|
|
15
|
+
/// Type alias for the common dependency resolution future type
|
|
16
|
+
type DependencyFuture<'a> =
|
|
17
|
+
Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + 'a>>;
|
|
18
|
+
|
|
19
|
+
/// Adapter trait for value dependencies across language bindings
|
|
20
|
+
///
|
|
21
|
+
/// Language bindings should implement this trait to wrap their
|
|
22
|
+
/// language-specific value storage (e.g., Py<PyAny>, Opaque<Value>, etc.)
|
|
23
|
+
pub trait ValueDependencyAdapter: Send + Sync {
|
|
24
|
+
/// Get the dependency key
|
|
25
|
+
fn key(&self) -> &str;
|
|
26
|
+
|
|
27
|
+
/// Resolve the stored value
|
|
28
|
+
///
|
|
29
|
+
/// Returns an Arc<dyn Any> that can be downcast to the concrete type
|
|
30
|
+
fn resolve_value(&self) -> DependencyFuture<'_>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Adapter trait for factory dependencies across language bindings
|
|
34
|
+
///
|
|
35
|
+
/// Language bindings should implement this trait to wrap their
|
|
36
|
+
/// language-specific callable storage (e.g., Py<PyAny>, ThreadsafeFunction, etc.)
|
|
37
|
+
pub trait FactoryDependencyAdapter: Send + Sync {
|
|
38
|
+
/// Get the dependency key
|
|
39
|
+
fn key(&self) -> &str;
|
|
40
|
+
|
|
41
|
+
/// Invoke the factory with resolved dependencies
|
|
42
|
+
///
|
|
43
|
+
/// The factory receives already-resolved dependencies and returns
|
|
44
|
+
/// a new instance wrapped in Arc<dyn Any>
|
|
45
|
+
fn invoke_factory(
|
|
46
|
+
&self,
|
|
47
|
+
request: &Request<()>,
|
|
48
|
+
request_data: &RequestData,
|
|
49
|
+
resolved: &ResolvedDependencies,
|
|
50
|
+
) -> DependencyFuture<'_>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// Bridge between language-specific adapters and core DI system
|
|
54
|
+
///
|
|
55
|
+
/// This struct implements the core `Dependency` trait while delegating
|
|
56
|
+
/// to the language-specific adapter implementation
|
|
57
|
+
pub struct ValueDependencyBridge<T: ValueDependencyAdapter> {
|
|
58
|
+
adapter: Arc<T>,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl<T: ValueDependencyAdapter + 'static> ValueDependencyBridge<T> {
|
|
62
|
+
/// Create a new value dependency bridge
|
|
63
|
+
pub fn new(adapter: T) -> Self {
|
|
64
|
+
Self {
|
|
65
|
+
adapter: Arc::new(adapter),
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl<T: ValueDependencyAdapter + 'static> Dependency for ValueDependencyBridge<T> {
|
|
71
|
+
fn resolve(
|
|
72
|
+
&self,
|
|
73
|
+
_request: &Request<()>,
|
|
74
|
+
_request_data: &RequestData,
|
|
75
|
+
_resolved: &ResolvedDependencies,
|
|
76
|
+
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
77
|
+
self.adapter.resolve_value()
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn key(&self) -> &str {
|
|
81
|
+
self.adapter.key()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
fn depends_on(&self) -> Vec<String> {
|
|
85
|
+
vec![]
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Bridge between language-specific factory adapters and core DI system
|
|
90
|
+
pub struct FactoryDependencyBridge<T: FactoryDependencyAdapter> {
|
|
91
|
+
adapter: Arc<T>,
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
impl<T: FactoryDependencyAdapter + 'static> FactoryDependencyBridge<T> {
|
|
95
|
+
/// Create a new factory dependency bridge
|
|
96
|
+
pub fn new(adapter: T) -> Self {
|
|
97
|
+
Self {
|
|
98
|
+
adapter: Arc::new(adapter),
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl<T: FactoryDependencyAdapter + 'static> Dependency for FactoryDependencyBridge<T> {
|
|
104
|
+
fn resolve(
|
|
105
|
+
&self,
|
|
106
|
+
request: &Request<()>,
|
|
107
|
+
request_data: &RequestData,
|
|
108
|
+
resolved: &ResolvedDependencies,
|
|
109
|
+
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
110
|
+
self.adapter.invoke_factory(request, request_data, resolved)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn key(&self) -> &str {
|
|
114
|
+
self.adapter.key()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
fn depends_on(&self) -> Vec<String> {
|
|
118
|
+
vec![]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#[cfg(test)]
|
|
123
|
+
mod tests {
|
|
124
|
+
use super::*;
|
|
125
|
+
use std::collections::HashMap;
|
|
126
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
127
|
+
|
|
128
|
+
struct MockValueAdapter {
|
|
129
|
+
key: String,
|
|
130
|
+
value: i32,
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
impl ValueDependencyAdapter for MockValueAdapter {
|
|
134
|
+
fn key(&self) -> &str {
|
|
135
|
+
&self.key
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
fn resolve_value(
|
|
139
|
+
&self,
|
|
140
|
+
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
141
|
+
let value = self.value;
|
|
142
|
+
Box::pin(async move { Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>) })
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
struct MockFactoryAdapter {
|
|
147
|
+
key: String,
|
|
148
|
+
call_count: Arc<AtomicUsize>,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
impl FactoryDependencyAdapter for MockFactoryAdapter {
|
|
152
|
+
fn key(&self) -> &str {
|
|
153
|
+
&self.key
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
fn invoke_factory(
|
|
157
|
+
&self,
|
|
158
|
+
_request: &Request<()>,
|
|
159
|
+
_request_data: &RequestData,
|
|
160
|
+
_resolved: &ResolvedDependencies,
|
|
161
|
+
) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send + '_>> {
|
|
162
|
+
let count = self.call_count.clone();
|
|
163
|
+
count.fetch_add(1, Ordering::SeqCst);
|
|
164
|
+
Box::pin(async move {
|
|
165
|
+
let value = count.load(Ordering::SeqCst);
|
|
166
|
+
Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>)
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#[tokio::test]
|
|
172
|
+
async fn test_value_dependency_bridge() {
|
|
173
|
+
let adapter = MockValueAdapter {
|
|
174
|
+
key: "test_key".to_string(),
|
|
175
|
+
value: 42,
|
|
176
|
+
};
|
|
177
|
+
let bridge = ValueDependencyBridge::new(adapter);
|
|
178
|
+
|
|
179
|
+
assert_eq!(bridge.key(), "test_key");
|
|
180
|
+
assert_eq!(bridge.depends_on(), Vec::<String>::new());
|
|
181
|
+
|
|
182
|
+
let request = Request::builder().body(()).unwrap();
|
|
183
|
+
let request_data = RequestData {
|
|
184
|
+
path_params: Arc::new(HashMap::new()),
|
|
185
|
+
query_params: serde_json::Value::Null,
|
|
186
|
+
validated_params: None,
|
|
187
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
188
|
+
body: serde_json::Value::Null,
|
|
189
|
+
raw_body: None,
|
|
190
|
+
headers: Arc::new(HashMap::new()),
|
|
191
|
+
cookies: Arc::new(HashMap::new()),
|
|
192
|
+
method: "GET".to_string(),
|
|
193
|
+
path: "/".to_string(),
|
|
194
|
+
dependencies: None,
|
|
195
|
+
};
|
|
196
|
+
let resolved = ResolvedDependencies::new();
|
|
197
|
+
|
|
198
|
+
let result = bridge.resolve(&request, &request_data, &resolved).await;
|
|
199
|
+
assert!(result.is_ok());
|
|
200
|
+
|
|
201
|
+
let value = result.unwrap();
|
|
202
|
+
let downcast = value.downcast_ref::<i32>();
|
|
203
|
+
assert_eq!(*downcast.unwrap(), 42);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[tokio::test]
|
|
207
|
+
async fn test_factory_dependency_bridge() {
|
|
208
|
+
let call_count = Arc::new(AtomicUsize::new(0));
|
|
209
|
+
let adapter = MockFactoryAdapter {
|
|
210
|
+
key: "factory_key".to_string(),
|
|
211
|
+
call_count: call_count.clone(),
|
|
212
|
+
};
|
|
213
|
+
let bridge = FactoryDependencyBridge::new(adapter);
|
|
214
|
+
|
|
215
|
+
assert_eq!(bridge.key(), "factory_key");
|
|
216
|
+
assert_eq!(bridge.depends_on(), Vec::<String>::new());
|
|
217
|
+
|
|
218
|
+
let request = Request::builder().body(()).unwrap();
|
|
219
|
+
let request_data = RequestData {
|
|
220
|
+
path_params: Arc::new(HashMap::new()),
|
|
221
|
+
query_params: serde_json::Value::Null,
|
|
222
|
+
validated_params: None,
|
|
223
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
224
|
+
body: serde_json::Value::Null,
|
|
225
|
+
raw_body: None,
|
|
226
|
+
headers: Arc::new(HashMap::new()),
|
|
227
|
+
cookies: Arc::new(HashMap::new()),
|
|
228
|
+
method: "GET".to_string(),
|
|
229
|
+
path: "/".to_string(),
|
|
230
|
+
dependencies: None,
|
|
231
|
+
};
|
|
232
|
+
let resolved = ResolvedDependencies::new();
|
|
233
|
+
|
|
234
|
+
let result1 = bridge.resolve(&request, &request_data, &resolved).await;
|
|
235
|
+
assert!(result1.is_ok());
|
|
236
|
+
let value1 = result1.unwrap();
|
|
237
|
+
let count1 = *value1.downcast_ref::<usize>().unwrap();
|
|
238
|
+
assert_eq!(count1, 1);
|
|
239
|
+
|
|
240
|
+
let result2 = bridge.resolve(&request, &request_data, &resolved).await;
|
|
241
|
+
assert!(result2.is_ok());
|
|
242
|
+
let value2 = result2.unwrap();
|
|
243
|
+
let count2 = *value2.downcast_ref::<usize>().unwrap();
|
|
244
|
+
assert_eq!(count2, 2);
|
|
245
|
+
}
|
|
246
|
+
}
|