spikard 0.12.0 → 0.15.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/Steepfile +6 -0
- data/ext/spikard_rb/extconf.rb +1 -2
- data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +897 -451
- data/ext/spikard_rb/native/Cargo.toml +24 -0
- data/ext/spikard_rb/src/lib.rs +5366 -3
- data/lib/spikard/native.rb +86 -0
- data/lib/spikard/version.rb +6 -1
- data/lib/spikard.rb +8 -45
- data/lib/spikard_rb.so +0 -0
- data/sig/types.rbs +427 -0
- metadata +14 -242
- data/LICENSE +0 -1
- data/README.md +0 -267
- data/ext/spikard_rb/Cargo.toml +0 -17
- data/lib/spikard/app.rb +0 -428
- data/lib/spikard/background.rb +0 -58
- data/lib/spikard/config.rb +0 -506
- data/lib/spikard/converters.rb +0 -13
- data/lib/spikard/grpc.rb +0 -182
- data/lib/spikard/handler_wrapper.rb +0 -113
- data/lib/spikard/provide.rb +0 -214
- data/lib/spikard/response.rb +0 -173
- data/lib/spikard/schema.rb +0 -243
- data/lib/spikard/sse.rb +0 -111
- data/lib/spikard/streaming_response.rb +0 -44
- data/lib/spikard/testing.rb +0 -432
- data/lib/spikard/upload_file.rb +0 -131
- data/lib/spikard/websocket.rb +0 -59
- data/sig/spikard.rbs +0 -719
- data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -80
- data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
- data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
- data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
- data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
- data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
- data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
- data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
- data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
- data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
- data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
- data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
- data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
- data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
- data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
- data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
- data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
- data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
- data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
- data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
- data/vendor/crates/spikard-core/Cargo.toml +0 -60
- data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
- data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
- data/vendor/crates/spikard-core/src/debug.rs +0 -127
- data/vendor/crates/spikard-core/src/di/container.rs +0 -702
- 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 -507
- data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
- data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
- data/vendor/crates/spikard-core/src/di/value.rs +0 -282
- data/vendor/crates/spikard-core/src/errors.rs +0 -72
- data/vendor/crates/spikard-core/src/http.rs +0 -492
- data/vendor/crates/spikard-core/src/lib.rs +0 -29
- data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
- data/vendor/crates/spikard-core/src/metadata.rs +0 -378
- data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
- data/vendor/crates/spikard-core/src/problem.rs +0 -358
- data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
- data/vendor/crates/spikard-core/src/router.rs +0 -530
- data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
- data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
- data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
- data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
- data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
- data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
- data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
- data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
- data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
- data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
- data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
- data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
- data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
- data/vendor/crates/spikard-http/Cargo.toml +0 -87
- data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
- data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
- data/vendor/crates/spikard-http/src/auth.rs +0 -301
- data/vendor/crates/spikard-http/src/background.rs +0 -1860
- 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 -1026
- data/vendor/crates/spikard-http/src/debug.rs +0 -128
- data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
- data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -469
- data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1122
- data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -434
- data/vendor/crates/spikard-http/src/grpc/service.rs +0 -622
- data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
- data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
- data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
- data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
- data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
- data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
- data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -58
- data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
- data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
- data/vendor/crates/spikard-http/src/lib.rs +0 -548
- data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
- data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
- data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
- data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
- data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
- data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
- data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
- data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
- data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
- data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
- data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
- data/vendor/crates/spikard-http/src/response.rs +0 -720
- data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
- data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -858
- data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
- data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
- data/vendor/crates/spikard-http/src/server/mod.rs +0 -1649
- data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
- data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
- data/vendor/crates/spikard-http/src/sse.rs +0 -1409
- data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
- data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
- data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -787
- data/vendor/crates/spikard-http/src/testing.rs +0 -617
- data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
- data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
- data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
- data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
- data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
- data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
- data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
- data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
- data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
- data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
- data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
- data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
- data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
- data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
- data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
- data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
- data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -974
- data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
- data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
- data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
- data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
- data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
- data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -314
- data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -200
- data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
- data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
- data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
- data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
- data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
- data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
- data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -421
- data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
- data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
- data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
- data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
- data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
- data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
- data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
- data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
- data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
- data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
- data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
- data/vendor/crates/spikard-rb/Cargo.toml +0 -68
- data/vendor/crates/spikard-rb/build.rs +0 -200
- data/vendor/crates/spikard-rb/src/background.rs +0 -63
- data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
- data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
- data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
- data/vendor/crates/spikard-rb/src/di/mod.rs +0 -375
- data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -834
- data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
- data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
- data/vendor/crates/spikard-rb/src/handler.rs +0 -699
- data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
- data/vendor/crates/spikard-rb/src/lib.rs +0 -2264
- data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -303
- data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
- data/vendor/crates/spikard-rb/src/request.rs +0 -439
- data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
- data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -344
- data/vendor/crates/spikard-rb/src/server.rs +0 -307
- data/vendor/crates/spikard-rb/src/sse.rs +0 -231
- data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
- data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
- data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
- data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
- data/vendor/crates/spikard-rb/src/websocket.rs +0 -475
- data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -25
- data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
|
@@ -1,507 +0,0 @@
|
|
|
1
|
-
//! Dependency graph with topological sorting and cycle detection
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides the `DependencyGraph` type which manages the dependency
|
|
4
|
-
//! relationships between registered dependencies, detects cycles, and calculates
|
|
5
|
-
//! the optimal batched resolution order.
|
|
6
|
-
|
|
7
|
-
use super::error::DependencyError;
|
|
8
|
-
use std::collections::{HashMap, HashSet, VecDeque};
|
|
9
|
-
|
|
10
|
-
/// Dependency graph for managing dependency relationships
|
|
11
|
-
///
|
|
12
|
-
/// The graph tracks which dependencies depend on which other dependencies,
|
|
13
|
-
/// and provides algorithms for:
|
|
14
|
-
/// - Cycle detection (preventing circular dependencies)
|
|
15
|
-
/// - Topological sorting (determining resolution order)
|
|
16
|
-
/// - Batch calculation (enabling parallel resolution)
|
|
17
|
-
///
|
|
18
|
-
/// # Examples
|
|
19
|
-
///
|
|
20
|
-
/// ```ignore
|
|
21
|
-
/// use spikard_core::di::DependencyGraph;
|
|
22
|
-
///
|
|
23
|
-
/// let mut graph = DependencyGraph::new();
|
|
24
|
-
///
|
|
25
|
-
/// // Add dependencies
|
|
26
|
-
/// graph.add_dependency("config", vec![]).unwrap();
|
|
27
|
-
/// graph.add_dependency("database", vec!["config".to_string()]).unwrap();
|
|
28
|
-
/// graph.add_dependency("cache", vec!["config".to_string()]).unwrap();
|
|
29
|
-
/// graph.add_dependency("service", vec!["database".to_string(), "cache".to_string()]).unwrap();
|
|
30
|
-
///
|
|
31
|
-
/// // Calculate batches for parallel resolution
|
|
32
|
-
/// let batches = graph.calculate_batches(&[
|
|
33
|
-
/// "config".to_string(),
|
|
34
|
-
/// "database".to_string(),
|
|
35
|
-
/// "cache".to_string(),
|
|
36
|
-
/// "service".to_string(),
|
|
37
|
-
/// ]).unwrap();
|
|
38
|
-
///
|
|
39
|
-
/// // Batch 1: config (no dependencies)
|
|
40
|
-
/// // Batch 2: database, cache (both depend only on config, can run in parallel)
|
|
41
|
-
/// // Batch 3: service (depends on database and cache)
|
|
42
|
-
/// assert_eq!(batches.len(), 3);
|
|
43
|
-
/// ```
|
|
44
|
-
#[derive(Debug, Clone, Default)]
|
|
45
|
-
pub struct DependencyGraph {
|
|
46
|
-
/// Adjacency list: key -> list of dependencies it depends on
|
|
47
|
-
graph: HashMap<String, Vec<String>>,
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
impl DependencyGraph {
|
|
51
|
-
/// Create a new empty dependency graph
|
|
52
|
-
///
|
|
53
|
-
/// # Examples
|
|
54
|
-
///
|
|
55
|
-
/// ```ignore
|
|
56
|
-
/// use spikard_core::di::DependencyGraph;
|
|
57
|
-
///
|
|
58
|
-
/// let graph = DependencyGraph::new();
|
|
59
|
-
/// ```
|
|
60
|
-
#[must_use]
|
|
61
|
-
pub fn new() -> Self {
|
|
62
|
-
Self { graph: HashMap::new() }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/// Add a dependency and its dependencies to the graph
|
|
66
|
-
///
|
|
67
|
-
/// This will check for cycles before adding. If adding this dependency
|
|
68
|
-
/// would create a cycle, it returns an error.
|
|
69
|
-
///
|
|
70
|
-
/// # Arguments
|
|
71
|
-
///
|
|
72
|
-
/// * `key` - The unique key for this dependency
|
|
73
|
-
/// * `depends_on` - List of dependency keys that this depends on
|
|
74
|
-
///
|
|
75
|
-
/// # Errors
|
|
76
|
-
///
|
|
77
|
-
/// Returns `DependencyError::CircularDependency` if adding this dependency
|
|
78
|
-
/// would create a cycle in the graph.
|
|
79
|
-
///
|
|
80
|
-
/// Returns `DependencyError::DuplicateKey` if a dependency with this key
|
|
81
|
-
/// already exists.
|
|
82
|
-
///
|
|
83
|
-
/// # Examples
|
|
84
|
-
///
|
|
85
|
-
/// ```ignore
|
|
86
|
-
/// use spikard_core::di::DependencyGraph;
|
|
87
|
-
///
|
|
88
|
-
/// let mut graph = DependencyGraph::new();
|
|
89
|
-
///
|
|
90
|
-
/// // Simple dependency chain
|
|
91
|
-
/// graph.add_dependency("a", vec![]).unwrap();
|
|
92
|
-
/// graph.add_dependency("b", vec!["a".to_string()]).unwrap();
|
|
93
|
-
///
|
|
94
|
-
/// // This would create a cycle: a -> b -> a
|
|
95
|
-
/// let result = graph.add_dependency("a", vec!["b".to_string()]);
|
|
96
|
-
/// assert!(result.is_err());
|
|
97
|
-
/// ```
|
|
98
|
-
pub fn add_dependency(&mut self, key: &str, depends_on: Vec<String>) -> Result<(), DependencyError> {
|
|
99
|
-
if self.graph.contains_key(key) {
|
|
100
|
-
return Err(DependencyError::DuplicateKey { key: key.to_string() });
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
self.graph.insert(key.to_string(), depends_on);
|
|
104
|
-
Ok(())
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/// Check if adding a new dependency would create a cycle
|
|
108
|
-
///
|
|
109
|
-
/// Uses depth-first search to detect cycles in the graph if the new
|
|
110
|
-
/// dependency were added.
|
|
111
|
-
///
|
|
112
|
-
/// # Arguments
|
|
113
|
-
///
|
|
114
|
-
/// * `new_key` - The key of the dependency to potentially add
|
|
115
|
-
/// * `new_deps` - The dependencies that the new dependency would depend on
|
|
116
|
-
///
|
|
117
|
-
/// # Returns
|
|
118
|
-
///
|
|
119
|
-
/// `true` if adding this dependency would create a cycle, `false` otherwise.
|
|
120
|
-
///
|
|
121
|
-
/// # Examples
|
|
122
|
-
///
|
|
123
|
-
/// ```ignore
|
|
124
|
-
/// use spikard_core::di::DependencyGraph;
|
|
125
|
-
///
|
|
126
|
-
/// let mut graph = DependencyGraph::new();
|
|
127
|
-
/// graph.add_dependency("a", vec!["b".to_string()]).unwrap();
|
|
128
|
-
/// graph.add_dependency("b", vec!["c".to_string()]).unwrap();
|
|
129
|
-
///
|
|
130
|
-
/// // Adding c -> a would create a cycle
|
|
131
|
-
/// assert!(graph.has_cycle_with("c", &["a".to_string()]));
|
|
132
|
-
///
|
|
133
|
-
/// // Adding c -> [] would not
|
|
134
|
-
/// assert!(!graph.has_cycle_with("c", &[]));
|
|
135
|
-
/// ```
|
|
136
|
-
#[must_use]
|
|
137
|
-
pub fn has_cycle_with(&self, new_key: &str, new_deps: &[String]) -> bool {
|
|
138
|
-
let mut temp_graph = self.graph.clone();
|
|
139
|
-
temp_graph.insert(new_key.to_string(), new_deps.to_vec());
|
|
140
|
-
|
|
141
|
-
let mut visited = HashSet::new();
|
|
142
|
-
let mut rec_stack = HashSet::new();
|
|
143
|
-
|
|
144
|
-
for key in temp_graph.keys() {
|
|
145
|
-
if !visited.contains(key) && Self::has_cycle_dfs(key, &temp_graph, &mut visited, &mut rec_stack) {
|
|
146
|
-
return true;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
false
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/// Depth-first search for cycle detection
|
|
154
|
-
///
|
|
155
|
-
/// # Arguments
|
|
156
|
-
///
|
|
157
|
-
/// * `node` - Current node being visited
|
|
158
|
-
/// * `graph` - The graph to search
|
|
159
|
-
/// * `visited` - Set of all visited nodes
|
|
160
|
-
/// * `rec_stack` - Set of nodes in the current recursion stack
|
|
161
|
-
///
|
|
162
|
-
/// # Returns
|
|
163
|
-
///
|
|
164
|
-
/// `true` if a cycle is detected, `false` otherwise.
|
|
165
|
-
fn has_cycle_dfs(
|
|
166
|
-
node: &str,
|
|
167
|
-
graph: &HashMap<String, Vec<String>>,
|
|
168
|
-
visited: &mut HashSet<String>,
|
|
169
|
-
rec_stack: &mut HashSet<String>,
|
|
170
|
-
) -> bool {
|
|
171
|
-
visited.insert(node.to_string());
|
|
172
|
-
rec_stack.insert(node.to_string());
|
|
173
|
-
|
|
174
|
-
if let Some(deps) = graph.get(node) {
|
|
175
|
-
for dep in deps {
|
|
176
|
-
if !visited.contains(dep) {
|
|
177
|
-
if Self::has_cycle_dfs(dep, graph, visited, rec_stack) {
|
|
178
|
-
return true;
|
|
179
|
-
}
|
|
180
|
-
} else if rec_stack.contains(dep) {
|
|
181
|
-
return true;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
rec_stack.remove(node);
|
|
187
|
-
false
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/// Calculate batches of dependencies that can be resolved in parallel
|
|
191
|
-
///
|
|
192
|
-
/// Uses topological sorting with Kahn's algorithm to determine the order
|
|
193
|
-
/// in which dependencies should be resolved. Dependencies with no unresolved
|
|
194
|
-
/// dependencies can be resolved in parallel (same batch).
|
|
195
|
-
///
|
|
196
|
-
/// # Arguments
|
|
197
|
-
///
|
|
198
|
-
/// * `keys` - The dependency keys to resolve
|
|
199
|
-
///
|
|
200
|
-
/// # Returns
|
|
201
|
-
///
|
|
202
|
-
/// A vector of batches, where each batch is a set of dependency keys that
|
|
203
|
-
/// can be resolved in parallel. Batches must be executed sequentially.
|
|
204
|
-
///
|
|
205
|
-
/// # Errors
|
|
206
|
-
///
|
|
207
|
-
/// Returns `DependencyError::CircularDependency` if the graph contains a cycle.
|
|
208
|
-
/// Returns `DependencyError::NotFound` if a requested dependency doesn't exist.
|
|
209
|
-
///
|
|
210
|
-
/// # Examples
|
|
211
|
-
///
|
|
212
|
-
/// ```ignore
|
|
213
|
-
/// use spikard_core::di::DependencyGraph;
|
|
214
|
-
///
|
|
215
|
-
/// let mut graph = DependencyGraph::new();
|
|
216
|
-
/// graph.add_dependency("a", vec![]).unwrap();
|
|
217
|
-
/// graph.add_dependency("b", vec![]).unwrap();
|
|
218
|
-
/// graph.add_dependency("c", vec!["a".to_string(), "b".to_string()]).unwrap();
|
|
219
|
-
///
|
|
220
|
-
/// let batches = graph.calculate_batches(&[
|
|
221
|
-
/// "a".to_string(),
|
|
222
|
-
/// "b".to_string(),
|
|
223
|
-
/// "c".to_string(),
|
|
224
|
-
/// ]).unwrap();
|
|
225
|
-
///
|
|
226
|
-
/// // Batch 1: a and b (no dependencies, can run in parallel)
|
|
227
|
-
/// assert_eq!(batches[0].len(), 2);
|
|
228
|
-
/// assert!(batches[0].contains("a"));
|
|
229
|
-
/// assert!(batches[0].contains("b"));
|
|
230
|
-
///
|
|
231
|
-
/// // Batch 2: c (depends on a and b)
|
|
232
|
-
/// assert_eq!(batches[1].len(), 1);
|
|
233
|
-
/// assert!(batches[1].contains("c"));
|
|
234
|
-
/// ```
|
|
235
|
-
pub fn calculate_batches(&self, keys: &[String]) -> Result<Vec<HashSet<String>>, DependencyError> {
|
|
236
|
-
let mut subgraph = HashMap::new();
|
|
237
|
-
let mut to_visit: VecDeque<String> = keys.iter().cloned().collect();
|
|
238
|
-
let mut visited = HashSet::new();
|
|
239
|
-
|
|
240
|
-
while let Some(key) = to_visit.pop_front() {
|
|
241
|
-
if visited.contains(&key) {
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
visited.insert(key.clone());
|
|
245
|
-
|
|
246
|
-
if let Some(deps) = self.graph.get(&key) {
|
|
247
|
-
subgraph.insert(key.clone(), deps.clone());
|
|
248
|
-
for dep in deps {
|
|
249
|
-
to_visit.push_back(dep.clone());
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
subgraph.insert(key.clone(), vec![]);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
let mut in_degree: HashMap<String, usize> = HashMap::new();
|
|
257
|
-
for key in subgraph.keys() {
|
|
258
|
-
in_degree.entry(key.clone()).or_insert(0);
|
|
259
|
-
}
|
|
260
|
-
for deps in subgraph.values() {
|
|
261
|
-
for dep in deps {
|
|
262
|
-
*in_degree.entry(dep.clone()).or_insert(0) += 1;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
let mut batches = Vec::new();
|
|
267
|
-
let mut queue: VecDeque<String> = in_degree
|
|
268
|
-
.iter()
|
|
269
|
-
.filter(|&(_, °ree)| degree == 0)
|
|
270
|
-
.map(|(key, _)| key.clone())
|
|
271
|
-
.collect();
|
|
272
|
-
|
|
273
|
-
let mut processed = 0;
|
|
274
|
-
let total = subgraph.len();
|
|
275
|
-
|
|
276
|
-
while !queue.is_empty() {
|
|
277
|
-
let mut batch = HashSet::new();
|
|
278
|
-
|
|
279
|
-
let batch_size = queue.len();
|
|
280
|
-
for _ in 0..batch_size {
|
|
281
|
-
if let Some(node) = queue.pop_front() {
|
|
282
|
-
batch.insert(node.clone());
|
|
283
|
-
processed += 1;
|
|
284
|
-
|
|
285
|
-
if let Some(deps) = subgraph.get(&node) {
|
|
286
|
-
for dep in deps {
|
|
287
|
-
if let Some(degree) = in_degree.get_mut(dep) {
|
|
288
|
-
*degree -= 1;
|
|
289
|
-
if *degree == 0 {
|
|
290
|
-
queue.push_back(dep.clone());
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if !batch.is_empty() {
|
|
299
|
-
batches.push(batch);
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if processed < total {
|
|
304
|
-
let unprocessed: Vec<String> = subgraph
|
|
305
|
-
.keys()
|
|
306
|
-
.filter(|k| in_degree.get(*k).is_some_and(|&d| d > 0))
|
|
307
|
-
.cloned()
|
|
308
|
-
.collect();
|
|
309
|
-
|
|
310
|
-
if let Some(start) = unprocessed.first() {
|
|
311
|
-
let mut cycle = vec![start.clone()];
|
|
312
|
-
let mut current = start;
|
|
313
|
-
let mut visited_in_path = HashSet::new();
|
|
314
|
-
visited_in_path.insert(start.clone());
|
|
315
|
-
|
|
316
|
-
while let Some(deps) = subgraph.get(current) {
|
|
317
|
-
if let Some(next) = deps.iter().find(|d| unprocessed.contains(d)) {
|
|
318
|
-
if visited_in_path.contains(next) {
|
|
319
|
-
cycle.push(next.clone());
|
|
320
|
-
break;
|
|
321
|
-
}
|
|
322
|
-
cycle.push(next.clone());
|
|
323
|
-
visited_in_path.insert(next.clone());
|
|
324
|
-
current = next;
|
|
325
|
-
} else {
|
|
326
|
-
break;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
if cycle.len() > 1
|
|
331
|
-
&& let Some((min_idx, _)) = cycle[..cycle.len() - 1].iter().enumerate().min_by_key(|(_, s)| *s)
|
|
332
|
-
{
|
|
333
|
-
cycle.rotate_left(min_idx);
|
|
334
|
-
if let Some(first) = cycle.first().cloned()
|
|
335
|
-
&& let Some(last) = cycle.last_mut()
|
|
336
|
-
{
|
|
337
|
-
*last = first;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
return Err(DependencyError::CircularDependency { cycle });
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return Err(DependencyError::CircularDependency { cycle: unprocessed });
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
batches.reverse();
|
|
348
|
-
|
|
349
|
-
Ok(batches)
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
#[cfg(test)]
|
|
354
|
-
mod tests {
|
|
355
|
-
use super::*;
|
|
356
|
-
|
|
357
|
-
#[test]
|
|
358
|
-
fn test_new() {
|
|
359
|
-
let graph = DependencyGraph::new();
|
|
360
|
-
assert_eq!(graph.graph.len(), 0);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
#[test]
|
|
364
|
-
fn test_add_dependency_simple() {
|
|
365
|
-
let mut graph = DependencyGraph::new();
|
|
366
|
-
assert!(graph.add_dependency("a", vec![]).is_ok());
|
|
367
|
-
assert!(graph.graph.contains_key("a"));
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
#[test]
|
|
371
|
-
fn test_add_dependency_duplicate() {
|
|
372
|
-
let mut graph = DependencyGraph::new();
|
|
373
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
374
|
-
let result = graph.add_dependency("a", vec![]);
|
|
375
|
-
assert!(matches!(result, Err(DependencyError::DuplicateKey { .. })));
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
#[test]
|
|
379
|
-
fn test_has_cycle_simple() {
|
|
380
|
-
let mut graph = DependencyGraph::new();
|
|
381
|
-
graph.add_dependency("a", vec!["b".to_string()]).unwrap();
|
|
382
|
-
|
|
383
|
-
assert!(graph.has_cycle_with("b", &["a".to_string()]));
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
#[test]
|
|
387
|
-
fn test_has_cycle_complex() {
|
|
388
|
-
let mut graph = DependencyGraph::new();
|
|
389
|
-
graph.add_dependency("a", vec!["b".to_string()]).unwrap();
|
|
390
|
-
graph.add_dependency("b", vec!["c".to_string()]).unwrap();
|
|
391
|
-
|
|
392
|
-
assert!(graph.has_cycle_with("c", &["a".to_string()]));
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
#[test]
|
|
396
|
-
fn test_has_cycle_self_loop() {
|
|
397
|
-
let graph = DependencyGraph::new();
|
|
398
|
-
|
|
399
|
-
assert!(graph.has_cycle_with("a", &["a".to_string()]));
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
#[test]
|
|
403
|
-
fn test_no_cycle() {
|
|
404
|
-
let mut graph = DependencyGraph::new();
|
|
405
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
406
|
-
graph.add_dependency("b", vec!["a".to_string()]).unwrap();
|
|
407
|
-
|
|
408
|
-
assert!(!graph.has_cycle_with("c", &["a".to_string()]));
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
#[test]
|
|
412
|
-
fn test_calculate_batches_simple() {
|
|
413
|
-
let mut graph = DependencyGraph::new();
|
|
414
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
415
|
-
|
|
416
|
-
let batches = graph.calculate_batches(&["a".to_string()]).unwrap();
|
|
417
|
-
assert_eq!(batches.len(), 1);
|
|
418
|
-
assert!(batches[0].contains("a"));
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
#[test]
|
|
422
|
-
fn test_calculate_batches_linear() {
|
|
423
|
-
let mut graph = DependencyGraph::new();
|
|
424
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
425
|
-
graph.add_dependency("b", vec!["a".to_string()]).unwrap();
|
|
426
|
-
graph.add_dependency("c", vec!["b".to_string()]).unwrap();
|
|
427
|
-
|
|
428
|
-
let batches = graph
|
|
429
|
-
.calculate_batches(&["a".to_string(), "b".to_string(), "c".to_string()])
|
|
430
|
-
.unwrap();
|
|
431
|
-
|
|
432
|
-
assert_eq!(batches.len(), 3);
|
|
433
|
-
assert!(batches[0].contains("a"));
|
|
434
|
-
assert!(batches[1].contains("b"));
|
|
435
|
-
assert!(batches[2].contains("c"));
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
#[test]
|
|
439
|
-
fn test_calculate_batches_parallel() {
|
|
440
|
-
let mut graph = DependencyGraph::new();
|
|
441
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
442
|
-
graph.add_dependency("b", vec![]).unwrap();
|
|
443
|
-
graph
|
|
444
|
-
.add_dependency("c", vec!["a".to_string(), "b".to_string()])
|
|
445
|
-
.unwrap();
|
|
446
|
-
|
|
447
|
-
let batches = graph
|
|
448
|
-
.calculate_batches(&["a".to_string(), "b".to_string(), "c".to_string()])
|
|
449
|
-
.unwrap();
|
|
450
|
-
|
|
451
|
-
assert_eq!(batches.len(), 2);
|
|
452
|
-
assert_eq!(batches[0].len(), 2);
|
|
453
|
-
assert!(batches[0].contains("a"));
|
|
454
|
-
assert!(batches[0].contains("b"));
|
|
455
|
-
assert!(batches[1].contains("c"));
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
#[test]
|
|
459
|
-
fn test_calculate_batches_nested() {
|
|
460
|
-
let mut graph = DependencyGraph::new();
|
|
461
|
-
graph.add_dependency("config", vec![]).unwrap();
|
|
462
|
-
graph.add_dependency("database", vec!["config".to_string()]).unwrap();
|
|
463
|
-
graph.add_dependency("cache", vec!["config".to_string()]).unwrap();
|
|
464
|
-
graph
|
|
465
|
-
.add_dependency("service", vec!["database".to_string(), "cache".to_string()])
|
|
466
|
-
.unwrap();
|
|
467
|
-
|
|
468
|
-
let batches = graph
|
|
469
|
-
.calculate_batches(&[
|
|
470
|
-
"config".to_string(),
|
|
471
|
-
"database".to_string(),
|
|
472
|
-
"cache".to_string(),
|
|
473
|
-
"service".to_string(),
|
|
474
|
-
])
|
|
475
|
-
.unwrap();
|
|
476
|
-
|
|
477
|
-
assert_eq!(batches.len(), 3);
|
|
478
|
-
assert!(batches[0].contains("config"));
|
|
479
|
-
assert_eq!(batches[1].len(), 2);
|
|
480
|
-
assert!(batches[1].contains("database"));
|
|
481
|
-
assert!(batches[1].contains("cache"));
|
|
482
|
-
assert!(batches[2].contains("service"));
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
#[test]
|
|
486
|
-
fn test_calculate_batches_partial() {
|
|
487
|
-
let mut graph = DependencyGraph::new();
|
|
488
|
-
graph.add_dependency("a", vec![]).unwrap();
|
|
489
|
-
graph.add_dependency("b", vec!["a".to_string()]).unwrap();
|
|
490
|
-
graph.add_dependency("c", vec!["a".to_string()]).unwrap();
|
|
491
|
-
|
|
492
|
-
let batches = graph.calculate_batches(&["b".to_string()]).unwrap();
|
|
493
|
-
|
|
494
|
-
assert_eq!(batches.len(), 2);
|
|
495
|
-
assert!(batches[0].contains("a"));
|
|
496
|
-
assert!(batches[1].contains("b"));
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
#[test]
|
|
500
|
-
fn test_calculate_batches_missing_dependency() {
|
|
501
|
-
let mut graph = DependencyGraph::new();
|
|
502
|
-
graph.add_dependency("a", vec!["missing".to_string()]).unwrap();
|
|
503
|
-
|
|
504
|
-
let batches = graph.calculate_batches(&["a".to_string()]).unwrap();
|
|
505
|
-
assert!(!batches.is_empty());
|
|
506
|
-
}
|
|
507
|
-
}
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
//! Dependency Injection system for Spikard
|
|
2
|
-
//!
|
|
3
|
-
//! This module provides a comprehensive dependency injection system with:
|
|
4
|
-
//!
|
|
5
|
-
//! - **Type-safe dependency resolution**: Dependencies are stored as `Arc<dyn Any>` but
|
|
6
|
-
//! can be retrieved with type safety using `ResolvedDependencies::get<T>()`
|
|
7
|
-
//! - **Async resolution**: All dependencies can perform async operations during resolution
|
|
8
|
-
//! - **Batched parallel resolution**: Dependencies with no interdependencies are resolved
|
|
9
|
-
//! in parallel using topological sorting
|
|
10
|
-
//! - **Multiple caching strategies**:
|
|
11
|
-
//! - Singleton: Resolved once globally, cached forever
|
|
12
|
-
//! - Per-request cacheable: Resolved once per request
|
|
13
|
-
//! - Non-cacheable: Resolved every time
|
|
14
|
-
//! - **Cycle detection**: Circular dependencies are detected at registration time
|
|
15
|
-
//! - **Cleanup support**: Generator-pattern dependencies can register cleanup tasks
|
|
16
|
-
//!
|
|
17
|
-
//! # Architecture
|
|
18
|
-
//!
|
|
19
|
-
//! The DI system is built on several core components:
|
|
20
|
-
//!
|
|
21
|
-
//! - [`Dependency`] trait: The core abstraction that all dependencies implement
|
|
22
|
-
//! - [`DependencyContainer`]: Manages registration and resolution
|
|
23
|
-
//! - [`ResolvedDependencies`]: Stores resolved dependencies with type-safe access
|
|
24
|
-
//! - [`DependencyGraph`]: Handles topological sorting and cycle detection
|
|
25
|
-
//! - [`ValueDependency<T>`]: Simple static value dependencies
|
|
26
|
-
//! - [`FactoryDependency`]: Dynamic factory-based dependencies
|
|
27
|
-
//!
|
|
28
|
-
//! # Examples
|
|
29
|
-
//!
|
|
30
|
-
//! ## Basic Usage
|
|
31
|
-
//!
|
|
32
|
-
//! ```ignore
|
|
33
|
-
//! use spikard_core::di::{DependencyContainer, ValueDependency, FactoryDependency};
|
|
34
|
-
//! use std::sync::Arc;
|
|
35
|
-
//!
|
|
36
|
-
//! # tokio_test::block_on(async {
|
|
37
|
-
//! let mut container = DependencyContainer::new();
|
|
38
|
-
//!
|
|
39
|
-
//! // Register a simple value dependency
|
|
40
|
-
//! let config = ValueDependency::new("database_url", "postgresql://localhost/mydb");
|
|
41
|
-
//! container.register("database_url".to_string(), Arc::new(config)).unwrap();
|
|
42
|
-
//!
|
|
43
|
-
//! // Register a factory dependency that depends on the config
|
|
44
|
-
//! let pool = FactoryDependency::builder("db_pool")
|
|
45
|
-
//! .depends_on(vec!["database_url".to_string()])
|
|
46
|
-
//! .factory(|_req, _data, resolved| {
|
|
47
|
-
//! Box::pin(async move {
|
|
48
|
-
//! let url: Arc<String> = resolved.get("database_url").unwrap();
|
|
49
|
-
//! let pool = format!("Pool connected to {}", *url);
|
|
50
|
-
//! Ok(Arc::new(pool) as Arc<dyn std::any::Any + Send + Sync>)
|
|
51
|
-
//! })
|
|
52
|
-
//! })
|
|
53
|
-
//! .singleton(true) // Share across all requests
|
|
54
|
-
//! .build();
|
|
55
|
-
//! container.register("db_pool".to_string(), Arc::new(pool)).unwrap();
|
|
56
|
-
//!
|
|
57
|
-
//! // Resolve for a handler
|
|
58
|
-
//! use http::Request;
|
|
59
|
-
//! use crate::request_data::RequestData;
|
|
60
|
-
//! use std::collections::HashMap;
|
|
61
|
-
//!
|
|
62
|
-
//! let request = Request::builder().body(()).unwrap();
|
|
63
|
-
//! let request_data = RequestData {
|
|
64
|
-
//! path_params: Arc::new(HashMap::new()),
|
|
65
|
-
//! query_params: serde_json::Value::Null,
|
|
66
|
-
//! validated_params: None,
|
|
67
|
-
//! raw_query_params: Arc::new(HashMap::new()),
|
|
68
|
-
//! body: serde_json::Value::Null,
|
|
69
|
-
//! raw_body: None,
|
|
70
|
-
//! headers: Arc::new(HashMap::new()),
|
|
71
|
-
//! cookies: Arc::new(HashMap::new()),
|
|
72
|
-
//! method: "GET".to_string(),
|
|
73
|
-
//! path: "/".to_string(),
|
|
74
|
-
//! };
|
|
75
|
-
//!
|
|
76
|
-
//! let resolved = container
|
|
77
|
-
//! .resolve_for_handler(&["db_pool".to_string()], &request, &request_data)
|
|
78
|
-
//! .await
|
|
79
|
-
//! .unwrap();
|
|
80
|
-
//!
|
|
81
|
-
//! let pool: Option<Arc<String>> = resolved.get("db_pool");
|
|
82
|
-
//! assert!(pool.is_some());
|
|
83
|
-
//! # });
|
|
84
|
-
//! ```
|
|
85
|
-
//!
|
|
86
|
-
//! ## Request-Scoped Dependencies
|
|
87
|
-
//!
|
|
88
|
-
//! ```ignore
|
|
89
|
-
//! use spikard_core::di::{DependencyContainer, FactoryDependency};
|
|
90
|
-
//! use std::sync::Arc;
|
|
91
|
-
//!
|
|
92
|
-
//! # tokio_test::block_on(async {
|
|
93
|
-
//! let mut container = DependencyContainer::new();
|
|
94
|
-
//!
|
|
95
|
-
//! // Create a request-scoped dependency (e.g., request ID)
|
|
96
|
-
//! let request_id = FactoryDependency::builder("request_id")
|
|
97
|
-
//! .factory(|_req, _data, _resolved| {
|
|
98
|
-
//! Box::pin(async {
|
|
99
|
-
//! let id = uuid::Uuid::new_v4().to_string();
|
|
100
|
-
//! Ok(Arc::new(id) as Arc<dyn std::any::Any + Send + Sync>)
|
|
101
|
-
//! })
|
|
102
|
-
//! })
|
|
103
|
-
//! .cacheable(true) // Same ID throughout the request
|
|
104
|
-
//! .build();
|
|
105
|
-
//!
|
|
106
|
-
//! container.register("request_id".to_string(), Arc::new(request_id)).unwrap();
|
|
107
|
-
//! # });
|
|
108
|
-
//! ```
|
|
109
|
-
//!
|
|
110
|
-
//! ## Accessing Request Data
|
|
111
|
-
//!
|
|
112
|
-
//! ```ignore
|
|
113
|
-
//! use spikard_core::di::{DependencyContainer, FactoryDependency};
|
|
114
|
-
//! use std::sync::Arc;
|
|
115
|
-
//!
|
|
116
|
-
//! # tokio_test::block_on(async {
|
|
117
|
-
//! let mut container = DependencyContainer::new();
|
|
118
|
-
//!
|
|
119
|
-
//! // Access headers, query params, etc.
|
|
120
|
-
//! let user_agent = FactoryDependency::builder("user_agent")
|
|
121
|
-
//! .factory(|_req, request_data, _resolved| {
|
|
122
|
-
//! let ua = request_data.headers
|
|
123
|
-
//! .get("user-agent")
|
|
124
|
-
//! .cloned()
|
|
125
|
-
//! .unwrap_or_else(|| "unknown".to_string());
|
|
126
|
-
//!
|
|
127
|
-
//! Box::pin(async move {
|
|
128
|
-
//! Ok(Arc::new(ua) as Arc<dyn std::any::Any + Send + Sync>)
|
|
129
|
-
//! })
|
|
130
|
-
//! })
|
|
131
|
-
//! .build();
|
|
132
|
-
//!
|
|
133
|
-
//! container.register("user_agent".to_string(), Arc::new(user_agent)).unwrap();
|
|
134
|
-
//! # });
|
|
135
|
-
//! ```
|
|
136
|
-
//!
|
|
137
|
-
//! ## Cleanup Tasks
|
|
138
|
-
//!
|
|
139
|
-
//! ```ignore
|
|
140
|
-
//! use spikard_core::di::ResolvedDependencies;
|
|
141
|
-
//! use std::sync::Arc;
|
|
142
|
-
//!
|
|
143
|
-
//! # tokio_test::block_on(async {
|
|
144
|
-
//! let mut resolved = ResolvedDependencies::new();
|
|
145
|
-
//!
|
|
146
|
-
//! // Add a dependency with cleanup
|
|
147
|
-
//! resolved.insert("connection".to_string(), Arc::new("DB Connection"));
|
|
148
|
-
//!
|
|
149
|
-
//! // Register cleanup task
|
|
150
|
-
//! resolved.add_cleanup_task(Box::new(|| {
|
|
151
|
-
//! Box::pin(async {
|
|
152
|
-
//! println!("Closing database connection");
|
|
153
|
-
//! })
|
|
154
|
-
//! }));
|
|
155
|
-
//!
|
|
156
|
-
//! // Cleanup runs when resolved is dropped (or explicitly)
|
|
157
|
-
//! resolved.cleanup().await;
|
|
158
|
-
//! # });
|
|
159
|
-
//! ```
|
|
160
|
-
//!
|
|
161
|
-
//! # Performance
|
|
162
|
-
//!
|
|
163
|
-
//! The DI system is designed for high performance:
|
|
164
|
-
//!
|
|
165
|
-
//! - **Parallel resolution**: Independent dependencies are resolved concurrently
|
|
166
|
-
//! - **Efficient caching**: Singleton and per-request caching minimize redundant work
|
|
167
|
-
//! - **Arc-based sharing**: Values are reference-counted, not cloned
|
|
168
|
-
//! - **Zero-cost abstractions**: Type erasure has minimal overhead
|
|
169
|
-
//!
|
|
170
|
-
//! # Thread Safety
|
|
171
|
-
//!
|
|
172
|
-
//! All components are thread-safe:
|
|
173
|
-
//!
|
|
174
|
-
//! - `DependencyContainer` can be shared with `Arc<DependencyContainer>`
|
|
175
|
-
//! - Singleton cache uses `RwLock` for concurrent access
|
|
176
|
-
//! - All dependencies must be `Send + Sync`
|
|
177
|
-
|
|
178
|
-
mod container;
|
|
179
|
-
mod dependency;
|
|
180
|
-
mod error;
|
|
181
|
-
mod factory;
|
|
182
|
-
mod graph;
|
|
183
|
-
mod resolved;
|
|
184
|
-
mod value;
|
|
185
|
-
|
|
186
|
-
pub use container::DependencyContainer;
|
|
187
|
-
pub use dependency::Dependency;
|
|
188
|
-
pub use error::DependencyError;
|
|
189
|
-
pub use factory::{FactoryDependency, FactoryDependencyBuilder, FactoryFn};
|
|
190
|
-
pub use graph::DependencyGraph;
|
|
191
|
-
pub use resolved::ResolvedDependencies;
|
|
192
|
-
pub use value::ValueDependency;
|