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.
Files changed (206) hide show
  1. checksums.yaml +4 -4
  2. data/Steepfile +6 -0
  3. data/ext/spikard_rb/extconf.rb +1 -2
  4. data/ext/spikard_rb/{Cargo.lock → native/Cargo.lock} +897 -451
  5. data/ext/spikard_rb/native/Cargo.toml +24 -0
  6. data/ext/spikard_rb/src/lib.rs +5366 -3
  7. data/lib/spikard/native.rb +86 -0
  8. data/lib/spikard/version.rb +6 -1
  9. data/lib/spikard.rb +8 -45
  10. data/lib/spikard_rb.so +0 -0
  11. data/sig/types.rbs +427 -0
  12. metadata +14 -242
  13. data/LICENSE +0 -1
  14. data/README.md +0 -267
  15. data/ext/spikard_rb/Cargo.toml +0 -17
  16. data/lib/spikard/app.rb +0 -428
  17. data/lib/spikard/background.rb +0 -58
  18. data/lib/spikard/config.rb +0 -506
  19. data/lib/spikard/converters.rb +0 -13
  20. data/lib/spikard/grpc.rb +0 -182
  21. data/lib/spikard/handler_wrapper.rb +0 -113
  22. data/lib/spikard/provide.rb +0 -214
  23. data/lib/spikard/response.rb +0 -173
  24. data/lib/spikard/schema.rb +0 -243
  25. data/lib/spikard/sse.rb +0 -111
  26. data/lib/spikard/streaming_response.rb +0 -44
  27. data/lib/spikard/testing.rb +0 -432
  28. data/lib/spikard/upload_file.rb +0 -131
  29. data/lib/spikard/websocket.rb +0 -59
  30. data/sig/spikard.rbs +0 -719
  31. data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -80
  32. data/vendor/crates/spikard-bindings-shared/examples/config_extraction.rs +0 -132
  33. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +0 -905
  34. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +0 -210
  35. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +0 -252
  36. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +0 -404
  37. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +0 -199
  38. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +0 -252
  39. data/vendor/crates/spikard-bindings-shared/src/json_conversion.rs +0 -829
  40. data/vendor/crates/spikard-bindings-shared/src/lazy_cache.rs +0 -587
  41. data/vendor/crates/spikard-bindings-shared/src/lib.rs +0 -33
  42. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +0 -298
  43. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +0 -594
  44. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +0 -743
  45. data/vendor/crates/spikard-bindings-shared/src/response_interpreter.rs +0 -944
  46. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +0 -260
  47. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +0 -369
  48. data/vendor/crates/spikard-bindings-shared/tests/config_extractor_behavior.rs +0 -192
  49. data/vendor/crates/spikard-bindings-shared/tests/error_response_edge_cases.rs +0 -383
  50. data/vendor/crates/spikard-bindings-shared/tests/full_coverage.rs +0 -459
  51. data/vendor/crates/spikard-bindings-shared/tests/handler_base_integration.rs +0 -280
  52. data/vendor/crates/spikard-bindings-shared/tests/integration_tests.rs +0 -669
  53. data/vendor/crates/spikard-core/Cargo.toml +0 -60
  54. data/vendor/crates/spikard-core/src/bindings/mod.rs +0 -3
  55. data/vendor/crates/spikard-core/src/bindings/response.rs +0 -130
  56. data/vendor/crates/spikard-core/src/debug.rs +0 -127
  57. data/vendor/crates/spikard-core/src/di/container.rs +0 -702
  58. data/vendor/crates/spikard-core/src/di/dependency.rs +0 -273
  59. data/vendor/crates/spikard-core/src/di/error.rs +0 -118
  60. data/vendor/crates/spikard-core/src/di/factory.rs +0 -538
  61. data/vendor/crates/spikard-core/src/di/graph.rs +0 -507
  62. data/vendor/crates/spikard-core/src/di/mod.rs +0 -192
  63. data/vendor/crates/spikard-core/src/di/resolved.rs +0 -428
  64. data/vendor/crates/spikard-core/src/di/value.rs +0 -282
  65. data/vendor/crates/spikard-core/src/errors.rs +0 -72
  66. data/vendor/crates/spikard-core/src/http.rs +0 -492
  67. data/vendor/crates/spikard-core/src/lib.rs +0 -29
  68. data/vendor/crates/spikard-core/src/lifecycle.rs +0 -1273
  69. data/vendor/crates/spikard-core/src/metadata.rs +0 -378
  70. data/vendor/crates/spikard-core/src/parameters.rs +0 -2546
  71. data/vendor/crates/spikard-core/src/problem.rs +0 -358
  72. data/vendor/crates/spikard-core/src/request_data.rs +0 -1146
  73. data/vendor/crates/spikard-core/src/router.rs +0 -530
  74. data/vendor/crates/spikard-core/src/schema_registry.rs +0 -197
  75. data/vendor/crates/spikard-core/src/type_hints.rs +0 -311
  76. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +0 -710
  77. data/vendor/crates/spikard-core/src/validation/mod.rs +0 -470
  78. data/vendor/crates/spikard-core/tests/bindings_response_tests.rs +0 -136
  79. data/vendor/crates/spikard-core/tests/di_dependency_defaults.rs +0 -37
  80. data/vendor/crates/spikard-core/tests/error_mapper.rs +0 -761
  81. data/vendor/crates/spikard-core/tests/parameters_edge_cases.rs +0 -106
  82. data/vendor/crates/spikard-core/tests/parameters_full.rs +0 -701
  83. data/vendor/crates/spikard-core/tests/parameters_schema_and_formats.rs +0 -301
  84. data/vendor/crates/spikard-core/tests/request_data_roundtrip.rs +0 -67
  85. data/vendor/crates/spikard-core/tests/validation_coverage.rs +0 -250
  86. data/vendor/crates/spikard-core/tests/validation_error_paths.rs +0 -45
  87. data/vendor/crates/spikard-http/Cargo.toml +0 -87
  88. data/vendor/crates/spikard-http/examples/sse-notifications.rs +0 -148
  89. data/vendor/crates/spikard-http/examples/websocket-chat.rs +0 -92
  90. data/vendor/crates/spikard-http/src/auth.rs +0 -301
  91. data/vendor/crates/spikard-http/src/background.rs +0 -1860
  92. data/vendor/crates/spikard-http/src/bindings/mod.rs +0 -3
  93. data/vendor/crates/spikard-http/src/bindings/response.rs +0 -1
  94. data/vendor/crates/spikard-http/src/body_metadata.rs +0 -8
  95. data/vendor/crates/spikard-http/src/cors.rs +0 -1026
  96. data/vendor/crates/spikard-http/src/debug.rs +0 -128
  97. data/vendor/crates/spikard-http/src/di_handler.rs +0 -1672
  98. data/vendor/crates/spikard-http/src/grpc/framing.rs +0 -469
  99. data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1122
  100. data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -434
  101. data/vendor/crates/spikard-http/src/grpc/service.rs +0 -622
  102. data/vendor/crates/spikard-http/src/grpc/streaming.rs +0 -319
  103. data/vendor/crates/spikard-http/src/handler_response.rs +0 -901
  104. data/vendor/crates/spikard-http/src/handler_trait.rs +0 -1015
  105. data/vendor/crates/spikard-http/src/handler_trait_tests.rs +0 -290
  106. data/vendor/crates/spikard-http/src/jsonrpc/http_handler.rs +0 -502
  107. data/vendor/crates/spikard-http/src/jsonrpc/method_registry.rs +0 -648
  108. data/vendor/crates/spikard-http/src/jsonrpc/mod.rs +0 -58
  109. data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
  110. data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
  111. data/vendor/crates/spikard-http/src/lib.rs +0 -548
  112. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
  113. data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
  114. data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
  115. data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
  116. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
  117. data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
  118. data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
  119. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
  120. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
  121. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
  122. data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
  123. data/vendor/crates/spikard-http/src/response.rs +0 -720
  124. data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
  125. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -858
  126. data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
  127. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
  128. data/vendor/crates/spikard-http/src/server/mod.rs +0 -1649
  129. data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
  130. data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
  131. data/vendor/crates/spikard-http/src/sse.rs +0 -1409
  132. data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
  133. data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
  134. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -787
  135. data/vendor/crates/spikard-http/src/testing.rs +0 -617
  136. data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
  137. data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
  138. data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
  139. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
  140. data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
  141. data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
  142. data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
  143. data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
  144. data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
  145. data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
  146. data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
  147. data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
  148. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
  149. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
  150. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
  151. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
  152. data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -974
  153. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
  154. data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
  155. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
  156. data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
  157. data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
  158. data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -314
  159. data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -200
  160. data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
  161. data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
  162. data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
  163. data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
  164. data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
  165. data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
  166. data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -421
  167. data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
  168. data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
  169. data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
  170. data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
  171. data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
  172. data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
  173. data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
  174. data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
  175. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
  176. data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
  177. data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
  178. data/vendor/crates/spikard-rb/Cargo.toml +0 -68
  179. data/vendor/crates/spikard-rb/build.rs +0 -200
  180. data/vendor/crates/spikard-rb/src/background.rs +0 -63
  181. data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
  182. data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
  183. data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
  184. data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
  185. data/vendor/crates/spikard-rb/src/di/mod.rs +0 -375
  186. data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -834
  187. data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
  188. data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
  189. data/vendor/crates/spikard-rb/src/handler.rs +0 -699
  190. data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
  191. data/vendor/crates/spikard-rb/src/lib.rs +0 -2264
  192. data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -303
  193. data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
  194. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
  195. data/vendor/crates/spikard-rb/src/request.rs +0 -439
  196. data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
  197. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -344
  198. data/vendor/crates/spikard-rb/src/server.rs +0 -307
  199. data/vendor/crates/spikard-rb/src/sse.rs +0 -231
  200. data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
  201. data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
  202. data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
  203. data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
  204. data/vendor/crates/spikard-rb/src/websocket.rs +0 -475
  205. data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -25
  206. data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
