spikard 0.4.0-x86_64-linux
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 +7 -0
- data/LICENSE +1 -0
- data/README.md +659 -0
- data/ext/spikard_rb/Cargo.toml +17 -0
- data/ext/spikard_rb/extconf.rb +10 -0
- data/ext/spikard_rb/src/lib.rs +6 -0
- data/lib/spikard/app.rb +405 -0
- data/lib/spikard/background.rb +27 -0
- data/lib/spikard/config.rb +396 -0
- data/lib/spikard/converters.rb +13 -0
- data/lib/spikard/handler_wrapper.rb +113 -0
- data/lib/spikard/provide.rb +214 -0
- data/lib/spikard/response.rb +173 -0
- data/lib/spikard/schema.rb +243 -0
- data/lib/spikard/sse.rb +111 -0
- data/lib/spikard/streaming_response.rb +44 -0
- data/lib/spikard/testing.rb +221 -0
- data/lib/spikard/upload_file.rb +131 -0
- data/lib/spikard/version.rb +5 -0
- data/lib/spikard/websocket.rb +59 -0
- data/lib/spikard.rb +43 -0
- data/sig/spikard.rbs +366 -0
- data/vendor/bundle/ruby/3.4.0/gems/diff-lcs-1.6.2/mise.toml +5 -0
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +63 -0
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +139 -0
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +561 -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 +403 -0
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +274 -0
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +25 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +298 -0
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +637 -0
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +309 -0
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +248 -0
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +355 -0
- data/vendor/crates/spikard-bindings-shared/tests/comprehensive_coverage.rs +502 -0
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +389 -0
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +413 -0
- data/vendor/crates/spikard-core/Cargo.toml +40 -0
- data/vendor/crates/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/crates/spikard-core/src/debug.rs +63 -0
- data/vendor/crates/spikard-core/src/di/container.rs +726 -0
- data/vendor/crates/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/crates/spikard-core/src/di/error.rs +118 -0
- data/vendor/crates/spikard-core/src/di/factory.rs +538 -0
- data/vendor/crates/spikard-core/src/di/graph.rs +545 -0
- data/vendor/crates/spikard-core/src/di/mod.rs +192 -0
- data/vendor/crates/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/crates/spikard-core/src/di/value.rs +283 -0
- data/vendor/crates/spikard-core/src/errors.rs +39 -0
- data/vendor/crates/spikard-core/src/http.rs +153 -0
- data/vendor/crates/spikard-core/src/lib.rs +29 -0
- data/vendor/crates/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/crates/spikard-core/src/metadata.rs +397 -0
- data/vendor/crates/spikard-core/src/parameters.rs +723 -0
- data/vendor/crates/spikard-core/src/problem.rs +310 -0
- data/vendor/crates/spikard-core/src/request_data.rs +189 -0
- data/vendor/crates/spikard-core/src/router.rs +249 -0
- data/vendor/crates/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/crates/spikard-core/src/type_hints.rs +304 -0
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +689 -0
- data/vendor/crates/spikard-core/src/validation/mod.rs +459 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +147 -0
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +91 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +1562 -0
- data/vendor/crates/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/crates/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/crates/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/crates/spikard-http/src/cors.rs +490 -0
- data/vendor/crates/spikard-http/src/debug.rs +63 -0
- data/vendor/crates/spikard-http/src/di_handler.rs +1878 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +532 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +861 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +524 -0
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/crates/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/crates/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +930 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +541 -0
- data/vendor/crates/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/crates/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +535 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +867 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +678 -0
- data/vendor/crates/spikard-http/src/query_parser.rs +369 -0
- data/vendor/crates/spikard-http/src/response.rs +399 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +1557 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +806 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +630 -0
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +497 -0
- data/vendor/crates/spikard-http/src/sse.rs +961 -0
- data/vendor/crates/spikard-http/src/testing/form.rs +14 -0
- data/vendor/crates/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/crates/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/crates/spikard-http/src/testing.rs +377 -0
- data/vendor/crates/spikard-http/src/websocket.rs +831 -0
- data/vendor/crates/spikard-http/tests/background_behavior.rs +918 -0
- data/vendor/crates/spikard-http/tests/common/handlers.rs +308 -0
- data/vendor/crates/spikard-http/tests/common/mod.rs +21 -0
- data/vendor/crates/spikard-http/tests/di_integration.rs +202 -0
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +4 -0
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +1135 -0
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +688 -0
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +324 -0
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +728 -0
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +724 -0
- data/vendor/crates/spikard-rb/Cargo.toml +43 -0
- data/vendor/crates/spikard-rb/build.rs +199 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/config/server_config.rs +283 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +459 -0
- data/vendor/crates/spikard-rb/src/di/builder.rs +105 -0
- data/vendor/crates/spikard-rb/src/di/mod.rs +413 -0
- data/vendor/crates/spikard-rb/src/handler.rs +612 -0
- data/vendor/crates/spikard-rb/src/integration/mod.rs +3 -0
- data/vendor/crates/spikard-rb/src/lib.rs +1857 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +275 -0
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +427 -0
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +5 -0
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +326 -0
- data/vendor/crates/spikard-rb/src/server.rs +283 -0
- data/vendor/crates/spikard-rb/src/sse.rs +231 -0
- data/vendor/crates/spikard-rb/src/testing/client.rs +404 -0
- data/vendor/crates/spikard-rb/src/testing/mod.rs +7 -0
- data/vendor/crates/spikard-rb/src/testing/sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- data/vendor/crates/spikard-rb/tests/magnus_ffi_tests.rs +14 -0
- metadata +213 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
//! Language-agnostic conversion interfaces
|
|
2
|
+
|
|
3
|
+
use std::any::Any;
|
|
4
|
+
|
|
5
|
+
/// Trait for converting from language-specific types to Rust types
|
|
6
|
+
pub trait FromLanguage: Sized {
|
|
7
|
+
/// The error type for conversion failures
|
|
8
|
+
type Error: std::fmt::Display;
|
|
9
|
+
|
|
10
|
+
/// Convert from a language-specific value
|
|
11
|
+
fn from_any(value: &(dyn Any + Send + Sync)) -> Result<Self, Self::Error>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// Trait for converting from Rust types to language-specific types
|
|
15
|
+
pub trait ToLanguage {
|
|
16
|
+
/// The error type for conversion failures
|
|
17
|
+
type Error: std::fmt::Display;
|
|
18
|
+
|
|
19
|
+
/// Convert to a language-specific value
|
|
20
|
+
fn to_any(&self) -> Result<Box<dyn Any + Send + Sync>, Self::Error>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Trait for converting to/from JSON values
|
|
24
|
+
pub trait JsonConvertible: Sized {
|
|
25
|
+
/// The error type for conversion failures
|
|
26
|
+
type Error: std::fmt::Display;
|
|
27
|
+
|
|
28
|
+
/// Convert from a JSON value
|
|
29
|
+
fn from_json(value: serde_json::Value) -> Result<Self, Self::Error>;
|
|
30
|
+
|
|
31
|
+
/// Convert to a JSON value
|
|
32
|
+
fn to_json(&self) -> Result<serde_json::Value, Self::Error>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Default JSON conversion error
|
|
36
|
+
#[derive(Debug)]
|
|
37
|
+
pub struct JsonConversionError(pub String);
|
|
38
|
+
|
|
39
|
+
impl std::fmt::Display for JsonConversionError {
|
|
40
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
41
|
+
write!(f, "JSON conversion error: {}", self.0)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/// Default implementation for JSON values
|
|
46
|
+
impl JsonConvertible for serde_json::Value {
|
|
47
|
+
type Error = JsonConversionError;
|
|
48
|
+
|
|
49
|
+
fn from_json(value: serde_json::Value) -> Result<Self, Self::Error> {
|
|
50
|
+
Ok(value)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn to_json(&self) -> Result<serde_json::Value, Self::Error> {
|
|
54
|
+
Ok(self.clone())
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[cfg(test)]
|
|
59
|
+
mod tests {
|
|
60
|
+
use super::*;
|
|
61
|
+
use serde_json::json;
|
|
62
|
+
|
|
63
|
+
#[derive(Debug)]
|
|
64
|
+
struct TestType {
|
|
65
|
+
value: i32,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
impl FromLanguage for TestType {
|
|
69
|
+
type Error = String;
|
|
70
|
+
|
|
71
|
+
fn from_any(value: &(dyn Any + Send + Sync)) -> Result<Self, Self::Error> {
|
|
72
|
+
value
|
|
73
|
+
.downcast_ref::<i32>()
|
|
74
|
+
.map(|&v| TestType { value: v })
|
|
75
|
+
.ok_or_else(|| "Invalid type".to_string())
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl ToLanguage for TestType {
|
|
80
|
+
type Error = String;
|
|
81
|
+
|
|
82
|
+
fn to_any(&self) -> Result<Box<dyn Any + Send + Sync>, Self::Error> {
|
|
83
|
+
Ok(Box::new(self.value))
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn test_json_conversion_error_display() {
|
|
89
|
+
let err = JsonConversionError("test error".to_string());
|
|
90
|
+
assert_eq!(err.to_string(), "JSON conversion error: test error");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
#[test]
|
|
94
|
+
fn test_json_value_from_json() {
|
|
95
|
+
let input = json!({ "key": "value" });
|
|
96
|
+
let result = serde_json::Value::from_json(input.clone());
|
|
97
|
+
assert!(result.is_ok());
|
|
98
|
+
assert_eq!(result.unwrap(), input);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#[test]
|
|
102
|
+
fn test_json_value_to_json() {
|
|
103
|
+
let input = json!({ "key": "value" });
|
|
104
|
+
let result = serde_json::Value::to_json(&input);
|
|
105
|
+
assert!(result.is_ok());
|
|
106
|
+
assert_eq!(result.unwrap(), input);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[test]
|
|
110
|
+
fn test_json_value_roundtrip() {
|
|
111
|
+
let original = json!({
|
|
112
|
+
"string": "test",
|
|
113
|
+
"number": 42,
|
|
114
|
+
"float": 3.14,
|
|
115
|
+
"bool": true,
|
|
116
|
+
"null": null,
|
|
117
|
+
"array": [1, 2, 3],
|
|
118
|
+
"object": { "nested": "value" }
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
let converted = serde_json::Value::from_json(original.clone()).unwrap();
|
|
122
|
+
let back = converted.to_json().unwrap();
|
|
123
|
+
assert_eq!(original, back);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[test]
|
|
127
|
+
fn test_from_language_trait() {
|
|
128
|
+
let any_value: Box<dyn Any + Send + Sync> = Box::new(42i32);
|
|
129
|
+
let result = TestType::from_any(&*any_value);
|
|
130
|
+
assert!(result.is_ok());
|
|
131
|
+
assert_eq!(result.unwrap().value, 42);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[test]
|
|
135
|
+
fn test_from_language_wrong_type() {
|
|
136
|
+
let any_value: Box<dyn Any + Send + Sync> = Box::new("string");
|
|
137
|
+
let result = TestType::from_any(&*any_value);
|
|
138
|
+
assert!(result.is_err());
|
|
139
|
+
assert_eq!(result.unwrap_err(), "Invalid type");
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[test]
|
|
143
|
+
fn test_to_language_trait() {
|
|
144
|
+
let test_obj = TestType { value: 123 };
|
|
145
|
+
let result = test_obj.to_any();
|
|
146
|
+
assert!(result.is_ok());
|
|
147
|
+
|
|
148
|
+
let any_box = result.unwrap();
|
|
149
|
+
let downcast = any_box.downcast_ref::<i32>();
|
|
150
|
+
assert_eq!(*downcast.unwrap(), 123);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_json_null_conversion() {
|
|
155
|
+
let null_value = serde_json::Value::Null;
|
|
156
|
+
let result = serde_json::Value::from_json(null_value.clone());
|
|
157
|
+
assert!(result.is_ok());
|
|
158
|
+
assert!(result.unwrap().is_null());
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
#[test]
|
|
162
|
+
fn test_json_array_conversion() {
|
|
163
|
+
let array = json!([1, 2, 3, 4, 5]);
|
|
164
|
+
let result = serde_json::Value::from_json(array.clone());
|
|
165
|
+
assert!(result.is_ok());
|
|
166
|
+
let converted = result.unwrap();
|
|
167
|
+
assert!(converted.is_array());
|
|
168
|
+
assert_eq!(converted.as_array().unwrap().len(), 5);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#[test]
|
|
172
|
+
fn test_json_nested_object_conversion() {
|
|
173
|
+
let nested = json!({
|
|
174
|
+
"level1": {
|
|
175
|
+
"level2": {
|
|
176
|
+
"level3": {
|
|
177
|
+
"value": "deep"
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
let result = serde_json::Value::from_json(nested.clone());
|
|
184
|
+
assert!(result.is_ok());
|
|
185
|
+
let converted = result.unwrap();
|
|
186
|
+
assert_eq!(converted["level1"]["level2"]["level3"]["value"], "deep");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
#[test]
|
|
190
|
+
fn test_json_conversion_error_description() {
|
|
191
|
+
let err = JsonConversionError("Custom message".to_string());
|
|
192
|
+
assert!(err.to_string().contains("Custom message"));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +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
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
187
|
+
body: serde_json::Value::Null,
|
|
188
|
+
raw_body: None,
|
|
189
|
+
headers: Arc::new(HashMap::new()),
|
|
190
|
+
cookies: Arc::new(HashMap::new()),
|
|
191
|
+
method: "GET".to_string(),
|
|
192
|
+
path: "/".to_string(),
|
|
193
|
+
dependencies: None,
|
|
194
|
+
};
|
|
195
|
+
let resolved = ResolvedDependencies::new();
|
|
196
|
+
|
|
197
|
+
let result = bridge.resolve(&request, &request_data, &resolved).await;
|
|
198
|
+
assert!(result.is_ok());
|
|
199
|
+
|
|
200
|
+
let value = result.unwrap();
|
|
201
|
+
let downcast = value.downcast_ref::<i32>();
|
|
202
|
+
assert_eq!(*downcast.unwrap(), 42);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
#[tokio::test]
|
|
206
|
+
async fn test_factory_dependency_bridge() {
|
|
207
|
+
let call_count = Arc::new(AtomicUsize::new(0));
|
|
208
|
+
let adapter = MockFactoryAdapter {
|
|
209
|
+
key: "factory_key".to_string(),
|
|
210
|
+
call_count: call_count.clone(),
|
|
211
|
+
};
|
|
212
|
+
let bridge = FactoryDependencyBridge::new(adapter);
|
|
213
|
+
|
|
214
|
+
assert_eq!(bridge.key(), "factory_key");
|
|
215
|
+
assert_eq!(bridge.depends_on(), Vec::<String>::new());
|
|
216
|
+
|
|
217
|
+
let request = Request::builder().body(()).unwrap();
|
|
218
|
+
let request_data = RequestData {
|
|
219
|
+
path_params: Arc::new(HashMap::new()),
|
|
220
|
+
query_params: serde_json::Value::Null,
|
|
221
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
222
|
+
body: serde_json::Value::Null,
|
|
223
|
+
raw_body: None,
|
|
224
|
+
headers: Arc::new(HashMap::new()),
|
|
225
|
+
cookies: Arc::new(HashMap::new()),
|
|
226
|
+
method: "GET".to_string(),
|
|
227
|
+
path: "/".to_string(),
|
|
228
|
+
dependencies: None,
|
|
229
|
+
};
|
|
230
|
+
let resolved = ResolvedDependencies::new();
|
|
231
|
+
|
|
232
|
+
// First call
|
|
233
|
+
let result1 = bridge.resolve(&request, &request_data, &resolved).await;
|
|
234
|
+
assert!(result1.is_ok());
|
|
235
|
+
let value1 = result1.unwrap();
|
|
236
|
+
let count1 = *value1.downcast_ref::<usize>().unwrap();
|
|
237
|
+
assert_eq!(count1, 1);
|
|
238
|
+
|
|
239
|
+
// Second call - factory is invoked again
|
|
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
|
+
}
|