spikard 0.3.1 → 0.3.2
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/ext/spikard_rb/Cargo.toml +1 -1
- data/lib/spikard/version.rb +1 -1
- 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/parameters.rs +722 -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.rs +699 -0
- data/vendor/crates/spikard-http/Cargo.toml +58 -0
- data/vendor/crates/spikard-http/src/auth.rs +247 -0
- data/vendor/crates/spikard-http/src/background.rs +249 -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 +423 -0
- data/vendor/crates/spikard-http/src/handler_response.rs +190 -0
- data/vendor/crates/spikard-http/src/handler_trait.rs +228 -0
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/crates/spikard-http/src/lib.rs +529 -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 +86 -0
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +147 -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 +190 -0
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +308 -0
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +195 -0
- data/vendor/crates/spikard-http/src/parameters.rs +1 -0
- data/vendor/crates/spikard-http/src/problem.rs +1 -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/router.rs +1 -0
- data/vendor/crates/spikard-http/src/schema_registry.rs +1 -0
- data/vendor/crates/spikard-http/src/server/handler.rs +87 -0
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/crates/spikard-http/src/server/mod.rs +805 -0
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +119 -0
- data/vendor/crates/spikard-http/src/sse.rs +447 -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/type_hints.rs +1 -0
- data/vendor/crates/spikard-http/src/validation.rs +1 -0
- data/vendor/crates/spikard-http/src/websocket.rs +324 -0
- data/vendor/crates/spikard-rb/Cargo.toml +42 -0
- data/vendor/crates/spikard-rb/build.rs +8 -0
- data/vendor/crates/spikard-rb/src/background.rs +63 -0
- data/vendor/crates/spikard-rb/src/config.rs +294 -0
- data/vendor/crates/spikard-rb/src/conversion.rs +453 -0
- data/vendor/crates/spikard-rb/src/di.rs +409 -0
- data/vendor/crates/spikard-rb/src/handler.rs +625 -0
- data/vendor/crates/spikard-rb/src/lib.rs +2771 -0
- data/vendor/crates/spikard-rb/src/lifecycle.rs +274 -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/test_client.rs +404 -0
- data/vendor/crates/spikard-rb/src/test_sse.rs +143 -0
- data/vendor/crates/spikard-rb/src/test_websocket.rs +221 -0
- data/vendor/crates/spikard-rb/src/websocket.rs +233 -0
- data/vendor/spikard-core/Cargo.toml +40 -0
- data/vendor/spikard-core/src/bindings/mod.rs +3 -0
- data/vendor/spikard-core/src/bindings/response.rs +133 -0
- data/vendor/spikard-core/src/debug.rs +63 -0
- data/vendor/spikard-core/src/di/container.rs +726 -0
- data/vendor/spikard-core/src/di/dependency.rs +273 -0
- data/vendor/spikard-core/src/di/error.rs +118 -0
- data/vendor/spikard-core/src/di/factory.rs +538 -0
- data/vendor/spikard-core/src/di/graph.rs +545 -0
- data/vendor/spikard-core/src/di/mod.rs +192 -0
- data/vendor/spikard-core/src/di/resolved.rs +411 -0
- data/vendor/spikard-core/src/di/value.rs +283 -0
- data/vendor/spikard-core/src/http.rs +153 -0
- data/vendor/spikard-core/src/lib.rs +28 -0
- data/vendor/spikard-core/src/lifecycle.rs +422 -0
- data/vendor/spikard-core/src/parameters.rs +719 -0
- data/vendor/spikard-core/src/problem.rs +310 -0
- data/vendor/spikard-core/src/request_data.rs +189 -0
- data/vendor/spikard-core/src/router.rs +249 -0
- data/vendor/spikard-core/src/schema_registry.rs +183 -0
- data/vendor/spikard-core/src/type_hints.rs +304 -0
- data/vendor/spikard-core/src/validation.rs +699 -0
- data/vendor/spikard-http/Cargo.toml +58 -0
- data/vendor/spikard-http/src/auth.rs +247 -0
- data/vendor/spikard-http/src/background.rs +249 -0
- data/vendor/spikard-http/src/bindings/mod.rs +3 -0
- data/vendor/spikard-http/src/bindings/response.rs +1 -0
- data/vendor/spikard-http/src/body_metadata.rs +8 -0
- data/vendor/spikard-http/src/cors.rs +490 -0
- data/vendor/spikard-http/src/debug.rs +63 -0
- data/vendor/spikard-http/src/di_handler.rs +423 -0
- data/vendor/spikard-http/src/handler_response.rs +190 -0
- data/vendor/spikard-http/src/handler_trait.rs +228 -0
- data/vendor/spikard-http/src/handler_trait_tests.rs +284 -0
- data/vendor/spikard-http/src/lib.rs +529 -0
- data/vendor/spikard-http/src/lifecycle/adapter.rs +149 -0
- data/vendor/spikard-http/src/lifecycle.rs +428 -0
- data/vendor/spikard-http/src/middleware/mod.rs +285 -0
- data/vendor/spikard-http/src/middleware/multipart.rs +86 -0
- data/vendor/spikard-http/src/middleware/urlencoded.rs +147 -0
- data/vendor/spikard-http/src/middleware/validation.rs +287 -0
- data/vendor/spikard-http/src/openapi/mod.rs +309 -0
- data/vendor/spikard-http/src/openapi/parameter_extraction.rs +190 -0
- data/vendor/spikard-http/src/openapi/schema_conversion.rs +308 -0
- data/vendor/spikard-http/src/openapi/spec_generation.rs +195 -0
- data/vendor/spikard-http/src/parameters.rs +1 -0
- data/vendor/spikard-http/src/problem.rs +1 -0
- data/vendor/spikard-http/src/query_parser.rs +369 -0
- data/vendor/spikard-http/src/response.rs +399 -0
- data/vendor/spikard-http/src/router.rs +1 -0
- data/vendor/spikard-http/src/schema_registry.rs +1 -0
- data/vendor/spikard-http/src/server/handler.rs +80 -0
- data/vendor/spikard-http/src/server/lifecycle_execution.rs +98 -0
- data/vendor/spikard-http/src/server/mod.rs +805 -0
- data/vendor/spikard-http/src/server/request_extraction.rs +119 -0
- data/vendor/spikard-http/src/sse.rs +447 -0
- data/vendor/spikard-http/src/testing/form.rs +14 -0
- data/vendor/spikard-http/src/testing/multipart.rs +60 -0
- data/vendor/spikard-http/src/testing/test_client.rs +285 -0
- data/vendor/spikard-http/src/testing.rs +377 -0
- data/vendor/spikard-http/src/type_hints.rs +1 -0
- data/vendor/spikard-http/src/validation.rs +1 -0
- data/vendor/spikard-http/src/websocket.rs +324 -0
- data/vendor/spikard-rb/Cargo.toml +42 -0
- data/vendor/spikard-rb/build.rs +8 -0
- data/vendor/spikard-rb/src/background.rs +63 -0
- data/vendor/spikard-rb/src/config.rs +294 -0
- data/vendor/spikard-rb/src/conversion.rs +392 -0
- data/vendor/spikard-rb/src/di.rs +409 -0
- data/vendor/spikard-rb/src/handler.rs +534 -0
- data/vendor/spikard-rb/src/lib.rs +2020 -0
- data/vendor/spikard-rb/src/lifecycle.rs +267 -0
- data/vendor/spikard-rb/src/server.rs +283 -0
- data/vendor/spikard-rb/src/sse.rs +231 -0
- data/vendor/spikard-rb/src/test_client.rs +404 -0
- data/vendor/spikard-rb/src/test_sse.rs +143 -0
- data/vendor/spikard-rb/src/test_websocket.rs +221 -0
- data/vendor/spikard-rb/src/websocket.rs +233 -0
- metadata +158 -1
|
@@ -0,0 +1,538 @@
|
|
|
1
|
+
//! Factory dependency implementation
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides `FactoryDependency`, a dependency that uses a factory
|
|
4
|
+
//! function to create values dynamically based on request context.
|
|
5
|
+
|
|
6
|
+
use super::dependency::Dependency;
|
|
7
|
+
use super::error::DependencyError;
|
|
8
|
+
use super::resolved::ResolvedDependencies;
|
|
9
|
+
use crate::request_data::RequestData;
|
|
10
|
+
use http::Request;
|
|
11
|
+
use std::any::Any;
|
|
12
|
+
use std::future::Future;
|
|
13
|
+
use std::pin::Pin;
|
|
14
|
+
use std::sync::Arc;
|
|
15
|
+
|
|
16
|
+
type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
|
17
|
+
|
|
18
|
+
/// Factory function type for creating dependencies
|
|
19
|
+
///
|
|
20
|
+
/// The factory receives:
|
|
21
|
+
/// - The HTTP request
|
|
22
|
+
/// - Extracted request data
|
|
23
|
+
/// - Already-resolved dependencies
|
|
24
|
+
///
|
|
25
|
+
/// And returns a future that resolves to the dependency value or an error.
|
|
26
|
+
pub type FactoryFn = dyn Fn(
|
|
27
|
+
&Request<()>,
|
|
28
|
+
&RequestData,
|
|
29
|
+
&ResolvedDependencies,
|
|
30
|
+
) -> BoxFuture<'static, Result<Arc<dyn Any + Send + Sync>, DependencyError>>
|
|
31
|
+
+ Send
|
|
32
|
+
+ Sync;
|
|
33
|
+
|
|
34
|
+
/// A dependency that uses a factory function to create values
|
|
35
|
+
///
|
|
36
|
+
/// Factory dependencies are more flexible than value dependencies - they can:
|
|
37
|
+
/// - Access request data (headers, query params, etc.)
|
|
38
|
+
/// - Depend on other dependencies
|
|
39
|
+
/// - Perform async operations (database queries, HTTP requests)
|
|
40
|
+
/// - Return different values based on context
|
|
41
|
+
///
|
|
42
|
+
/// # Caching Strategies
|
|
43
|
+
///
|
|
44
|
+
/// - **Singleton**: Factory runs once globally, result cached forever
|
|
45
|
+
/// - **Cacheable**: Factory runs once per request, result cached for that request
|
|
46
|
+
/// - **Non-cacheable**: Factory runs every time the dependency is requested
|
|
47
|
+
///
|
|
48
|
+
/// # Examples
|
|
49
|
+
///
|
|
50
|
+
/// ```ignore
|
|
51
|
+
/// use spikard_core::di::{FactoryDependency, Dependency, ResolvedDependencies};
|
|
52
|
+
/// use http::Request;
|
|
53
|
+
/// use crate::request_data::RequestData;
|
|
54
|
+
/// use std::sync::Arc;
|
|
55
|
+
///
|
|
56
|
+
/// # tokio_test::block_on(async {
|
|
57
|
+
/// // Simple factory that returns a constant
|
|
58
|
+
/// let factory = FactoryDependency::builder("counter")
|
|
59
|
+
/// .factory(|_req, _data, _resolved| {
|
|
60
|
+
/// Box::pin(async {
|
|
61
|
+
/// Ok(Arc::new(42i32) as Arc<dyn std::any::Any + Send + Sync>)
|
|
62
|
+
/// })
|
|
63
|
+
/// })
|
|
64
|
+
/// .build();
|
|
65
|
+
///
|
|
66
|
+
/// assert_eq!(factory.key(), "counter");
|
|
67
|
+
/// # });
|
|
68
|
+
/// ```
|
|
69
|
+
pub struct FactoryDependency {
|
|
70
|
+
key: String,
|
|
71
|
+
factory: Arc<FactoryFn>,
|
|
72
|
+
dependencies: Vec<String>,
|
|
73
|
+
cacheable: bool,
|
|
74
|
+
singleton: bool,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl FactoryDependency {
|
|
78
|
+
/// Create a new builder for constructing a factory dependency
|
|
79
|
+
///
|
|
80
|
+
/// # Arguments
|
|
81
|
+
///
|
|
82
|
+
/// * `key` - The unique key for this dependency
|
|
83
|
+
///
|
|
84
|
+
/// # Examples
|
|
85
|
+
///
|
|
86
|
+
/// ```ignore
|
|
87
|
+
/// use spikard_core::di::FactoryDependency;
|
|
88
|
+
/// use std::sync::Arc;
|
|
89
|
+
///
|
|
90
|
+
/// let factory = FactoryDependency::builder("my_dep")
|
|
91
|
+
/// .factory(|_req, _data, _resolved| {
|
|
92
|
+
/// Box::pin(async {
|
|
93
|
+
/// Ok(Arc::new(100i32) as Arc<dyn std::any::Any + Send + Sync>)
|
|
94
|
+
/// })
|
|
95
|
+
/// })
|
|
96
|
+
/// .build();
|
|
97
|
+
/// ```
|
|
98
|
+
pub fn builder(key: impl Into<String>) -> FactoryDependencyBuilder {
|
|
99
|
+
FactoryDependencyBuilder::new(key)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl Dependency for FactoryDependency {
|
|
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
|
+
// Call the factory function
|
|
111
|
+
(self.factory)(request, request_data, resolved)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn key(&self) -> &str {
|
|
115
|
+
&self.key
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
fn depends_on(&self) -> Vec<String> {
|
|
119
|
+
self.dependencies.clone()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn cacheable(&self) -> bool {
|
|
123
|
+
self.cacheable
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
fn singleton(&self) -> bool {
|
|
127
|
+
self.singleton
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
impl std::fmt::Debug for FactoryDependency {
|
|
132
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
133
|
+
f.debug_struct("FactoryDependency")
|
|
134
|
+
.field("key", &self.key)
|
|
135
|
+
.field("dependencies", &self.dependencies)
|
|
136
|
+
.field("cacheable", &self.cacheable)
|
|
137
|
+
.field("singleton", &self.singleton)
|
|
138
|
+
.finish()
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Builder for constructing factory dependencies
|
|
143
|
+
///
|
|
144
|
+
/// Provides a fluent API for configuring factory dependencies with optional
|
|
145
|
+
/// settings like dependencies, caching, and singleton behavior.
|
|
146
|
+
///
|
|
147
|
+
/// # Examples
|
|
148
|
+
///
|
|
149
|
+
/// ```ignore
|
|
150
|
+
/// use spikard_core::di::FactoryDependency;
|
|
151
|
+
/// use std::sync::Arc;
|
|
152
|
+
///
|
|
153
|
+
/// // Factory with dependencies
|
|
154
|
+
/// let factory = FactoryDependency::builder("service")
|
|
155
|
+
/// .depends_on(vec!["database".to_string(), "cache".to_string()])
|
|
156
|
+
/// .factory(|_req, _data, resolved| {
|
|
157
|
+
/// Box::pin(async move {
|
|
158
|
+
/// // Access other dependencies
|
|
159
|
+
/// let _db: Option<Arc<String>> = resolved.get("database");
|
|
160
|
+
/// let _cache: Option<Arc<String>> = resolved.get("cache");
|
|
161
|
+
///
|
|
162
|
+
/// Ok(Arc::new("service".to_string()) as Arc<dyn std::any::Any + Send + Sync>)
|
|
163
|
+
/// })
|
|
164
|
+
/// })
|
|
165
|
+
/// .cacheable(true)
|
|
166
|
+
/// .build();
|
|
167
|
+
/// ```
|
|
168
|
+
pub struct FactoryDependencyBuilder {
|
|
169
|
+
key: String,
|
|
170
|
+
factory: Option<Arc<FactoryFn>>,
|
|
171
|
+
dependencies: Vec<String>,
|
|
172
|
+
cacheable: bool,
|
|
173
|
+
singleton: bool,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
impl FactoryDependencyBuilder {
|
|
177
|
+
/// Create a new builder
|
|
178
|
+
///
|
|
179
|
+
/// # Arguments
|
|
180
|
+
///
|
|
181
|
+
/// * `key` - The unique key for this dependency
|
|
182
|
+
fn new(key: impl Into<String>) -> Self {
|
|
183
|
+
Self {
|
|
184
|
+
key: key.into(),
|
|
185
|
+
factory: None,
|
|
186
|
+
dependencies: Vec::new(),
|
|
187
|
+
cacheable: false,
|
|
188
|
+
singleton: false,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// Set the factory function
|
|
193
|
+
///
|
|
194
|
+
/// # Arguments
|
|
195
|
+
///
|
|
196
|
+
/// * `factory` - A function that creates the dependency value
|
|
197
|
+
///
|
|
198
|
+
/// # Examples
|
|
199
|
+
///
|
|
200
|
+
/// ```ignore
|
|
201
|
+
/// use spikard_core::di::FactoryDependency;
|
|
202
|
+
/// use std::sync::Arc;
|
|
203
|
+
///
|
|
204
|
+
/// let factory = FactoryDependency::builder("timestamp")
|
|
205
|
+
/// .factory(|_req, _data, _resolved| {
|
|
206
|
+
/// Box::pin(async {
|
|
207
|
+
/// let now = std::time::SystemTime::now();
|
|
208
|
+
/// Ok(Arc::new(now) as Arc<dyn std::any::Any + Send + Sync>)
|
|
209
|
+
/// })
|
|
210
|
+
/// })
|
|
211
|
+
/// .build();
|
|
212
|
+
/// ```
|
|
213
|
+
pub fn factory<F>(mut self, factory: F) -> Self
|
|
214
|
+
where
|
|
215
|
+
F: Fn(
|
|
216
|
+
&Request<()>,
|
|
217
|
+
&RequestData,
|
|
218
|
+
&ResolvedDependencies,
|
|
219
|
+
) -> BoxFuture<'static, Result<Arc<dyn Any + Send + Sync>, DependencyError>>
|
|
220
|
+
+ Send
|
|
221
|
+
+ Sync
|
|
222
|
+
+ 'static,
|
|
223
|
+
{
|
|
224
|
+
self.factory = Some(Arc::new(factory));
|
|
225
|
+
self
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/// Set the dependencies that this factory requires
|
|
229
|
+
///
|
|
230
|
+
/// # Arguments
|
|
231
|
+
///
|
|
232
|
+
/// * `dependencies` - List of dependency keys that must be resolved first
|
|
233
|
+
///
|
|
234
|
+
/// # Examples
|
|
235
|
+
///
|
|
236
|
+
/// ```ignore
|
|
237
|
+
/// use spikard_core::di::FactoryDependency;
|
|
238
|
+
/// use std::sync::Arc;
|
|
239
|
+
///
|
|
240
|
+
/// let factory = FactoryDependency::builder("service")
|
|
241
|
+
/// .depends_on(vec!["database".to_string()])
|
|
242
|
+
/// .factory(|_req, _data, _resolved| {
|
|
243
|
+
/// Box::pin(async {
|
|
244
|
+
/// Ok(Arc::new("service") as Arc<dyn std::any::Any + Send + Sync>)
|
|
245
|
+
/// })
|
|
246
|
+
/// })
|
|
247
|
+
/// .build();
|
|
248
|
+
/// ```
|
|
249
|
+
pub fn depends_on(mut self, dependencies: Vec<String>) -> Self {
|
|
250
|
+
self.dependencies = dependencies;
|
|
251
|
+
self
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// Set whether this dependency should be cached within a request
|
|
255
|
+
///
|
|
256
|
+
/// # Arguments
|
|
257
|
+
///
|
|
258
|
+
/// * `cacheable` - If true, resolves once per request
|
|
259
|
+
///
|
|
260
|
+
/// # Examples
|
|
261
|
+
///
|
|
262
|
+
/// ```ignore
|
|
263
|
+
/// use spikard_core::di::FactoryDependency;
|
|
264
|
+
/// use std::sync::Arc;
|
|
265
|
+
///
|
|
266
|
+
/// let factory = FactoryDependency::builder("request_id")
|
|
267
|
+
/// .factory(|_req, _data, _resolved| {
|
|
268
|
+
/// Box::pin(async {
|
|
269
|
+
/// let id = uuid::Uuid::new_v4().to_string();
|
|
270
|
+
/// Ok(Arc::new(id) as Arc<dyn std::any::Any + Send + Sync>)
|
|
271
|
+
/// })
|
|
272
|
+
/// })
|
|
273
|
+
/// .cacheable(true) // Same ID for all uses in one request
|
|
274
|
+
/// .build();
|
|
275
|
+
/// ```
|
|
276
|
+
pub fn cacheable(mut self, cacheable: bool) -> Self {
|
|
277
|
+
self.cacheable = cacheable;
|
|
278
|
+
self
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Set whether this dependency is a singleton (cached globally)
|
|
282
|
+
///
|
|
283
|
+
/// # Arguments
|
|
284
|
+
///
|
|
285
|
+
/// * `singleton` - If true, resolves once globally across all requests
|
|
286
|
+
///
|
|
287
|
+
/// # Examples
|
|
288
|
+
///
|
|
289
|
+
/// ```ignore
|
|
290
|
+
/// use spikard_core::di::FactoryDependency;
|
|
291
|
+
/// use std::sync::Arc;
|
|
292
|
+
///
|
|
293
|
+
/// let factory = FactoryDependency::builder("database_pool")
|
|
294
|
+
/// .factory(|_req, _data, _resolved| {
|
|
295
|
+
/// Box::pin(async {
|
|
296
|
+
/// // Expensive initialization
|
|
297
|
+
/// let pool = "DatabasePool::new()".to_string();
|
|
298
|
+
/// Ok(Arc::new(pool) as Arc<dyn std::any::Any + Send + Sync>)
|
|
299
|
+
/// })
|
|
300
|
+
/// })
|
|
301
|
+
/// .singleton(true) // Share across all requests
|
|
302
|
+
/// .build();
|
|
303
|
+
/// ```
|
|
304
|
+
pub fn singleton(mut self, singleton: bool) -> Self {
|
|
305
|
+
self.singleton = singleton;
|
|
306
|
+
self
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// Build the factory dependency
|
|
310
|
+
///
|
|
311
|
+
/// # Panics
|
|
312
|
+
///
|
|
313
|
+
/// Panics if the factory function was not set.
|
|
314
|
+
///
|
|
315
|
+
/// # Examples
|
|
316
|
+
///
|
|
317
|
+
/// ```ignore
|
|
318
|
+
/// use spikard_core::di::FactoryDependency;
|
|
319
|
+
/// use std::sync::Arc;
|
|
320
|
+
///
|
|
321
|
+
/// let factory = FactoryDependency::builder("my_dep")
|
|
322
|
+
/// .factory(|_req, _data, _resolved| {
|
|
323
|
+
/// Box::pin(async {
|
|
324
|
+
/// Ok(Arc::new(42i32) as Arc<dyn std::any::Any + Send + Sync>)
|
|
325
|
+
/// })
|
|
326
|
+
/// })
|
|
327
|
+
/// .build();
|
|
328
|
+
/// ```
|
|
329
|
+
#[must_use]
|
|
330
|
+
pub fn build(self) -> FactoryDependency {
|
|
331
|
+
FactoryDependency {
|
|
332
|
+
key: self.key.clone(),
|
|
333
|
+
factory: self
|
|
334
|
+
.factory
|
|
335
|
+
.unwrap_or_else(|| panic!("Factory function must be set for dependency '{}'", self.key)),
|
|
336
|
+
dependencies: self.dependencies,
|
|
337
|
+
cacheable: self.cacheable,
|
|
338
|
+
singleton: self.singleton,
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[cfg(test)]
|
|
344
|
+
mod tests {
|
|
345
|
+
use super::*;
|
|
346
|
+
use std::collections::HashMap;
|
|
347
|
+
use std::sync::atomic::{AtomicU32, Ordering};
|
|
348
|
+
|
|
349
|
+
fn make_request_data() -> RequestData {
|
|
350
|
+
RequestData {
|
|
351
|
+
path_params: Arc::new(HashMap::new()),
|
|
352
|
+
query_params: serde_json::Value::Null,
|
|
353
|
+
raw_query_params: Arc::new(HashMap::new()),
|
|
354
|
+
body: serde_json::Value::Null,
|
|
355
|
+
raw_body: None,
|
|
356
|
+
headers: Arc::new(HashMap::new()),
|
|
357
|
+
cookies: Arc::new(HashMap::new()),
|
|
358
|
+
method: "GET".to_string(),
|
|
359
|
+
path: "/".to_string(),
|
|
360
|
+
#[cfg(feature = "di")]
|
|
361
|
+
dependencies: None,
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
#[test]
|
|
366
|
+
fn test_builder_key() {
|
|
367
|
+
let factory = FactoryDependency::builder("test")
|
|
368
|
+
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(42i32) as Arc<dyn Any + Send + Sync>) }))
|
|
369
|
+
.build();
|
|
370
|
+
|
|
371
|
+
assert_eq!(factory.key(), "test");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
#[test]
|
|
375
|
+
fn test_builder_depends_on() {
|
|
376
|
+
let factory = FactoryDependency::builder("test")
|
|
377
|
+
.depends_on(vec!["dep1".to_string(), "dep2".to_string()])
|
|
378
|
+
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(42i32) as Arc<dyn Any + Send + Sync>) }))
|
|
379
|
+
.build();
|
|
380
|
+
|
|
381
|
+
assert_eq!(factory.depends_on(), vec!["dep1", "dep2"]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#[test]
|
|
385
|
+
fn test_builder_cacheable() {
|
|
386
|
+
let factory = FactoryDependency::builder("test")
|
|
387
|
+
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(42i32) as Arc<dyn Any + Send + Sync>) }))
|
|
388
|
+
.cacheable(true)
|
|
389
|
+
.build();
|
|
390
|
+
|
|
391
|
+
assert!(factory.cacheable());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#[test]
|
|
395
|
+
fn test_builder_singleton() {
|
|
396
|
+
let factory = FactoryDependency::builder("test")
|
|
397
|
+
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(42i32) as Arc<dyn Any + Send + Sync>) }))
|
|
398
|
+
.singleton(true)
|
|
399
|
+
.build();
|
|
400
|
+
|
|
401
|
+
assert!(factory.singleton());
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
#[tokio::test]
|
|
405
|
+
async fn test_factory_async() {
|
|
406
|
+
let factory = FactoryDependency::builder("async_value")
|
|
407
|
+
.factory(|_req, _data, _resolved| {
|
|
408
|
+
Box::pin(async {
|
|
409
|
+
// Simulate async work
|
|
410
|
+
tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
|
|
411
|
+
Ok(Arc::new(100i32) as Arc<dyn Any + Send + Sync>)
|
|
412
|
+
})
|
|
413
|
+
})
|
|
414
|
+
.build();
|
|
415
|
+
|
|
416
|
+
let request = Request::builder().body(()).unwrap();
|
|
417
|
+
let request_data = make_request_data();
|
|
418
|
+
let resolved = ResolvedDependencies::new();
|
|
419
|
+
|
|
420
|
+
let result = factory.resolve(&request, &request_data, &resolved).await;
|
|
421
|
+
assert!(result.is_ok());
|
|
422
|
+
|
|
423
|
+
let value: Arc<i32> = result.unwrap().downcast().unwrap();
|
|
424
|
+
assert_eq!(*value, 100);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#[tokio::test]
|
|
428
|
+
async fn test_factory_depends_on() {
|
|
429
|
+
// Create resolved dependencies with a value
|
|
430
|
+
let mut resolved = ResolvedDependencies::new();
|
|
431
|
+
resolved.insert("config".to_string(), Arc::new("test_config".to_string()));
|
|
432
|
+
|
|
433
|
+
let factory = FactoryDependency::builder("service")
|
|
434
|
+
.depends_on(vec!["config".to_string()])
|
|
435
|
+
.factory(|_req, _data, resolved| {
|
|
436
|
+
let resolved = resolved.clone();
|
|
437
|
+
Box::pin(async move {
|
|
438
|
+
// Access the config dependency
|
|
439
|
+
let config: Option<Arc<String>> = resolved.get("config");
|
|
440
|
+
let config_value = config.map(|c| (*c).clone()).unwrap_or_default();
|
|
441
|
+
|
|
442
|
+
Ok(Arc::new(format!("Service using {}", config_value)) as Arc<dyn Any + Send + Sync>)
|
|
443
|
+
})
|
|
444
|
+
})
|
|
445
|
+
.build();
|
|
446
|
+
|
|
447
|
+
let request = Request::builder().body(()).unwrap();
|
|
448
|
+
let request_data = make_request_data();
|
|
449
|
+
|
|
450
|
+
let result = factory.resolve(&request, &request_data, &resolved).await;
|
|
451
|
+
assert!(result.is_ok());
|
|
452
|
+
|
|
453
|
+
let value: Arc<String> = result.unwrap().downcast().unwrap();
|
|
454
|
+
assert_eq!(*value, "Service using test_config");
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
#[tokio::test]
|
|
458
|
+
async fn test_factory_request_data() {
|
|
459
|
+
let factory = FactoryDependency::builder("user_agent")
|
|
460
|
+
.factory(|_req, request_data, _resolved| {
|
|
461
|
+
let ua = request_data
|
|
462
|
+
.headers
|
|
463
|
+
.get("user-agent")
|
|
464
|
+
.cloned()
|
|
465
|
+
.unwrap_or_else(|| "unknown".to_string());
|
|
466
|
+
|
|
467
|
+
Box::pin(async move { Ok(Arc::new(ua) as Arc<dyn Any + Send + Sync>) })
|
|
468
|
+
})
|
|
469
|
+
.build();
|
|
470
|
+
|
|
471
|
+
let mut headers = HashMap::new();
|
|
472
|
+
headers.insert("user-agent".to_string(), "test-agent/1.0".to_string());
|
|
473
|
+
|
|
474
|
+
let request_data = RequestData {
|
|
475
|
+
headers: Arc::new(headers),
|
|
476
|
+
..make_request_data()
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
let request = Request::builder().body(()).unwrap();
|
|
480
|
+
let resolved = ResolvedDependencies::new();
|
|
481
|
+
|
|
482
|
+
let result = factory.resolve(&request, &request_data, &resolved).await;
|
|
483
|
+
assert!(result.is_ok());
|
|
484
|
+
|
|
485
|
+
let value: Arc<String> = result.unwrap().downcast().unwrap();
|
|
486
|
+
assert_eq!(*value, "test-agent/1.0");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
#[tokio::test]
|
|
490
|
+
async fn test_factory_call_count() {
|
|
491
|
+
let call_count = Arc::new(AtomicU32::new(0));
|
|
492
|
+
|
|
493
|
+
let call_count_clone = Arc::clone(&call_count);
|
|
494
|
+
let factory = FactoryDependency::builder("counter")
|
|
495
|
+
.factory(move |_req, _data, _resolved| {
|
|
496
|
+
let count = Arc::clone(&call_count_clone);
|
|
497
|
+
Box::pin(async move {
|
|
498
|
+
let current = count.fetch_add(1, Ordering::SeqCst);
|
|
499
|
+
Ok(Arc::new(current) as Arc<dyn Any + Send + Sync>)
|
|
500
|
+
})
|
|
501
|
+
})
|
|
502
|
+
.build();
|
|
503
|
+
|
|
504
|
+
let request = Request::builder().body(()).unwrap();
|
|
505
|
+
let request_data = make_request_data();
|
|
506
|
+
let resolved = ResolvedDependencies::new();
|
|
507
|
+
|
|
508
|
+
// Call factory multiple times
|
|
509
|
+
for i in 0..3 {
|
|
510
|
+
let result = factory.resolve(&request, &request_data, &resolved).await;
|
|
511
|
+
let value: Arc<u32> = result.unwrap().downcast().unwrap();
|
|
512
|
+
assert_eq!(*value, i);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
assert_eq!(call_count.load(Ordering::SeqCst), 3);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
#[test]
|
|
519
|
+
fn test_debug() {
|
|
520
|
+
let factory = FactoryDependency::builder("test")
|
|
521
|
+
.depends_on(vec!["dep1".to_string()])
|
|
522
|
+
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(42i32) as Arc<dyn Any + Send + Sync>) }))
|
|
523
|
+
.cacheable(true)
|
|
524
|
+
.singleton(false)
|
|
525
|
+
.build();
|
|
526
|
+
|
|
527
|
+
let debug_str = format!("{:?}", factory);
|
|
528
|
+
assert!(debug_str.contains("FactoryDependency"));
|
|
529
|
+
assert!(debug_str.contains("test"));
|
|
530
|
+
assert!(debug_str.contains("dep1"));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#[test]
|
|
534
|
+
#[should_panic(expected = "Factory function must be set")]
|
|
535
|
+
fn test_builder_without_factory() {
|
|
536
|
+
let _factory = FactoryDependency::builder("test").build();
|
|
537
|
+
}
|
|
538
|
+
}
|