spikard 0.2.5 → 0.3.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 +659 -626
- data/ext/spikard_rb/Cargo.toml +17 -17
- data/ext/spikard_rb/extconf.rb +10 -10
- data/ext/spikard_rb/src/lib.rs +6 -6
- data/lib/spikard/app.rb +386 -374
- data/lib/spikard/background.rb +27 -27
- data/lib/spikard/config.rb +396 -396
- data/lib/spikard/converters.rb +13 -85
- data/lib/spikard/handler_wrapper.rb +113 -116
- data/lib/spikard/provide.rb +214 -228
- data/lib/spikard/response.rb +173 -109
- data/lib/spikard/schema.rb +243 -243
- data/lib/spikard/sse.rb +111 -111
- data/lib/spikard/streaming_response.rb +44 -21
- data/lib/spikard/testing.rb +221 -221
- 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 +360 -349
- data/vendor/bundle/ruby/3.4.0/gems/rake-compiler-dock-1.10.0/build/buildkitd.toml +2 -0
- metadata +5 -85
- data/vendor/crates/spikard-core/Cargo.toml +0 -40
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -133
- data/vendor/crates/spikard-core/src/debug.rs +0 -63
- data/vendor/crates/spikard-core/src/di/container.rs +0 -726
- data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
- data/vendor/crates/spikard-core/src/di/error.rs +0 -118
- data/vendor/crates/spikard-core/src/di/factory.rs +0 -538
- data/vendor/crates/spikard-core/src/di/graph.rs +0 -545
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -411
- data/vendor/crates/spikard-core/src/di/value.rs +0 -283
- data/vendor/crates/spikard-core/src/http.rs +0 -153
- data/vendor/crates/spikard-core/src/lib.rs +0 -28
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -422
- data/vendor/crates/spikard-core/src/parameters.rs +0 -719
- data/vendor/crates/spikard-core/src/problem.rs +0 -310
- data/vendor/crates/spikard-core/src/request_data.rs +0 -189
- data/vendor/crates/spikard-core/src/router.rs +0 -249
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -183
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -304
- data/vendor/crates/spikard-core/src/validation.rs +0 -699
- data/vendor/crates/spikard-http/Cargo.toml +0 -58
- data/vendor/crates/spikard-http/src/auth.rs +0 -247
- data/vendor/crates/spikard-http/src/background.rs +0 -249
- data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
- data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
- data/vendor/crates/spikard-http/src/cors.rs +0 -490
- data/vendor/crates/spikard-http/src/debug.rs +0 -63
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -423
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -190
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -228
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -284
- data/vendor/crates/spikard-http/src/lib.rs +0 -529
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -149
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -428
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -285
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -86
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -147
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -287
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -190
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -308
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -195
- 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/query_parser.rs +0 -369
- data/vendor/crates/spikard-http/src/response.rs +0 -399
- 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/server/handler.rs +0 -80
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -98
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -805
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -119
- data/vendor/crates/spikard-http/src/sse.rs +0 -447
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -14
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -60
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -285
- data/vendor/crates/spikard-http/src/testing.rs +0 -377
- 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-http/src/websocket.rs +0 -324
- data/vendor/crates/spikard-rb/Cargo.toml +0 -42
- data/vendor/crates/spikard-rb/build.rs +0 -8
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config.rs +0 -294
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -392
- data/vendor/crates/spikard-rb/src/di.rs +0 -409
- data/vendor/crates/spikard-rb/src/handler.rs +0 -534
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2020
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -267
- data/vendor/crates/spikard-rb/src/server.rs +0 -283
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/test_client.rs +0 -404
- data/vendor/crates/spikard-rb/src/test_sse.rs +0 -143
- data/vendor/crates/spikard-rb/src/test_websocket.rs +0 -221
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -233
- /data/vendor/bundle/ruby/{3.3.0 → 3.4.0}/gems/diff-lcs-1.6.2/mise.toml +0 -0
|
@@ -1,726 +0,0 @@
|
|
|
1
|
-
//! Dependency injection container
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides the main `DependencyContainer` which manages dependency
|
|
4
|
-
//! registration, resolution, and caching.
|
|
5
|
-
|
|
6
|
-
use super::dependency::Dependency;
|
|
7
|
-
use super::error::DependencyError;
|
|
8
|
-
use super::graph::DependencyGraph;
|
|
9
|
-
use super::resolved::ResolvedDependencies;
|
|
10
|
-
use crate::request_data::RequestData;
|
|
11
|
-
use http::Request;
|
|
12
|
-
use indexmap::IndexMap;
|
|
13
|
-
use std::any::Any;
|
|
14
|
-
use std::collections::HashMap;
|
|
15
|
-
use std::sync::Arc;
|
|
16
|
-
use tokio::sync::RwLock;
|
|
17
|
-
|
|
18
|
-
/// Main dependency injection container
|
|
19
|
-
///
|
|
20
|
-
/// The container manages:
|
|
21
|
-
/// - Registration of dependencies with cycle detection
|
|
22
|
-
/// - Batched parallel resolution using topological sorting
|
|
23
|
-
/// - Singleton caching (global across all requests)
|
|
24
|
-
/// - Request-scoped caching (within a single request)
|
|
25
|
-
///
|
|
26
|
-
/// # Thread Safety
|
|
27
|
-
///
|
|
28
|
-
/// The container is thread-safe and can be shared across multiple threads
|
|
29
|
-
/// using `Arc<DependencyContainer>`.
|
|
30
|
-
///
|
|
31
|
-
/// # Examples
|
|
32
|
-
///
|
|
33
|
-
/// ```ignore
|
|
34
|
-
/// use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
35
|
-
/// use std::sync::Arc;
|
|
36
|
-
///
|
|
37
|
-
/// # tokio_test::block_on(async {
|
|
38
|
-
/// let mut container = DependencyContainer::new();
|
|
39
|
-
///
|
|
40
|
-
/// // Register a simple value dependency
|
|
41
|
-
/// let config = ValueDependency::new("port", 8080u16);
|
|
42
|
-
/// container.register("port".to_string(), Arc::new(config)).unwrap();
|
|
43
|
-
///
|
|
44
|
-
/// // Resolve dependencies for a handler
|
|
45
|
-
/// use http::Request;
|
|
46
|
-
/// use crate::request_data::RequestData;
|
|
47
|
-
/// use std::collections::HashMap;
|
|
48
|
-
///
|
|
49
|
-
/// let request = Request::builder().body(()).unwrap();
|
|
50
|
-
/// let request_data = RequestData {
|
|
51
|
-
/// path_params: Arc::new(HashMap::new()),
|
|
52
|
-
/// query_params: serde_json::Value::Null,
|
|
53
|
-
/// raw_query_params: Arc::new(HashMap::new()),
|
|
54
|
-
/// body: serde_json::Value::Null,
|
|
55
|
-
/// raw_body: None,
|
|
56
|
-
/// headers: Arc::new(HashMap::new()),
|
|
57
|
-
/// cookies: Arc::new(HashMap::new()),
|
|
58
|
-
/// method: "GET".to_string(),
|
|
59
|
-
/// path: "/".to_string(),
|
|
60
|
-
/// };
|
|
61
|
-
///
|
|
62
|
-
/// let resolved = container
|
|
63
|
-
/// .resolve_for_handler(&["port".to_string()], &request, &request_data)
|
|
64
|
-
/// .await
|
|
65
|
-
/// .unwrap();
|
|
66
|
-
///
|
|
67
|
-
/// let port: Option<Arc<u16>> = resolved.get("port");
|
|
68
|
-
/// assert_eq!(port.map(|p| *p), Some(8080));
|
|
69
|
-
/// # });
|
|
70
|
-
/// ```
|
|
71
|
-
pub struct DependencyContainer {
|
|
72
|
-
/// Registered dependencies by key (preserves insertion order)
|
|
73
|
-
dependencies: IndexMap<String, Arc<dyn Dependency>>,
|
|
74
|
-
/// Dependency graph for topological sorting and cycle detection
|
|
75
|
-
dependency_graph: DependencyGraph,
|
|
76
|
-
/// Global singleton cache
|
|
77
|
-
singleton_cache: Arc<RwLock<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
impl DependencyContainer {
|
|
81
|
-
/// Create a new empty dependency container
|
|
82
|
-
///
|
|
83
|
-
/// # Examples
|
|
84
|
-
///
|
|
85
|
-
/// ```ignore
|
|
86
|
-
/// use spikard_core::di::DependencyContainer;
|
|
87
|
-
///
|
|
88
|
-
/// let container = DependencyContainer::new();
|
|
89
|
-
/// ```
|
|
90
|
-
#[must_use]
|
|
91
|
-
pub fn new() -> Self {
|
|
92
|
-
Self {
|
|
93
|
-
dependencies: IndexMap::new(),
|
|
94
|
-
dependency_graph: DependencyGraph::new(),
|
|
95
|
-
singleton_cache: Arc::new(RwLock::new(HashMap::new())),
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/// Register a dependency in the container
|
|
100
|
-
///
|
|
101
|
-
/// This will validate that:
|
|
102
|
-
/// - The key is not already registered
|
|
103
|
-
/// - Adding this dependency won't create a circular dependency
|
|
104
|
-
///
|
|
105
|
-
/// # Arguments
|
|
106
|
-
///
|
|
107
|
-
/// * `key` - The unique key for this dependency
|
|
108
|
-
/// * `dep` - The dependency implementation
|
|
109
|
-
///
|
|
110
|
-
/// # Returns
|
|
111
|
-
///
|
|
112
|
-
/// Returns `&mut Self` for method chaining.
|
|
113
|
-
///
|
|
114
|
-
/// # Errors
|
|
115
|
-
///
|
|
116
|
-
/// - `DependencyError::DuplicateKey` if a dependency with this key exists
|
|
117
|
-
/// - `DependencyError::CircularDependency` if this would create a cycle
|
|
118
|
-
///
|
|
119
|
-
/// # Examples
|
|
120
|
-
///
|
|
121
|
-
/// ```ignore
|
|
122
|
-
/// use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
123
|
-
/// use std::sync::Arc;
|
|
124
|
-
///
|
|
125
|
-
/// let mut container = DependencyContainer::new();
|
|
126
|
-
///
|
|
127
|
-
/// let config = ValueDependency::new("config", "production".to_string());
|
|
128
|
-
/// container.register("config".to_string(), Arc::new(config)).unwrap();
|
|
129
|
-
/// ```
|
|
130
|
-
pub fn register(&mut self, key: String, dep: Arc<dyn Dependency>) -> Result<&mut Self, DependencyError> {
|
|
131
|
-
// Add to dependency graph (this checks for cycles and duplicates)
|
|
132
|
-
self.dependency_graph.add_dependency(&key, dep.depends_on())?;
|
|
133
|
-
|
|
134
|
-
// Store the dependency
|
|
135
|
-
self.dependencies.insert(key, dep);
|
|
136
|
-
|
|
137
|
-
Ok(self)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/// Resolve dependencies for a handler
|
|
141
|
-
///
|
|
142
|
-
/// This method:
|
|
143
|
-
/// 1. Calculates the optimal batched resolution order using topological sorting
|
|
144
|
-
/// 2. Resolves dependencies in batches (dependencies in the same batch run in parallel)
|
|
145
|
-
/// 3. Caches singleton dependencies globally
|
|
146
|
-
/// 4. Caches per-request dependencies within the returned `ResolvedDependencies`
|
|
147
|
-
///
|
|
148
|
-
/// # Arguments
|
|
149
|
-
///
|
|
150
|
-
/// * `deps` - The dependency keys needed by the handler
|
|
151
|
-
/// * `req` - The HTTP request being handled
|
|
152
|
-
/// * `data` - Extracted request data
|
|
153
|
-
///
|
|
154
|
-
/// # Returns
|
|
155
|
-
///
|
|
156
|
-
/// A `ResolvedDependencies` instance containing all resolved dependencies.
|
|
157
|
-
///
|
|
158
|
-
/// # Errors
|
|
159
|
-
///
|
|
160
|
-
/// - `DependencyError::NotFound` if a required dependency is not registered
|
|
161
|
-
/// - `DependencyError::CircularDependency` if there's a cycle in dependencies
|
|
162
|
-
/// - `DependencyError::ResolutionFailed` if a dependency fails to resolve
|
|
163
|
-
///
|
|
164
|
-
/// # Examples
|
|
165
|
-
///
|
|
166
|
-
/// ```ignore
|
|
167
|
-
/// use spikard_core::di::{DependencyContainer, ValueDependency, FactoryDependency};
|
|
168
|
-
/// use std::sync::Arc;
|
|
169
|
-
///
|
|
170
|
-
/// # tokio_test::block_on(async {
|
|
171
|
-
/// let mut container = DependencyContainer::new();
|
|
172
|
-
///
|
|
173
|
-
/// // Register dependencies
|
|
174
|
-
/// let config = ValueDependency::new("config", "production".to_string());
|
|
175
|
-
/// container.register("config".to_string(), Arc::new(config)).unwrap();
|
|
176
|
-
///
|
|
177
|
-
/// let db = FactoryDependency::builder("database")
|
|
178
|
-
/// .depends_on(vec!["config".to_string()])
|
|
179
|
-
/// .factory(|_req, _data, resolved| {
|
|
180
|
-
/// Box::pin(async move {
|
|
181
|
-
/// let config: Arc<String> = resolved.get("config").unwrap();
|
|
182
|
-
/// let db = format!("DB connected to {}", *config);
|
|
183
|
-
/// Ok(Arc::new(db) as Arc<dyn std::any::Any + Send + Sync>)
|
|
184
|
-
/// })
|
|
185
|
-
/// })
|
|
186
|
-
/// .build();
|
|
187
|
-
/// container.register("database".to_string(), Arc::new(db)).unwrap();
|
|
188
|
-
///
|
|
189
|
-
/// // Resolve for handler
|
|
190
|
-
/// use http::Request;
|
|
191
|
-
/// use crate::request_data::RequestData;
|
|
192
|
-
/// use std::collections::HashMap;
|
|
193
|
-
///
|
|
194
|
-
/// let request = Request::builder().body(()).unwrap();
|
|
195
|
-
/// let request_data = RequestData {
|
|
196
|
-
/// path_params: Arc::new(HashMap::new()),
|
|
197
|
-
/// query_params: serde_json::Value::Null,
|
|
198
|
-
/// raw_query_params: Arc::new(HashMap::new()),
|
|
199
|
-
/// body: serde_json::Value::Null,
|
|
200
|
-
/// raw_body: None,
|
|
201
|
-
/// headers: Arc::new(HashMap::new()),
|
|
202
|
-
/// cookies: Arc::new(HashMap::new()),
|
|
203
|
-
/// method: "GET".to_string(),
|
|
204
|
-
/// path: "/".to_string(),
|
|
205
|
-
/// };
|
|
206
|
-
///
|
|
207
|
-
/// let resolved = container
|
|
208
|
-
/// .resolve_for_handler(&["database".to_string()], &request, &request_data)
|
|
209
|
-
/// .await
|
|
210
|
-
/// .unwrap();
|
|
211
|
-
///
|
|
212
|
-
/// let db: Option<Arc<String>> = resolved.get("database");
|
|
213
|
-
/// assert!(db.is_some());
|
|
214
|
-
/// # });
|
|
215
|
-
/// ```
|
|
216
|
-
pub async fn resolve_for_handler(
|
|
217
|
-
&self,
|
|
218
|
-
deps: &[String],
|
|
219
|
-
req: &Request<()>,
|
|
220
|
-
data: &RequestData,
|
|
221
|
-
) -> Result<ResolvedDependencies, DependencyError> {
|
|
222
|
-
for key in deps {
|
|
223
|
-
if !self.dependencies.contains_key(key) {
|
|
224
|
-
return Err(DependencyError::NotFound { key: key.clone() });
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Calculate resolution batches
|
|
229
|
-
let batches = self.dependency_graph.calculate_batches(deps)?;
|
|
230
|
-
|
|
231
|
-
let mut resolved = ResolvedDependencies::new();
|
|
232
|
-
let mut request_cache: HashMap<String, Arc<dyn Any + Send + Sync>> = HashMap::new();
|
|
233
|
-
|
|
234
|
-
// Process each batch sequentially
|
|
235
|
-
for batch in batches {
|
|
236
|
-
// Sort keys within batch by registration order for deterministic resolution
|
|
237
|
-
// This ensures cleanup happens in a predictable reverse order
|
|
238
|
-
// NOTE: We resolve sequentially within each batch to ensure cleanup tasks
|
|
239
|
-
// are registered in a deterministic order (LIFO on cleanup)
|
|
240
|
-
let mut sorted_keys: Vec<_> = batch.iter().collect();
|
|
241
|
-
|
|
242
|
-
// Sort by insertion order (index in IndexMap) instead of alphabetically
|
|
243
|
-
sorted_keys.sort_by_key(|key| self.dependencies.get_index_of(*key).unwrap_or(usize::MAX));
|
|
244
|
-
|
|
245
|
-
for key in sorted_keys {
|
|
246
|
-
// Get the dependency
|
|
247
|
-
let dep = self
|
|
248
|
-
.dependencies
|
|
249
|
-
.get(key)
|
|
250
|
-
.ok_or_else(|| DependencyError::NotFound { key: key.clone() })?;
|
|
251
|
-
|
|
252
|
-
// Check singleton cache first
|
|
253
|
-
if dep.singleton() {
|
|
254
|
-
let cache = self.singleton_cache.read().await;
|
|
255
|
-
if let Some(cached) = cache.get(key) {
|
|
256
|
-
resolved.insert(key.clone(), Arc::clone(cached));
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Check request cache
|
|
262
|
-
if dep.cacheable()
|
|
263
|
-
&& let Some(cached) = request_cache.get(key)
|
|
264
|
-
{
|
|
265
|
-
resolved.insert(key.clone(), Arc::clone(cached));
|
|
266
|
-
continue;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Need to resolve - do it sequentially to preserve cleanup order
|
|
270
|
-
let result = dep.resolve(req, data, &resolved).await?;
|
|
271
|
-
|
|
272
|
-
// Store in appropriate cache
|
|
273
|
-
if dep.singleton() {
|
|
274
|
-
let mut cache = self.singleton_cache.write().await;
|
|
275
|
-
cache.insert(key.clone(), Arc::clone(&result));
|
|
276
|
-
} else if dep.cacheable() {
|
|
277
|
-
request_cache.insert(key.clone(), Arc::clone(&result));
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Always store in resolved
|
|
281
|
-
resolved.insert(key.clone(), result);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
Ok(resolved)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/// Get the number of registered dependencies
|
|
289
|
-
///
|
|
290
|
-
/// # Examples
|
|
291
|
-
///
|
|
292
|
-
/// ```ignore
|
|
293
|
-
/// use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
294
|
-
/// use std::sync::Arc;
|
|
295
|
-
///
|
|
296
|
-
/// let mut container = DependencyContainer::new();
|
|
297
|
-
/// assert_eq!(container.len(), 0);
|
|
298
|
-
///
|
|
299
|
-
/// let dep = ValueDependency::new("test", 42);
|
|
300
|
-
/// container.register("test".to_string(), Arc::new(dep)).unwrap();
|
|
301
|
-
/// assert_eq!(container.len(), 1);
|
|
302
|
-
/// ```
|
|
303
|
-
#[must_use]
|
|
304
|
-
pub fn len(&self) -> usize {
|
|
305
|
-
self.dependencies.len()
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/// Check if the container is empty
|
|
309
|
-
///
|
|
310
|
-
/// # Examples
|
|
311
|
-
///
|
|
312
|
-
/// ```ignore
|
|
313
|
-
/// use spikard_core::di::DependencyContainer;
|
|
314
|
-
///
|
|
315
|
-
/// let container = DependencyContainer::new();
|
|
316
|
-
/// assert!(container.is_empty());
|
|
317
|
-
/// ```
|
|
318
|
-
#[must_use]
|
|
319
|
-
pub fn is_empty(&self) -> bool {
|
|
320
|
-
self.dependencies.is_empty()
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/// Check if a dependency is registered
|
|
324
|
-
///
|
|
325
|
-
/// # Examples
|
|
326
|
-
///
|
|
327
|
-
/// ```ignore
|
|
328
|
-
/// use spikard_core::di::{DependencyContainer, ValueDependency};
|
|
329
|
-
/// use std::sync::Arc;
|
|
330
|
-
///
|
|
331
|
-
/// let mut container = DependencyContainer::new();
|
|
332
|
-
/// assert!(!container.contains("config"));
|
|
333
|
-
///
|
|
334
|
-
/// let dep = ValueDependency::new("config", "value");
|
|
335
|
-
/// container.register("config".to_string(), Arc::new(dep)).unwrap();
|
|
336
|
-
/// assert!(container.contains("config"));
|
|
337
|
-
/// ```
|
|
338
|
-
#[must_use]
|
|
339
|
-
pub fn contains(&self, key: &str) -> bool {
|
|
340
|
-
self.dependencies.contains_key(key)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/// Get the keys of all registered dependencies
|
|
344
|
-
#[must_use]
|
|
345
|
-
pub fn keys(&self) -> Vec<String> {
|
|
346
|
-
self.dependencies.keys().cloned().collect()
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/// Clear the singleton cache
|
|
350
|
-
///
|
|
351
|
-
/// This is useful for testing or when you need to force re-resolution
|
|
352
|
-
/// of singleton dependencies.
|
|
353
|
-
///
|
|
354
|
-
/// # Examples
|
|
355
|
-
///
|
|
356
|
-
/// ```ignore
|
|
357
|
-
/// use spikard_core::di::DependencyContainer;
|
|
358
|
-
///
|
|
359
|
-
/// # tokio_test::block_on(async {
|
|
360
|
-
/// let container = DependencyContainer::new();
|
|
361
|
-
/// container.clear_singleton_cache().await;
|
|
362
|
-
/// # });
|
|
363
|
-
/// ```
|
|
364
|
-
pub async fn clear_singleton_cache(&self) {
|
|
365
|
-
let mut cache = self.singleton_cache.write().await;
|
|
366
|
-
cache.clear();
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
impl Default for DependencyContainer {
|
|
371
|
-
fn default() -> Self {
|
|
372
|
-
Self::new()
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
impl std::fmt::Debug for DependencyContainer {
|
|
377
|
-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
378
|
-
f.debug_struct("DependencyContainer")
|
|
379
|
-
.field("dependencies", &self.dependencies.keys())
|
|
380
|
-
.finish()
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
#[cfg(test)]
|
|
385
|
-
mod tests {
|
|
386
|
-
use super::*;
|
|
387
|
-
use crate::di::{FactoryDependency, ValueDependency};
|
|
388
|
-
use std::collections::HashMap;
|
|
389
|
-
use std::sync::atomic::{AtomicU32, Ordering};
|
|
390
|
-
|
|
391
|
-
fn make_request() -> Request<()> {
|
|
392
|
-
Request::builder().body(()).unwrap()
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
fn make_request_data() -> RequestData {
|
|
396
|
-
RequestData {
|
|
397
|
-
path_params: Arc::new(HashMap::new()),
|
|
398
|
-
query_params: serde_json::Value::Null,
|
|
399
|
-
raw_query_params: Arc::new(HashMap::new()),
|
|
400
|
-
body: serde_json::Value::Null,
|
|
401
|
-
raw_body: None,
|
|
402
|
-
headers: Arc::new(HashMap::new()),
|
|
403
|
-
cookies: Arc::new(HashMap::new()),
|
|
404
|
-
method: "GET".to_string(),
|
|
405
|
-
path: "/".to_string(),
|
|
406
|
-
#[cfg(feature = "di")]
|
|
407
|
-
dependencies: None,
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
#[test]
|
|
412
|
-
fn test_new() {
|
|
413
|
-
let container = DependencyContainer::new();
|
|
414
|
-
assert!(container.is_empty());
|
|
415
|
-
assert_eq!(container.len(), 0);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
#[test]
|
|
419
|
-
fn test_register_simple() {
|
|
420
|
-
let mut container = DependencyContainer::new();
|
|
421
|
-
let dep = ValueDependency::new("test", 42i32);
|
|
422
|
-
|
|
423
|
-
assert!(container.register("test".to_string(), Arc::new(dep)).is_ok());
|
|
424
|
-
assert_eq!(container.len(), 1);
|
|
425
|
-
assert!(container.contains("test"));
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
#[test]
|
|
429
|
-
fn test_register_duplicate() {
|
|
430
|
-
let mut container = DependencyContainer::new();
|
|
431
|
-
let dep1 = ValueDependency::new("test", 42i32);
|
|
432
|
-
let dep2 = ValueDependency::new("test", 100i32);
|
|
433
|
-
|
|
434
|
-
container.register("test".to_string(), Arc::new(dep1)).unwrap();
|
|
435
|
-
let result = container.register("test".to_string(), Arc::new(dep2));
|
|
436
|
-
|
|
437
|
-
assert!(matches!(result, Err(DependencyError::DuplicateKey { .. })));
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
#[tokio::test]
|
|
441
|
-
async fn test_register_circular() {
|
|
442
|
-
let mut container = DependencyContainer::new();
|
|
443
|
-
|
|
444
|
-
let dep_a = FactoryDependency::builder("a")
|
|
445
|
-
.depends_on(vec!["b".to_string()])
|
|
446
|
-
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(1i32) as Arc<dyn Any + Send + Sync>) }))
|
|
447
|
-
.build();
|
|
448
|
-
|
|
449
|
-
let dep_b = FactoryDependency::builder("b")
|
|
450
|
-
.depends_on(vec!["a".to_string()])
|
|
451
|
-
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(2i32) as Arc<dyn Any + Send + Sync>) }))
|
|
452
|
-
.build();
|
|
453
|
-
|
|
454
|
-
container.register("a".to_string(), Arc::new(dep_a)).unwrap();
|
|
455
|
-
container.register("b".to_string(), Arc::new(dep_b)).unwrap();
|
|
456
|
-
|
|
457
|
-
let request = make_request();
|
|
458
|
-
let request_data = make_request_data();
|
|
459
|
-
let result = container
|
|
460
|
-
.resolve_for_handler(&["a".to_string()], &request, &request_data)
|
|
461
|
-
.await;
|
|
462
|
-
|
|
463
|
-
assert!(matches!(result, Err(DependencyError::CircularDependency { .. })));
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#[tokio::test]
|
|
467
|
-
async fn test_resolve_value() {
|
|
468
|
-
let mut container = DependencyContainer::new();
|
|
469
|
-
let dep = ValueDependency::new("answer", 42i32);
|
|
470
|
-
container.register("answer".to_string(), Arc::new(dep)).unwrap();
|
|
471
|
-
|
|
472
|
-
let request = make_request();
|
|
473
|
-
let request_data = make_request_data();
|
|
474
|
-
|
|
475
|
-
let resolved = container
|
|
476
|
-
.resolve_for_handler(&["answer".to_string()], &request, &request_data)
|
|
477
|
-
.await
|
|
478
|
-
.unwrap();
|
|
479
|
-
|
|
480
|
-
let value: Option<Arc<i32>> = resolved.get("answer");
|
|
481
|
-
assert_eq!(value.map(|v| *v), Some(42));
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
#[tokio::test]
|
|
485
|
-
async fn test_resolve_factory() {
|
|
486
|
-
let mut container = DependencyContainer::new();
|
|
487
|
-
|
|
488
|
-
let factory = FactoryDependency::builder("computed")
|
|
489
|
-
.factory(|_req, _data, _resolved| Box::pin(async { Ok(Arc::new(100i32) as Arc<dyn Any + Send + Sync>) }))
|
|
490
|
-
.build();
|
|
491
|
-
|
|
492
|
-
container.register("computed".to_string(), Arc::new(factory)).unwrap();
|
|
493
|
-
|
|
494
|
-
let request = make_request();
|
|
495
|
-
let request_data = make_request_data();
|
|
496
|
-
|
|
497
|
-
let resolved = container
|
|
498
|
-
.resolve_for_handler(&["computed".to_string()], &request, &request_data)
|
|
499
|
-
.await
|
|
500
|
-
.unwrap();
|
|
501
|
-
|
|
502
|
-
let value: Option<Arc<i32>> = resolved.get("computed");
|
|
503
|
-
assert_eq!(value.map(|v| *v), Some(100));
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
#[tokio::test]
|
|
507
|
-
async fn test_resolve_nested() {
|
|
508
|
-
let mut container = DependencyContainer::new();
|
|
509
|
-
|
|
510
|
-
// config (no dependencies)
|
|
511
|
-
let config = ValueDependency::new("config", "production".to_string());
|
|
512
|
-
container.register("config".to_string(), Arc::new(config)).unwrap();
|
|
513
|
-
|
|
514
|
-
// database (depends on config)
|
|
515
|
-
let database = FactoryDependency::builder("database")
|
|
516
|
-
.depends_on(vec!["config".to_string()])
|
|
517
|
-
.factory(|_req, _data, resolved| {
|
|
518
|
-
let resolved = resolved.clone();
|
|
519
|
-
Box::pin(async move {
|
|
520
|
-
let config: Arc<String> = resolved.get("config").unwrap();
|
|
521
|
-
let db = format!("DB:{}", *config);
|
|
522
|
-
Ok(Arc::new(db) as Arc<dyn Any + Send + Sync>)
|
|
523
|
-
})
|
|
524
|
-
})
|
|
525
|
-
.build();
|
|
526
|
-
container.register("database".to_string(), Arc::new(database)).unwrap();
|
|
527
|
-
|
|
528
|
-
let request = make_request();
|
|
529
|
-
let request_data = make_request_data();
|
|
530
|
-
|
|
531
|
-
let resolved = container
|
|
532
|
-
.resolve_for_handler(&["database".to_string()], &request, &request_data)
|
|
533
|
-
.await
|
|
534
|
-
.unwrap();
|
|
535
|
-
|
|
536
|
-
let db: Option<Arc<String>> = resolved.get("database");
|
|
537
|
-
assert_eq!(db.as_ref().map(|v| v.as_str()), Some("DB:production"));
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
#[tokio::test]
|
|
541
|
-
async fn test_resolve_batched() {
|
|
542
|
-
let mut container = DependencyContainer::new();
|
|
543
|
-
|
|
544
|
-
// Track resolution order
|
|
545
|
-
let counter = Arc::new(AtomicU32::new(0));
|
|
546
|
-
|
|
547
|
-
// config (no deps)
|
|
548
|
-
let counter1 = Arc::clone(&counter);
|
|
549
|
-
let config = FactoryDependency::builder("config")
|
|
550
|
-
.factory(move |_req, _data, _resolved| {
|
|
551
|
-
let c = Arc::clone(&counter1);
|
|
552
|
-
Box::pin(async move {
|
|
553
|
-
let order = c.fetch_add(1, Ordering::SeqCst);
|
|
554
|
-
Ok(Arc::new(order) as Arc<dyn Any + Send + Sync>)
|
|
555
|
-
})
|
|
556
|
-
})
|
|
557
|
-
.build();
|
|
558
|
-
container.register("config".to_string(), Arc::new(config)).unwrap();
|
|
559
|
-
|
|
560
|
-
// db and cache (both depend on config, can run in parallel)
|
|
561
|
-
let counter2 = Arc::clone(&counter);
|
|
562
|
-
let database = FactoryDependency::builder("database")
|
|
563
|
-
.depends_on(vec!["config".to_string()])
|
|
564
|
-
.factory(move |_req, _data, _resolved| {
|
|
565
|
-
let c = Arc::clone(&counter2);
|
|
566
|
-
Box::pin(async move {
|
|
567
|
-
let order = c.fetch_add(1, Ordering::SeqCst);
|
|
568
|
-
Ok(Arc::new(order) as Arc<dyn Any + Send + Sync>)
|
|
569
|
-
})
|
|
570
|
-
})
|
|
571
|
-
.build();
|
|
572
|
-
container.register("database".to_string(), Arc::new(database)).unwrap();
|
|
573
|
-
|
|
574
|
-
let counter3 = Arc::clone(&counter);
|
|
575
|
-
let cache = FactoryDependency::builder("cache")
|
|
576
|
-
.depends_on(vec!["config".to_string()])
|
|
577
|
-
.factory(move |_req, _data, _resolved| {
|
|
578
|
-
let c = Arc::clone(&counter3);
|
|
579
|
-
Box::pin(async move {
|
|
580
|
-
let order = c.fetch_add(1, Ordering::SeqCst);
|
|
581
|
-
Ok(Arc::new(order) as Arc<dyn Any + Send + Sync>)
|
|
582
|
-
})
|
|
583
|
-
})
|
|
584
|
-
.build();
|
|
585
|
-
container.register("cache".to_string(), Arc::new(cache)).unwrap();
|
|
586
|
-
|
|
587
|
-
let request = make_request();
|
|
588
|
-
let request_data = make_request_data();
|
|
589
|
-
|
|
590
|
-
let resolved = container
|
|
591
|
-
.resolve_for_handler(&["database".to_string(), "cache".to_string()], &request, &request_data)
|
|
592
|
-
.await
|
|
593
|
-
.unwrap();
|
|
594
|
-
|
|
595
|
-
// config should be resolved first (order 0)
|
|
596
|
-
let config_order: Arc<u32> = resolved.get("config").unwrap();
|
|
597
|
-
assert_eq!(*config_order, 0);
|
|
598
|
-
|
|
599
|
-
// db and cache should be resolved after config (order 1 and 2, in either order)
|
|
600
|
-
let db_order: Arc<u32> = resolved.get("database").unwrap();
|
|
601
|
-
let cache_order: Arc<u32> = resolved.get("cache").unwrap();
|
|
602
|
-
assert!(*db_order >= 1);
|
|
603
|
-
assert!(*cache_order >= 1);
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
#[tokio::test]
|
|
607
|
-
async fn test_singleton_cache() {
|
|
608
|
-
let mut container = DependencyContainer::new();
|
|
609
|
-
|
|
610
|
-
let counter = Arc::new(AtomicU32::new(0));
|
|
611
|
-
let counter_clone = Arc::clone(&counter);
|
|
612
|
-
|
|
613
|
-
let singleton = FactoryDependency::builder("singleton")
|
|
614
|
-
.singleton(true)
|
|
615
|
-
.factory(move |_req, _data, _resolved| {
|
|
616
|
-
let c = Arc::clone(&counter_clone);
|
|
617
|
-
Box::pin(async move {
|
|
618
|
-
let value = c.fetch_add(1, Ordering::SeqCst);
|
|
619
|
-
Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>)
|
|
620
|
-
})
|
|
621
|
-
})
|
|
622
|
-
.build();
|
|
623
|
-
|
|
624
|
-
container
|
|
625
|
-
.register("singleton".to_string(), Arc::new(singleton))
|
|
626
|
-
.unwrap();
|
|
627
|
-
|
|
628
|
-
let request = make_request();
|
|
629
|
-
let request_data = make_request_data();
|
|
630
|
-
|
|
631
|
-
// Resolve multiple times
|
|
632
|
-
for _ in 0..3 {
|
|
633
|
-
let resolved = container
|
|
634
|
-
.resolve_for_handler(&["singleton".to_string()], &request, &request_data)
|
|
635
|
-
.await
|
|
636
|
-
.unwrap();
|
|
637
|
-
|
|
638
|
-
let value: Arc<u32> = resolved.get("singleton").unwrap();
|
|
639
|
-
// Should always be 0 (resolved only once)
|
|
640
|
-
assert_eq!(*value, 0);
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Counter should only have been incremented once
|
|
644
|
-
assert_eq!(counter.load(Ordering::SeqCst), 1);
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
#[tokio::test]
|
|
648
|
-
async fn test_clear_singleton_cache() {
|
|
649
|
-
let mut container = DependencyContainer::new();
|
|
650
|
-
|
|
651
|
-
let counter = Arc::new(AtomicU32::new(0));
|
|
652
|
-
let counter_clone = Arc::clone(&counter);
|
|
653
|
-
|
|
654
|
-
let singleton = FactoryDependency::builder("singleton")
|
|
655
|
-
.singleton(true)
|
|
656
|
-
.factory(move |_req, _data, _resolved| {
|
|
657
|
-
let c = Arc::clone(&counter_clone);
|
|
658
|
-
Box::pin(async move {
|
|
659
|
-
let value = c.fetch_add(1, Ordering::SeqCst);
|
|
660
|
-
Ok(Arc::new(value) as Arc<dyn Any + Send + Sync>)
|
|
661
|
-
})
|
|
662
|
-
})
|
|
663
|
-
.build();
|
|
664
|
-
|
|
665
|
-
container
|
|
666
|
-
.register("singleton".to_string(), Arc::new(singleton))
|
|
667
|
-
.unwrap();
|
|
668
|
-
|
|
669
|
-
let request = make_request();
|
|
670
|
-
let request_data = make_request_data();
|
|
671
|
-
|
|
672
|
-
// First resolve
|
|
673
|
-
let resolved1 = container
|
|
674
|
-
.resolve_for_handler(&["singleton".to_string()], &request, &request_data)
|
|
675
|
-
.await
|
|
676
|
-
.unwrap();
|
|
677
|
-
let value1: Arc<u32> = resolved1.get("singleton").unwrap();
|
|
678
|
-
assert_eq!(*value1, 0);
|
|
679
|
-
|
|
680
|
-
// Clear cache
|
|
681
|
-
container.clear_singleton_cache().await;
|
|
682
|
-
|
|
683
|
-
// Second resolve should re-execute factory
|
|
684
|
-
let resolved2 = container
|
|
685
|
-
.resolve_for_handler(&["singleton".to_string()], &request, &request_data)
|
|
686
|
-
.await
|
|
687
|
-
.unwrap();
|
|
688
|
-
let value2: Arc<u32> = resolved2.get("singleton").unwrap();
|
|
689
|
-
assert_eq!(*value2, 1);
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
#[tokio::test]
|
|
693
|
-
async fn test_resolve_not_found() {
|
|
694
|
-
let container = DependencyContainer::new();
|
|
695
|
-
let request = make_request();
|
|
696
|
-
let request_data = make_request_data();
|
|
697
|
-
|
|
698
|
-
let result = container
|
|
699
|
-
.resolve_for_handler(&["missing".to_string()], &request, &request_data)
|
|
700
|
-
.await;
|
|
701
|
-
|
|
702
|
-
assert!(matches!(result, Err(DependencyError::NotFound { .. })));
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
#[test]
|
|
706
|
-
fn test_contains() {
|
|
707
|
-
let mut container = DependencyContainer::new();
|
|
708
|
-
assert!(!container.contains("test"));
|
|
709
|
-
|
|
710
|
-
let dep = ValueDependency::new("test", 42i32);
|
|
711
|
-
container.register("test".to_string(), Arc::new(dep)).unwrap();
|
|
712
|
-
|
|
713
|
-
assert!(container.contains("test"));
|
|
714
|
-
assert!(!container.contains("other"));
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
#[test]
|
|
718
|
-
fn test_debug() {
|
|
719
|
-
let mut container = DependencyContainer::new();
|
|
720
|
-
let dep = ValueDependency::new("test", 42i32);
|
|
721
|
-
container.register("test".to_string(), Arc::new(dep)).unwrap();
|
|
722
|
-
|
|
723
|
-
let debug_str = format!("{:?}", container);
|
|
724
|
-
assert!(debug_str.contains("DependencyContainer"));
|
|
725
|
-
}
|
|
726
|
-
}
|