@@ -1,428 +0,0 @@
1
- //! Storage for resolved dependencies
2
- //!
3
- //! This module provides the `ResolvedDependencies` type which holds all dependencies
4
- //! resolved for a particular request, with type-safe access and cleanup support.
5
-
6
- use std::any::Any;
7
- use std::collections::HashMap;
8
- use std::future::Future;
9
- use std::pin::Pin;
10
- use std::sync::{Arc, Mutex};
11
-
12
- type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
13
- type CleanupTask = Box<dyn FnOnce() -> BoxFuture<'static, ()> + Send>;
14
-
15
- /// Storage for resolved dependencies with type-safe access
16
- ///
17
- /// This type stores all dependencies that have been resolved for a request,
18
- /// allowing type-safe retrieval and supporting cleanup tasks for generator-pattern
19
- /// dependencies.
20
- ///
21
- /// Uses Arc<Mutex<>> internally for thread-safe shared access.
22
- ///
23
- /// # Examples
24
- ///
25
- /// ```ignore
26
- /// use spikard_core::di::ResolvedDependencies;
27
- /// use std::sync::Arc;
28
- ///
29
- /// # tokio_test::block_on(async {
30
- /// let mut resolved = ResolvedDependencies::new();
31
- ///
32
- /// // Insert a dependency
33
- /// let value = Arc::new(42i32);
34
- /// resolved.insert("answer".to_string(), value);
35
- ///
36
- /// // Retrieve with type safety
37
- /// let retrieved: Option<Arc<i32>> = resolved.get("answer");
38
- /// assert_eq!(retrieved.map(|v| *v), Some(42));
39
- ///
40
- /// // Type mismatch returns None
41
- /// let wrong_type: Option<Arc<String>> = resolved.get("answer");
42
- /// assert!(wrong_type.is_none());
43
- ///
44
- /// // Cleanup
45
- /// resolved.cleanup().await;
46
- /// # });
47
- /// ```
48
- #[derive(Default, Clone)]
49
- pub struct ResolvedDependencies {
50
- /// Map of dependency keys to type-erased values
51
- dependencies: Arc<Mutex<HashMap<String, Arc<dyn Any + Send + Sync>>>>,
52
- /// Cleanup tasks to run when dependencies are dropped
53
- cleanup_tasks: Arc<Mutex<Vec<CleanupTask>>>,
54
- }
55
-
56
- impl ResolvedDependencies {
57
- /// Create a new empty resolved dependencies storage
58
- ///
59
- /// # Examples
60
- ///
61
- /// ```ignore
62
- /// use spikard_core::di::ResolvedDependencies;
63
- ///
64
- /// let resolved = ResolvedDependencies::new();
65
- /// ```
66
- #[must_use]
67
- pub fn new() -> Self {
68
- Self {
69
- dependencies: Arc::new(Mutex::new(HashMap::new())),
70
- cleanup_tasks: Arc::new(Mutex::new(Vec::new())),
71
- }
72
- }
73
-
74
- /// Insert a dependency into the storage
75
- ///
76
- /// # Arguments
77
- ///
78
- /// * `key` - The unique key for this dependency
79
- /// * `value` - The dependency value wrapped in Arc<dyn Any + Send + Sync>
80
- ///
81
- /// # Examples
82
- ///
83
- /// ```ignore
84
- /// use spikard_core::di::ResolvedDependencies;
85
- /// use std::sync::Arc;
86
- ///
87
- /// let mut resolved = ResolvedDependencies::new();
88
- /// let config = Arc::new("production".to_string());
89
- /// resolved.insert("config".to_string(), config);
90
- /// ```
91
- ///
92
- /// # Panics
93
- /// Panics if the lock is poisoned.
94
- pub fn insert(&mut self, key: String, value: Arc<dyn Any + Send + Sync>) {
95
- self.dependencies.lock().unwrap().insert(key, value);
96
- }
97
-
98
- /// Get a dependency with type-safe downcasting
99
- ///
100
- /// Returns `Some(Arc<T>)` if the dependency exists and is of type `T`,
101
- /// or `None` if it doesn't exist or has a different type.
102
- ///
103
- /// # Type Parameters
104
- ///
105
- /// * `T` - The expected type of the dependency (must be Send + Sync)
106
- ///
107
- /// # Arguments
108
- ///
109
- /// * `key` - The key of the dependency to retrieve
110
- ///
111
- /// # Examples
112
- ///
113
- /// ```ignore
114
- /// use spikard_core::di::ResolvedDependencies;
115
- /// use std::sync::Arc;
116
- ///
117
- /// let mut resolved = ResolvedDependencies::new();
118
- /// resolved.insert("count".to_string(), Arc::new(100i32));
119
- ///
120
- /// // Correct type
121
- /// let count: Option<Arc<i32>> = resolved.get("count");
122
- /// assert_eq!(count.map(|v| *v), Some(100));
123
- ///
124
- /// // Wrong type
125
- /// let wrong: Option<Arc<String>> = resolved.get("count");
126
- /// assert!(wrong.is_none());
127
- ///
128
- /// // Missing key
129
- /// let missing: Option<Arc<i32>> = resolved.get("missing");
130
- /// assert!(missing.is_none());
131
- /// ```
132
- ///
133
- /// # Panics
134
- /// Panics if the lock is poisoned.
135
- #[must_use]
136
- pub fn get<T: Send + Sync + 'static>(&self, key: &str) -> Option<Arc<T>> {
137
- self.dependencies
138
- .lock()
139
- .unwrap()
140
- .get(key)
141
- .and_then(|value| Arc::clone(value).downcast::<T>().ok())
142
- }
143
-
144
- /// Get a dependency as Arc<dyn Any> without type checking
145
- ///
146
- /// This is useful when you need to pass dependencies around without
147
- /// knowing their concrete type.
148
- ///
149
- /// # Arguments
150
- ///
151
- /// * `key` - The key of the dependency to retrieve
152
- ///
153
- /// # Examples
154
- ///
155
- /// ```ignore
156
- /// use spikard_core::di::ResolvedDependencies;
157
- /// use std::sync::Arc;
158
- ///
159
- /// let mut resolved = ResolvedDependencies::new();
160
- /// resolved.insert("data".to_string(), Arc::new(vec![1, 2, 3]));
161
- ///
162
- /// let any_ref = resolved.get_arc("data");
163
- /// assert!(any_ref.is_some());
164
- /// ```
165
- ///
166
- /// # Panics
167
- /// Panics if the lock is poisoned.
168
- #[must_use]
169
- pub fn get_arc(&self, key: &str) -> Option<Arc<dyn Any + Send + Sync>> {
170
- self.dependencies.lock().unwrap().get(key).cloned()
171
- }
172
-
173
- /// Check if a dependency exists
174
- ///
175
- /// # Arguments
176
- ///
177
- /// * `key` - The key to check
178
- ///
179
- /// # Examples
180
- ///
181
- /// ```ignore
182
- /// use spikard_core::di::ResolvedDependencies;
183
- /// use std::sync::Arc;
184
- ///
185
- /// let mut resolved = ResolvedDependencies::new();
186
- /// resolved.insert("exists".to_string(), Arc::new(true));
187
- ///
188
- /// assert!(resolved.contains("exists"));
189
- /// assert!(!resolved.contains("missing"));
190
- /// ```
191
- ///
192
- /// # Panics
193
- /// Panics if the lock is poisoned.
194
- #[must_use]
195
- pub fn contains(&self, key: &str) -> bool {
196
- self.dependencies.lock().unwrap().contains_key(key)
197
- }
198
-
199
- /// Get all dependency keys
200
- ///
201
- /// Returns a vector of all keys currently stored in this resolved dependencies.
202
- /// Useful for iterating over all dependencies when you need to extract them.
203
- ///
204
- /// # Examples
205
- ///
206
- /// ```ignore
207
- /// use spikard_core::di::ResolvedDependencies;
208
- /// use std::sync::Arc;
209
- ///
210
- /// let mut resolved = ResolvedDependencies::new();
211
- /// resolved.insert("config".to_string(), Arc::new("prod".to_string()));
212
- /// resolved.insert("db".to_string(), Arc::new(42i32));
213
- ///
214
- /// let keys = resolved.keys();
215
- /// assert_eq!(keys.len(), 2);
216
- /// assert!(keys.contains(&"config".to_string()));
217
- /// assert!(keys.contains(&"db".to_string()));
218
- /// ```
219
- ///
220
- /// # Panics
221
- /// Panics if the lock is poisoned.
222
- #[must_use]
223
- pub fn keys(&self) -> Vec<String> {
224
- self.dependencies.lock().unwrap().keys().cloned().collect()
225
- }
226
-
227
- /// Add a cleanup task to be run when dependencies are cleaned up
228
- ///
229
- /// Cleanup tasks are useful for generator-pattern dependencies that need
230
- /// to perform cleanup (e.g., closing database connections, releasing locks).
231
- ///
232
- /// # Arguments
233
- ///
234
- /// * `task` - A function that returns a future performing cleanup
235
- ///
236
- /// # Examples
237
- ///
238
- /// ```ignore
239
- /// use spikard_core::di::ResolvedDependencies;
240
- ///
241
- /// # tokio_test::block_on(async {
242
- /// let mut resolved = ResolvedDependencies::new();
243
- ///
244
- /// resolved.add_cleanup_task(Box::new(|| {
245
- /// Box::pin(async {
246
- /// println!("Cleaning up resources");
247
- /// })
248
- /// }));
249
- ///
250
- /// resolved.cleanup().await;
251
- /// # });
252
- /// ```
253
- ///
254
- /// # Panics
255
- /// Panics if the lock is poisoned.
256
- pub fn add_cleanup_task(&self, task: CleanupTask) {
257
- self.cleanup_tasks.lock().unwrap().push(task);
258
- }
259
-
260
- /// Run all cleanup tasks in reverse order
261
- ///
262
- /// Cleanup tasks are executed in LIFO order (last added, first executed)
263
- /// to properly handle nested resource dependencies.
264
- ///
265
- /// This consumes self to ensure cleanup is only run once.
266
- ///
267
- /// # Examples
268
- ///
269
- /// ```ignore
270
- /// use spikard_core::di::ResolvedDependencies;
271
- /// use std::sync::{Arc, Mutex};
272
- ///
273
- /// # tokio_test::block_on(async {
274
- /// let order = Arc::new(Mutex::new(Vec::new()));
275
- ///
276
- /// let mut resolved = ResolvedDependencies::new();
277
- ///
278
- /// let order1 = order.clone();
279
- /// resolved.add_cleanup_task(Box::new(move || {
280
- /// Box::pin(async move {
281
- /// order1.lock().unwrap().push(1);
282
- /// })
283
- /// }));
284
- ///
285
- /// let order2 = order.clone();
286
- /// resolved.add_cleanup_task(Box::new(move || {
287
- /// Box::pin(async move {
288
- /// order2.lock().unwrap().push(2);
289
- /// })
290
- /// }));
291
- ///
292
- /// resolved.cleanup().await;
293
- ///
294
- /// // Tasks run in reverse order (LIFO)
295
- /// assert_eq!(*order.lock().unwrap(), vec![2, 1]);
296
- /// # });
297
- /// ```
298
- ///
299
- /// # Panics
300
- /// Panics if the lock is poisoned.
301
- pub async fn cleanup(self) {
302
- let tasks = {
303
- let mut cleanup_tasks = self.cleanup_tasks.lock().unwrap();
304
- std::mem::take(&mut *cleanup_tasks)
305
- };
306
-
307
- for task in tasks.into_iter().rev() {
308
- task().await;
309
- }
310
- }
311
- }
312
-
313
- impl std::fmt::Debug for ResolvedDependencies {
314
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315
- let deps = self.dependencies.lock().unwrap();
316
- let tasks = self.cleanup_tasks.lock().unwrap();
317
- f.debug_struct("ResolvedDependencies")
318
- .field("dependencies", &deps.keys())
319
- .field("cleanup_tasks_count", &tasks.len())
320
- .finish()
321
- }
322
- }
323
-
324
- #[cfg(test)]
325
- mod tests {
326
- use super::*;
327
-
328
- #[test]
329
- fn test_new() {
330
- let resolved = ResolvedDependencies::new();
331
- assert!(!resolved.contains("anything"));
332
- }
333
-
334
- #[test]
335
- fn test_insert_and_get() {
336
- let mut resolved = ResolvedDependencies::new();
337
- let value = Arc::new(42i32);
338
- resolved.insert("answer".to_string(), value);
339
-
340
- let retrieved: Option<Arc<i32>> = resolved.get("answer");
341
- assert_eq!(retrieved.map(|v| *v), Some(42));
342
- }
343
-
344
- #[test]
345
- fn test_get_type_mismatch() {
346
- let mut resolved = ResolvedDependencies::new();
347
- resolved.insert("number".to_string(), Arc::new(42i32));
348
-
349
- let wrong: Option<Arc<String>> = resolved.get("number");
350
- assert!(wrong.is_none());
351
- }
352
-
353
- #[test]
354
- fn test_get_missing() {
355
- let resolved = ResolvedDependencies::new();
356
- let missing: Option<Arc<i32>> = resolved.get("missing");
357
- assert!(missing.is_none());
358
- }
359
-
360
- #[test]
361
- fn test_get_arc() {
362
- let mut resolved = ResolvedDependencies::new();
363
- resolved.insert("data".to_string(), Arc::new(vec![1, 2, 3]));
364
-
365
- let any_ref = resolved.get_arc("data");
366
- assert!(any_ref.is_some());
367
-
368
- let vec_ref = any_ref.unwrap().downcast::<Vec<i32>>().ok();
369
- assert!(vec_ref.is_some());
370
- }
371
-
372
- #[test]
373
- fn test_contains() {
374
- let mut resolved = ResolvedDependencies::new();
375
- resolved.insert("exists".to_string(), Arc::new(true));
376
-
377
- assert!(resolved.contains("exists"));
378
- assert!(!resolved.contains("missing"));
379
- }
380
-
381
- #[tokio::test]
382
- async fn test_cleanup_order() {
383
- let order = Arc::new(Mutex::new(Vec::new()));
384
-
385
- let resolved = ResolvedDependencies::new();
386
-
387
- let order1 = order.clone();
388
- resolved.add_cleanup_task(Box::new(move || {
389
- Box::pin(async move {
390
- order1.lock().unwrap().push(1);
391
- })
392
- }));
393
-
394
- let order2 = order.clone();
395
- resolved.add_cleanup_task(Box::new(move || {
396
- Box::pin(async move {
397
- order2.lock().unwrap().push(2);
398
- })
399
- }));
400
-
401
- let order3 = order.clone();
402
- resolved.add_cleanup_task(Box::new(move || {
403
- Box::pin(async move {
404
- order3.lock().unwrap().push(3);
405
- })
406
- }));
407
-
408
- resolved.cleanup().await;
409
-
410
- assert_eq!(*order.lock().unwrap(), vec![3, 2, 1]);
411
- }
412
-
413
- #[tokio::test]
414
- async fn test_cleanup_empty() {
415
- let resolved = ResolvedDependencies::new();
416
- resolved.cleanup().await;
417
- }
418
-
419
- #[test]
420
- fn test_clone() {
421
- let mut resolved1 = ResolvedDependencies::new();
422
- resolved1.insert("key".to_string(), Arc::new(42i32));
423
-
424
- let resolved2 = resolved1.clone();
425
- let value: Option<Arc<i32>> = resolved2.get("key");
426
- assert_eq!(value.map(|v| *v), Some(42));
427
- }
428
- }
@@ -1,282 +0,0 @@
1
- //! Value dependency implementation
2
- //!
3
- //! This module provides `ValueDependency<T>`, a simple dependency that wraps
4
- //! a static value and returns it whenever resolved.
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::marker::PhantomData;
14
- use std::pin::Pin;
15
- use std::sync::Arc;
16
-
17
- /// A dependency that wraps a static value
18
- ///
19
- /// This is the simplest form of dependency - it just returns a pre-configured
20
- /// value whenever resolved. Useful for configuration values, constants, or
21
- /// pre-built objects.
22
- ///
23
- /// # Type Parameters
24
- ///
25
- /// * `T` - The type of value to provide. Must be `Clone + Send + Sync + 'static`.
26
- ///
27
- /// # Examples
28
- ///
29
- /// ```ignore
30
- /// use spikard_core::di::{Dependency, ValueDependency};
31
- /// use http::Request;
32
- /// use crate::request_data::RequestData;
33
- /// use std::collections::HashMap;
34
- /// use std::sync::Arc;
35
- ///
36
- /// # tokio_test::block_on(async {
37
- /// // Create a value dependency with a configuration string
38
- /// let config = ValueDependency::new("database_url", "postgresql://localhost/mydb");
39
- ///
40
- /// // Resolve it (returns the same value every time)
41
- /// let request = Request::builder().body(()).unwrap();
42
- /// let request_data = RequestData {
43
- /// path_params: Arc::new(HashMap::new()),
44
- /// query_params: serde_json::Value::Null,
45
- /// validated_params: None,
46
- /// raw_query_params: Arc::new(HashMap::new()),
47
- /// body: serde_json::Value::Null,
48
- /// raw_body: None,
49
- /// headers: Arc::new(HashMap::new()),
50
- /// cookies: Arc::new(HashMap::new()),
51
- /// method: "GET".to_string(),
52
- /// path: "/".to_string(),
53
- /// };
54
- /// let resolved = spikard_core::di::ResolvedDependencies::new();
55
- ///
56
- /// let result = config.resolve(&request, &request_data, &resolved).await.unwrap();
57
- /// let value: Arc<String> = result.downcast().unwrap();
58
- /// assert_eq!(*value, "postgresql://localhost/mydb");
59
- /// # });
60
- /// ```
61
- pub struct ValueDependency<T: Clone + Send + Sync + 'static> {
62
- key: String,
63
- value: Arc<T>,
64
- _phantom: PhantomData<T>,
65
- }
66
-
67
- impl<T: Clone + Send + Sync + 'static> ValueDependency<T> {
68
- /// Create a new value dependency
69
- ///
70
- /// # Arguments
71
- ///
72
- /// * `key` - The unique key for this dependency
73
- /// * `value` - The value to provide when resolved
74
- ///
75
- /// # Examples
76
- ///
77
- /// ```ignore
78
- /// use spikard_core::di::ValueDependency;
79
- ///
80
- /// // Simple value
81
- /// let port = ValueDependency::new("port", 8080u16);
82
- ///
83
- /// // Complex value
84
- /// #[derive(Clone)]
85
- /// struct Config {
86
- /// debug: bool,
87
- /// timeout: u64,
88
- /// }
89
- ///
90
- /// let config = ValueDependency::new("config", Config {
91
- /// debug: true,
92
- /// timeout: 30,
93
- /// });
94
- /// ```
95
- pub fn new(key: impl Into<String>, value: T) -> Self {
96
- Self {
97
- key: key.into(),
98
- value: Arc::new(value),
99
- _phantom: PhantomData,
100
- }
101
- }
102
- }
103
-
104
- impl<T: Clone + Send + Sync + 'static> Dependency for ValueDependency<T> {
105
- fn resolve(
106
- &self,
107
- _request: &Request<()>,
108
- _request_data: &RequestData,
109
- _resolved: &ResolvedDependencies,
110
- ) -> Pin<Box<dyn Future<Output = Result<Arc<dyn Any + Send + Sync>, DependencyError>> + Send>> {
111
- let value = Arc::clone(&self.value);
112
- Box::pin(async move { Ok(value as Arc<dyn Any + Send + Sync>) })
113
- }
114
-
115
- fn key(&self) -> &str {
116
- &self.key
117
- }
118
-
119
- fn depends_on(&self) -> Vec<String> {
120
- vec![]
121
- }
122
-
123
- fn cacheable(&self) -> bool {
124
- true
125
- }
126
-
127
- fn singleton(&self) -> bool {
128
- true
129
- }
130
- }
131
-
132
- impl<T: Clone + Send + Sync + 'static> std::fmt::Debug for ValueDependency<T> {
133
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134
- f.debug_struct("ValueDependency")
135
- .field("key", &self.key)
136
- .field("value_type", &std::any::type_name::<T>())
137
- .field("value", &"<T>")
138
- .finish()
139
- }
140
- }
141
-
142
- #[cfg(test)]
143
- mod tests {
144
- use super::*;
145
- use std::collections::HashMap;
146
-
147
- fn make_request_data() -> RequestData {
148
- RequestData {
149
- path_params: Arc::new(HashMap::new()),
150
- query_params: serde_json::Value::Null,
151
- validated_params: None,
152
- raw_query_params: Arc::new(HashMap::new()),
153
- body: serde_json::Value::Null,
154
- raw_body: None,
155
- headers: Arc::new(HashMap::new()),
156
- cookies: Arc::new(HashMap::new()),
157
- method: "GET".to_string(),
158
- path: "/".to_string(),
159
- #[cfg(feature = "di")]
160
- dependencies: None,
161
- }
162
- }
163
-
164
- #[test]
165
- fn test_new() {
166
- let dep = ValueDependency::new("test", 42i32);
167
- assert_eq!(dep.key(), "test");
168
- }
169
-
170
- #[test]
171
- fn test_key() {
172
- let dep = ValueDependency::new("my_key", "value");
173
- assert_eq!(dep.key(), "my_key");
174
- }
175
-
176
- #[test]
177
- fn test_depends_on() {
178
- let dep = ValueDependency::new("test", 42i32);
179
- assert_eq!(dep.depends_on(), Vec::<String>::new());
180
- }
181
-
182
- #[test]
183
- fn test_cacheable() {
184
- let dep = ValueDependency::new("test", 42i32);
185
- assert!(dep.cacheable());
186
- }
187
-
188
- #[test]
189
- fn test_singleton() {
190
- let dep = ValueDependency::new("test", 42i32);
191
- assert!(dep.singleton());
192
- }
193
-
194
- #[tokio::test]
195
- async fn test_resolve_simple() {
196
- let dep = ValueDependency::new("answer", 42i32);
197
- let request = Request::builder().body(()).unwrap();
198
- let request_data = make_request_data();
199
- let resolved = ResolvedDependencies::new();
200
-
201
- let result = dep.resolve(&request, &request_data, &resolved).await;
202
- assert!(result.is_ok());
203
-
204
- let value: Arc<i32> = result.unwrap().downcast().unwrap();
205
- assert_eq!(*value, 42);
206
- }
207
-
208
- #[tokio::test]
209
- async fn test_resolve_string() {
210
- let dep = ValueDependency::new("message", "Hello, World!".to_string());
211
- let request = Request::builder().body(()).unwrap();
212
- let request_data = make_request_data();
213
- let resolved = ResolvedDependencies::new();
214
-
215
- let result = dep.resolve(&request, &request_data, &resolved).await;
216
- assert!(result.is_ok());
217
-
218
- let value: Arc<String> = result.unwrap().downcast().unwrap();
219
- assert_eq!(*value, "Hello, World!");
220
- }
221
-
222
- #[tokio::test]
223
- async fn test_resolve_concurrent() {
224
- let dep = Arc::new(ValueDependency::new("shared", 100i32));
225
- let request = Request::builder().body(()).unwrap();
226
- let request_data = make_request_data();
227
-
228
- let handles: Vec<_> = (0..10)
229
- .map(|_| {
230
- let dep = Arc::clone(&dep);
231
- let req = request.clone();
232
- let data = request_data.clone();
233
- tokio::spawn(async move {
234
- let resolved = ResolvedDependencies::new();
235
- let result = dep.resolve(&req, &data, &resolved).await.unwrap();
236
- let value: Arc<i32> = result.downcast().unwrap();
237
- *value
238
- })
239
- })
240
- .collect();
241
-
242
- for handle in handles {
243
- let value = handle.await.unwrap();
244
- assert_eq!(value, 100);
245
- }
246
- }
247
-
248
- #[derive(Clone, Debug, PartialEq)]
249
- struct ComplexValue {
250
- name: String,
251
- count: i32,
252
- tags: Vec<String>,
253
- }
254
-
255
- #[tokio::test]
256
- async fn test_resolve_complex_type() {
257
- let complex = ComplexValue {
258
- name: "test".to_string(),
259
- count: 42,
260
- tags: vec!["tag1".to_string(), "tag2".to_string()],
261
- };
262
-
263
- let dep = ValueDependency::new("complex", complex.clone());
264
- let request = Request::builder().body(()).unwrap();
265
- let request_data = make_request_data();
266
- let resolved = ResolvedDependencies::new();
267
-
268
- let result = dep.resolve(&request, &request_data, &resolved).await;
269
- assert!(result.is_ok());
270
-
271
- let value: Arc<ComplexValue> = result.unwrap().downcast().unwrap();
272
- assert_eq!(*value, complex);
273
- }
274
-
275
- #[test]
276
- fn test_debug() {
277
- let dep = ValueDependency::new("test", 42i32);
278
- let debug_str = format!("{dep:?}");
279
- assert!(debug_str.contains("ValueDependency"));
280
- assert!(debug_str.contains("test"));
281
- }
282
- }