spikard 0.13.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 (207) 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} +819 -424
  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 -52
  10. data/lib/spikard_rb.so +0 -0
  11. data/sig/types.rbs +427 -0
  12. metadata +14 -243
  13. data/LICENSE +0 -1
  14. data/README.md +0 -285
  15. data/ext/spikard_rb/Cargo.toml +0 -17
  16. data/lib/spikard/app.rb +0 -458
  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 -232
  21. data/lib/spikard/handler_wrapper.rb +0 -113
  22. data/lib/spikard/provide.rb +0 -315
  23. data/lib/spikard/response.rb +0 -198
  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 -474
  28. data/lib/spikard/upload_file.rb +0 -131
  29. data/lib/spikard/websocket.rb +0 -59
  30. data/sig/spikard.rbs +0 -739
  31. data/vendor/crates/spikard-bindings-shared/Cargo.toml +0 -75
  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 -55
  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 -711
  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 -548
  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 -82
  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 -1859
  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 -653
  99. data/vendor/crates/spikard-http/src/grpc/handler.rs +0 -1211
  100. data/vendor/crates/spikard-http/src/grpc/mod.rs +0 -556
  101. data/vendor/crates/spikard-http/src/grpc/service.rs +0 -706
  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 -60
  109. data/vendor/crates/spikard-http/src/jsonrpc/openrpc.rs +0 -325
  110. data/vendor/crates/spikard-http/src/jsonrpc/protocol.rs +0 -1207
  111. data/vendor/crates/spikard-http/src/jsonrpc/router.rs +0 -2262
  112. data/vendor/crates/spikard-http/src/lib.rs +0 -566
  113. data/vendor/crates/spikard-http/src/lifecycle/adapter.rs +0 -230
  114. data/vendor/crates/spikard-http/src/lifecycle.rs +0 -1193
  115. data/vendor/crates/spikard-http/src/middleware/mod.rs +0 -560
  116. data/vendor/crates/spikard-http/src/middleware/multipart.rs +0 -912
  117. data/vendor/crates/spikard-http/src/middleware/urlencoded.rs +0 -513
  118. data/vendor/crates/spikard-http/src/middleware/validation.rs +0 -768
  119. data/vendor/crates/spikard-http/src/openapi/mod.rs +0 -309
  120. data/vendor/crates/spikard-http/src/openapi/parameter_extraction.rs +0 -535
  121. data/vendor/crates/spikard-http/src/openapi/schema_conversion.rs +0 -1363
  122. data/vendor/crates/spikard-http/src/openapi/spec_generation.rs +0 -667
  123. data/vendor/crates/spikard-http/src/query_parser.rs +0 -793
  124. data/vendor/crates/spikard-http/src/response.rs +0 -720
  125. data/vendor/crates/spikard-http/src/server/fast_router.rs +0 -186
  126. data/vendor/crates/spikard-http/src/server/grpc_routing.rs +0 -1243
  127. data/vendor/crates/spikard-http/src/server/handler.rs +0 -1661
  128. data/vendor/crates/spikard-http/src/server/lifecycle_execution.rs +0 -253
  129. data/vendor/crates/spikard-http/src/server/mod.rs +0 -1717
  130. data/vendor/crates/spikard-http/src/server/request_extraction.rs +0 -871
  131. data/vendor/crates/spikard-http/src/server/routing_factory.rs +0 -618
  132. data/vendor/crates/spikard-http/src/sse.rs +0 -1409
  133. data/vendor/crates/spikard-http/src/testing/form.rs +0 -52
  134. data/vendor/crates/spikard-http/src/testing/multipart.rs +0 -64
  135. data/vendor/crates/spikard-http/src/testing/test_client.rs +0 -825
  136. data/vendor/crates/spikard-http/src/testing.rs +0 -617
  137. data/vendor/crates/spikard-http/src/websocket.rs +0 -1477
  138. data/vendor/crates/spikard-http/tests/auth_integration.rs +0 -645
  139. data/vendor/crates/spikard-http/tests/background_behavior.rs +0 -832
  140. data/vendor/crates/spikard-http/tests/common/grpc_helpers.rs +0 -1012
  141. data/vendor/crates/spikard-http/tests/common/handlers.rs +0 -309
  142. data/vendor/crates/spikard-http/tests/common/mod.rs +0 -33
  143. data/vendor/crates/spikard-http/tests/common/test_builders.rs +0 -628
  144. data/vendor/crates/spikard-http/tests/di_handler_error_responses.rs +0 -162
  145. data/vendor/crates/spikard-http/tests/di_integration.rs +0 -192
  146. data/vendor/crates/spikard-http/tests/doc_snippets.rs +0 -5
  147. data/vendor/crates/spikard-http/tests/grpc_bidirectional_streaming.rs +0 -430
  148. data/vendor/crates/spikard-http/tests/grpc_client_streaming.rs +0 -738
  149. data/vendor/crates/spikard-http/tests/grpc_error_handling_test.rs +0 -652
  150. data/vendor/crates/spikard-http/tests/grpc_integration_test.rs +0 -334
  151. data/vendor/crates/spikard-http/tests/grpc_metadata_test.rs +0 -532
  152. data/vendor/crates/spikard-http/tests/grpc_server_integration.rs +0 -495
  153. data/vendor/crates/spikard-http/tests/grpc_server_streaming.rs +0 -975
  154. data/vendor/crates/spikard-http/tests/lifecycle_execution.rs +0 -1093
  155. data/vendor/crates/spikard-http/tests/middleware_stack_integration.rs +0 -389
  156. data/vendor/crates/spikard-http/tests/multipart_behavior.rs +0 -656
  157. data/vendor/crates/spikard-http/tests/request_extraction_full.rs +0 -513
  158. data/vendor/crates/spikard-http/tests/server_auth_middleware_behavior.rs +0 -328
  159. data/vendor/crates/spikard-http/tests/server_config_builder.rs +0 -335
  160. data/vendor/crates/spikard-http/tests/server_configured_router_behavior.rs +0 -374
  161. data/vendor/crates/spikard-http/tests/server_cors_preflight.rs +0 -83
  162. data/vendor/crates/spikard-http/tests/server_handler_wrappers.rs +0 -464
  163. data/vendor/crates/spikard-http/tests/server_method_router_additional_behavior.rs +0 -286
  164. data/vendor/crates/spikard-http/tests/server_method_router_coverage.rs +0 -118
  165. data/vendor/crates/spikard-http/tests/server_middleware_behavior.rs +0 -99
  166. data/vendor/crates/spikard-http/tests/server_middleware_branches.rs +0 -204
  167. data/vendor/crates/spikard-http/tests/server_openapi_jsonrpc_static.rs +0 -427
  168. data/vendor/crates/spikard-http/tests/server_router_behavior.rs +0 -121
  169. data/vendor/crates/spikard-http/tests/sse_behavior.rs +0 -620
  170. data/vendor/crates/spikard-http/tests/sse_full_behavior.rs +0 -584
  171. data/vendor/crates/spikard-http/tests/sse_handler_behavior.rs +0 -130
  172. data/vendor/crates/spikard-http/tests/test_client_requests.rs +0 -167
  173. data/vendor/crates/spikard-http/tests/testing_helpers.rs +0 -87
  174. data/vendor/crates/spikard-http/tests/testing_module_coverage.rs +0 -155
  175. data/vendor/crates/spikard-http/tests/urlencoded_content_type.rs +0 -82
  176. data/vendor/crates/spikard-http/tests/websocket_behavior.rs +0 -663
  177. data/vendor/crates/spikard-http/tests/websocket_full_behavior.rs +0 -440
  178. data/vendor/crates/spikard-http/tests/websocket_integration.rs +0 -150
  179. data/vendor/crates/spikard-rb/Cargo.toml +0 -63
  180. data/vendor/crates/spikard-rb/build.rs +0 -200
  181. data/vendor/crates/spikard-rb/src/background.rs +0 -63
  182. data/vendor/crates/spikard-rb/src/config/mod.rs +0 -5
  183. data/vendor/crates/spikard-rb/src/config/server_config.rs +0 -401
  184. data/vendor/crates/spikard-rb/src/conversion.rs +0 -688
  185. data/vendor/crates/spikard-rb/src/di/builder.rs +0 -100
  186. data/vendor/crates/spikard-rb/src/di/mod.rs +0 -410
  187. data/vendor/crates/spikard-rb/src/grpc/handler.rs +0 -875
  188. data/vendor/crates/spikard-rb/src/grpc/mod.rs +0 -13
  189. data/vendor/crates/spikard-rb/src/gvl.rs +0 -80
  190. data/vendor/crates/spikard-rb/src/handler.rs +0 -699
  191. data/vendor/crates/spikard-rb/src/integration/mod.rs +0 -3
  192. data/vendor/crates/spikard-rb/src/lib.rs +0 -2268
  193. data/vendor/crates/spikard-rb/src/lifecycle.rs +0 -334
  194. data/vendor/crates/spikard-rb/src/metadata/mod.rs +0 -5
  195. data/vendor/crates/spikard-rb/src/metadata/route_extraction.rs +0 -507
  196. data/vendor/crates/spikard-rb/src/request.rs +0 -439
  197. data/vendor/crates/spikard-rb/src/runtime/mod.rs +0 -5
  198. data/vendor/crates/spikard-rb/src/runtime/server_runner.rs +0 -368
  199. data/vendor/crates/spikard-rb/src/server.rs +0 -304
  200. data/vendor/crates/spikard-rb/src/sse.rs +0 -231
  201. data/vendor/crates/spikard-rb/src/testing/client.rs +0 -698
  202. data/vendor/crates/spikard-rb/src/testing/mod.rs +0 -7
  203. data/vendor/crates/spikard-rb/src/testing/sse.rs +0 -108
  204. data/vendor/crates/spikard-rb/src/testing/websocket.rs +0 -573
  205. data/vendor/crates/spikard-rb/src/websocket.rs +0 -521
  206. data/vendor/crates/spikard-rb-macros/Cargo.toml +0 -20
  207. data/vendor/crates/spikard-rb-macros/src/lib.rs +0 -51
@@ -1,587 +0,0 @@
1
- //! Lazy initialization and caching for language binding values.
2
- //!
3
- //! This module provides `LazyCache<T>`, a zero-cost abstraction for lazy evaluation
4
- //! and caching of expensive-to-compute values within single-threaded language bindings.
5
- //!
6
- //! # Overview
7
- //!
8
- //! Language bindings (Python, Node.js, Ruby, PHP) frequently need to convert Rust data
9
- //! to native language objects. These conversions are expensive and often requested multiple
10
- //! times per request. `LazyCache<T>` defers expensive conversions until requested and caches
11
- //! the result for subsequent accesses.
12
- //!
13
- //! This pattern eliminates 30-40% of conversion overhead in typical request handling:
14
- //! - Headers are only converted if accessed
15
- //! - Query parameters are cached after first access
16
- //! - Complex nested structures are materialized once
17
- //!
18
- //! # Thread Safety
19
- //!
20
- //! **This type is NOT thread-safe.** It uses `RefCell<Option<T>>` for interior mutability,
21
- //! which will panic if accessed concurrently. This is intentional and correct because:
22
- //!
23
- //! - **Python GIL**: Single-threaded execution; one handler at a time
24
- //! - **Node.js**: Single-threaded event loop; async handled via futures
25
- //! - **Ruby GVL**: Global VM lock ensures single-threaded execution
26
- //! - **PHP**: Request-scoped execution; single-threaded per request
27
- //!
28
- //! For multi-threaded Rust code, use `parking_lot::Mutex<Option<T>>` instead.
29
- //!
30
- //! # Example
31
- //!
32
- //! ```ignore
33
- //! use spikard_bindings_shared::LazyCache;
34
- //!
35
- //! struct Request {
36
- //! raw_headers: HashMap<String, String>,
37
- //! headers_cache: LazyCache<RubyHash>, // Expensive Ruby object
38
- //! }
39
- //!
40
- //! impl Request {
41
- //! fn get_headers(&self, ruby: &Ruby) -> Result<&RubyHash> {
42
- //! self.headers_cache.get_or_init(|| {
43
- //! convert_hashmap_to_ruby_hash(ruby, &self.raw_headers)
44
- //! })
45
- //! }
46
- //! }
47
- //! ```
48
- //!
49
- //! First call to `get_headers()` invokes the closure and caches the result.
50
- //! Subsequent calls return the cached reference without invoking the closure.
51
-
52
- use std::cell::RefCell;
53
-
54
- /// Lazy-initialized and cached value.
55
- ///
56
- /// Stores an `Option<T>` in a `RefCell` for interior mutability. The value is
57
- /// initialized on first access via a provided closure and cached for subsequent
58
- /// accesses.
59
- ///
60
- /// # Panics
61
- ///
62
- /// Accessing `LazyCache` during active mutable borrowing will panic. This is
63
- /// only possible with nested or recursive access patterns, which should be avoided
64
- /// in language bindings.
65
- #[derive(Default, Debug)]
66
- pub struct LazyCache<T> {
67
- /// Interior mutability cell holding the cached value.
68
- ///
69
- /// `None` means not yet initialized. Some(value) means cached.
70
- cache: RefCell<Option<T>>,
71
- }
72
-
73
- impl<T> LazyCache<T> {
74
- /// Create a new empty cache.
75
- ///
76
- /// The value will be initialized on first access via `get_or_init`.
77
- ///
78
- /// # Example
79
- ///
80
- /// ```
81
- /// use spikard_bindings_shared::LazyCache;
82
- ///
83
- /// let cache: LazyCache<String> = LazyCache::new();
84
- /// assert!(!cache.is_cached());
85
- /// ```
86
- #[inline]
87
- pub const fn new() -> Self {
88
- Self {
89
- cache: RefCell::new(None),
90
- }
91
- }
92
-
93
- /// Get a cached reference or initialize via closure.
94
- ///
95
- /// If the value is already cached, returns a reference to it immediately
96
- /// without invoking the closure. On first call, invokes the closure, caches
97
- /// the result, and returns a reference.
98
- ///
99
- /// # Borrowing
100
- ///
101
- /// The returned reference is bound to the lifetime of the `LazyCache`.
102
- /// This is safe because the cache ensures the value persists for the
103
- /// lifetime of the `LazyCache` itself.
104
- ///
105
- /// # Panics
106
- ///
107
- /// Panics if the `RefCell` is currently borrowed mutably (e.g., from
108
- /// a nested call during initialization). This should not occur in normal
109
- /// single-threaded usage. This happens when `unwrap()` is called on a
110
- /// `RefCell` that is actively borrowed, which the runtime detects.
111
- ///
112
- /// # Example
113
- ///
114
- /// ```
115
- /// use spikard_bindings_shared::LazyCache;
116
- ///
117
- /// let cache = LazyCache::new();
118
- ///
119
- /// // First call: invokes closure
120
- /// let value1 = cache.get_or_init(|| 42);
121
- /// assert_eq!(*value1, 42);
122
- ///
123
- /// // Second call: returns cached value without invoking closure
124
- /// let value2 = cache.get_or_init(|| {
125
- /// panic!("This should not be called");
126
- /// // #[allow(unreachable_code)]
127
- /// // 999
128
- /// });
129
- /// assert_eq!(*value2, 42);
130
- /// ```
131
- #[must_use]
132
- pub fn get_or_init<F>(&self, init: F) -> &T
133
- where
134
- F: FnOnce() -> T,
135
- {
136
- // PERFORMANCE + SAFETY: Check if already cached without holding borrow.
137
- // This avoids the RefCell borrow guard and reduces overhead for cached hits.
138
- if self.cache.borrow().is_some() {
139
- // SAFETY: We verified the value exists. The returned reference is tied to
140
- // this function call's stack frame, but RefCell::map ensures it's valid
141
- // for the cache's lifetime. We map the borrow to extract &T directly.
142
- return unsafe {
143
- // Cast the raw pointer from RefCell's internal storage to &T.
144
- // This is safe because:
145
- // 1. The value is guaranteed to exist (Some branch)
146
- // 2. RefCell stores values contiguously; dereferencing is valid
147
- // 3. No RefCell borrow is held after this function returns
148
- // 4. The lifetime is correctly extended to the cache's lifetime
149
- let ptr = self.cache.as_ptr().cast_const();
150
- (*ptr).as_ref().unwrap_or_else(|| unreachable!())
151
- };
152
- }
153
-
154
- // Not cached; initialize and cache
155
- let value = init();
156
- *self.cache.borrow_mut() = Some(value);
157
-
158
- // SAFETY: We just set the value; same reasoning as above.
159
- unsafe {
160
- let ptr = self.cache.as_ptr().cast_const();
161
- (*ptr).as_ref().unwrap_or_else(|| unreachable!())
162
- }
163
- }
164
-
165
- /// Get a cached reference or initialize via fallible closure.
166
- ///
167
- /// Similar to `get_or_init`, but the closure returns a `Result`. If the closure
168
- /// returns `Err`, the error is returned and the cache remains uninitialized.
169
- /// Subsequent calls will re-attempt initialization.
170
- ///
171
- /// If the cache already contains a value, returns a reference without invoking
172
- /// the closure.
173
- ///
174
- /// # Panics
175
- ///
176
- /// Panics if the `RefCell` is currently borrowed mutably. This should not occur
177
- /// in normal single-threaded usage.
178
- ///
179
- /// # Errors
180
- ///
181
- /// Returns `Err(E)` if the initialization closure returns an error.
182
- /// The cache remains uninitialized, allowing subsequent retry attempts.
183
- ///
184
- /// # Example
185
- ///
186
- /// ```
187
- /// use spikard_bindings_shared::LazyCache;
188
- ///
189
- /// let cache: LazyCache<i32> = LazyCache::new();
190
- ///
191
- /// // First call: succeeds
192
- /// let result1 = cache.get_or_try_init::<_, String>(|| Ok(42));
193
- /// assert_eq!(result1, Ok(&42));
194
- ///
195
- /// // Second call: returns cached value
196
- /// let result2 = cache.get_or_try_init::<_, String>(|| {
197
- /// Err("This should not be called".to_string())
198
- /// });
199
- /// assert_eq!(result2, Ok(&42));
200
- ///
201
- /// // Failed initialization doesn't cache
202
- /// let cache2: LazyCache<i32> = LazyCache::new();
203
- /// let result3 = cache2.get_or_try_init::<_, String>(|| {
204
- /// Err("initialization failed".to_string())
205
- /// });
206
- /// assert!(result3.is_err());
207
- ///
208
- /// // Subsequent call re-attempts initialization
209
- /// let result4 = cache2.get_or_try_init::<_, String>(|| Ok(100));
210
- /// assert_eq!(result4, Ok(&100));
211
- /// ```
212
- pub fn get_or_try_init<F, E>(&self, init: F) -> Result<&T, E>
213
- where
214
- F: FnOnce() -> Result<T, E>,
215
- {
216
- // PERFORMANCE: Check if cached without holding the borrow.
217
- if self.cache.borrow().is_some() {
218
- // SAFETY: Same as `get_or_init`; value is guaranteed to exist.
219
- return Ok(unsafe {
220
- let ptr = self.cache.as_ptr().cast_const();
221
- (*ptr).as_ref().unwrap_or_else(|| unreachable!())
222
- });
223
- }
224
-
225
- // Not cached; attempt initialization
226
- let value = init()?;
227
- *self.cache.borrow_mut() = Some(value);
228
-
229
- // SAFETY: We just set the value; same reasoning as get_or_init.
230
- Ok(unsafe {
231
- let ptr = self.cache.as_ptr().cast_const();
232
- (*ptr).as_ref().unwrap_or_else(|| unreachable!())
233
- })
234
- }
235
-
236
- /// Check if a value is currently cached.
237
- ///
238
- /// Returns `true` if `get_or_init` or `get_or_try_init` has successfully
239
- /// cached a value, `false` otherwise.
240
- ///
241
- /// # Example
242
- ///
243
- /// ```
244
- /// use spikard_bindings_shared::LazyCache;
245
- ///
246
- /// let cache = LazyCache::new();
247
- /// assert!(!cache.is_cached());
248
- ///
249
- /// let _ = cache.get_or_init(|| 42);
250
- /// assert!(cache.is_cached());
251
- /// ```
252
- #[inline]
253
- #[must_use]
254
- pub fn is_cached(&self) -> bool {
255
- self.cache.borrow().is_some()
256
- }
257
-
258
- /// Clear the cached value.
259
- ///
260
- /// After invalidation, the cache behaves as if freshly created. The next call
261
- /// to `get_or_init` or `get_or_try_init` will re-invoke the initialization closure.
262
- ///
263
- /// # Example
264
- ///
265
- /// ```
266
- /// use spikard_bindings_shared::LazyCache;
267
- ///
268
- /// let cache = LazyCache::new();
269
- /// let v1 = cache.get_or_init(|| 42);
270
- /// assert_eq!(*v1, 42);
271
- ///
272
- /// cache.invalidate();
273
- /// assert!(!cache.is_cached());
274
- ///
275
- /// let call_count = std::cell::Cell::new(0);
276
- /// let v2 = cache.get_or_init(|| {
277
- /// call_count.set(call_count.get() + 1);
278
- /// 100
279
- /// });
280
- /// assert_eq!(*v2, 100);
281
- /// assert_eq!(call_count.get(), 1);
282
- /// ```
283
- #[inline]
284
- pub fn invalidate(&self) {
285
- *self.cache.borrow_mut() = None;
286
- }
287
-
288
- /// Attempt to unwrap and take ownership of the cached value.
289
- ///
290
- /// Returns the cached value if it exists, consuming the cache. If the cache
291
- /// is empty, returns `None`.
292
- ///
293
- /// This is useful when the `LazyCache` itself is being dropped or moved,
294
- /// and you want to recover the cached value.
295
- ///
296
- /// # Example
297
- ///
298
- /// ```
299
- /// use spikard_bindings_shared::LazyCache;
300
- ///
301
- /// let cache = LazyCache::new();
302
- /// let _ = cache.get_or_init(|| vec![1, 2, 3]);
303
- ///
304
- /// let value = cache.into_inner();
305
- /// assert_eq!(value, Some(vec![1, 2, 3]));
306
- /// ```
307
- #[inline]
308
- #[must_use]
309
- pub fn into_inner(self) -> Option<T> {
310
- self.cache.into_inner()
311
- }
312
- }
313
-
314
- // Implement Clone only if T is Clone
315
- impl<T: Clone> Clone for LazyCache<T> {
316
- fn clone(&self) -> Self {
317
- Self {
318
- cache: RefCell::new(self.cache.borrow().clone()),
319
- }
320
- }
321
- }
322
-
323
- #[cfg(test)]
324
- mod tests {
325
- use super::*;
326
- use std::cell::Cell;
327
- use std::rc::Rc;
328
-
329
- #[test]
330
- fn test_new_cache_is_empty() {
331
- let cache: LazyCache<i32> = LazyCache::new();
332
- assert!(!cache.is_cached());
333
- }
334
-
335
- #[test]
336
- fn test_get_or_init_initializes_once() {
337
- let cache = LazyCache::new();
338
- let call_count = Rc::new(Cell::new(0));
339
- let call_count_clone = call_count.clone();
340
-
341
- let value1 = cache.get_or_init(|| {
342
- call_count_clone.set(call_count_clone.get() + 1);
343
- 42
344
- });
345
- assert_eq!(*value1, 42);
346
- assert_eq!(call_count.get(), 1);
347
-
348
- // Second call should not invoke the closure
349
- let value2 = cache.get_or_init(|| {
350
- call_count.set(call_count.get() + 999);
351
- unreachable!()
352
- });
353
- assert_eq!(*value2, 42);
354
- assert_eq!(call_count.get(), 1); // Still 1, not 1000
355
- }
356
-
357
- #[test]
358
- fn test_get_or_init_returns_stable_reference() {
359
- let cache = LazyCache::new();
360
- let v1 = cache.get_or_init(|| "hello".to_string());
361
- let v2 = cache.get_or_init(|| "world".to_string());
362
-
363
- // Both should be the same value
364
- assert_eq!(v1, v2);
365
- assert_eq!(*v1, "hello");
366
- }
367
-
368
- #[test]
369
- fn test_is_cached_tracks_state() {
370
- let cache: LazyCache<i32> = LazyCache::new();
371
- assert!(!cache.is_cached());
372
-
373
- let _ = cache.get_or_init(|| 10);
374
- assert!(cache.is_cached());
375
-
376
- cache.invalidate();
377
- assert!(!cache.is_cached());
378
- }
379
-
380
- #[test]
381
- fn test_invalidate_forces_reinit() {
382
- let cache = LazyCache::new();
383
- let call_count = Rc::new(Cell::new(0));
384
-
385
- let call_count_clone1 = call_count.clone();
386
- let v1 = cache.get_or_init(|| {
387
- call_count_clone1.set(call_count_clone1.get() + 1);
388
- 100
389
- });
390
- assert_eq!(*v1, 100);
391
- assert_eq!(call_count.get(), 1);
392
-
393
- cache.invalidate();
394
- assert!(!cache.is_cached());
395
-
396
- let call_count_clone2 = call_count.clone();
397
- let v2 = cache.get_or_init(|| {
398
- call_count_clone2.set(call_count_clone2.get() + 1);
399
- 200
400
- });
401
- assert_eq!(*v2, 200);
402
- assert_eq!(call_count.get(), 2);
403
- }
404
-
405
- #[test]
406
- fn test_get_or_try_init_success() {
407
- let cache: LazyCache<String> = LazyCache::new();
408
- let call_count = Rc::new(Cell::new(0));
409
-
410
- let call_count_clone = call_count.clone();
411
- let result = cache.get_or_try_init::<_, &str>(|| {
412
- call_count_clone.set(call_count_clone.get() + 1);
413
- Ok("success".to_string())
414
- });
415
-
416
- assert_eq!(result, Ok(&"success".to_string()));
417
- assert_eq!(call_count.get(), 1);
418
- assert!(cache.is_cached());
419
- }
420
-
421
- #[test]
422
- fn test_get_or_try_init_failure_does_not_cache() {
423
- let cache: LazyCache<i32> = LazyCache::new();
424
- let call_count = Rc::new(Cell::new(0));
425
-
426
- let call_count_clone1 = call_count.clone();
427
- let result1 = cache.get_or_try_init::<_, String>(|| {
428
- call_count_clone1.set(call_count_clone1.get() + 1);
429
- Err("error1".to_string())
430
- });
431
-
432
- assert_eq!(result1, Err("error1".to_string()));
433
- assert!(!cache.is_cached());
434
- assert_eq!(call_count.get(), 1);
435
-
436
- // Second call should attempt initialization again
437
- let call_count_clone2 = call_count.clone();
438
- let result2 = cache.get_or_try_init::<_, String>(|| {
439
- call_count_clone2.set(call_count_clone2.get() + 1);
440
- Ok(42)
441
- });
442
-
443
- assert_eq!(result2, Ok(&42));
444
- assert!(cache.is_cached());
445
- assert_eq!(call_count.get(), 2);
446
- }
447
-
448
- #[test]
449
- fn test_get_or_try_init_cached_skips_closure() {
450
- let cache = LazyCache::new();
451
- let call_count = Rc::new(Cell::new(0));
452
-
453
- // First call succeeds
454
- let call_count_clone1 = call_count.clone();
455
- let result1 = cache.get_or_try_init::<_, &str>(|| {
456
- call_count_clone1.set(call_count_clone1.get() + 1);
457
- Ok(100)
458
- });
459
- assert_eq!(result1, Ok(&100));
460
- assert_eq!(call_count.get(), 1);
461
-
462
- // Second call returns cached value without invoking closure
463
- let call_count_clone2 = call_count.clone();
464
- let result2 = cache.get_or_try_init::<_, String>(|| {
465
- call_count_clone2.set(call_count_clone2.get() + 999);
466
- Err("should not reach".to_string())
467
- });
468
- assert_eq!(result2, Ok(&100));
469
- assert_eq!(call_count.get(), 1); // Not incremented
470
- }
471
-
472
- #[test]
473
- fn test_into_inner_with_value() {
474
- let cache = LazyCache::new();
475
- let _ = cache.get_or_init(|| vec![1, 2, 3]);
476
-
477
- let value = cache.into_inner();
478
- assert_eq!(value, Some(vec![1, 2, 3]));
479
- }
480
-
481
- #[test]
482
- fn test_into_inner_without_value() {
483
- let cache: LazyCache<i32> = LazyCache::new();
484
- let value = cache.into_inner();
485
- assert_eq!(value, None);
486
- }
487
-
488
- #[test]
489
- fn test_default_is_empty() {
490
- let cache: LazyCache<i32> = LazyCache::default();
491
- assert!(!cache.is_cached());
492
- }
493
-
494
- #[test]
495
- fn test_clone_copies_cached_state() {
496
- let cache = LazyCache::new();
497
- let _ = cache.get_or_init(|| 42);
498
-
499
- let _cloned = cache.clone();
500
- assert!(cache.is_cached());
501
- let value = cache.get_or_init(|| 0); // Should not reinit
502
- assert_eq!(*value, 42);
503
- }
504
-
505
- #[test]
506
- fn test_clone_empty_cache() {
507
- let cache: LazyCache<i32> = LazyCache::new();
508
- let _cloned = cache.clone();
509
- assert!(!cache.is_cached());
510
- }
511
-
512
- #[test]
513
- fn test_complex_type_conversion() {
514
- struct Complex {
515
- data: Vec<(String, i32)>,
516
- }
517
-
518
- let cache = LazyCache::new();
519
- let call_count = Rc::new(Cell::new(0));
520
-
521
- let call_count_clone = call_count.clone();
522
- let value = cache.get_or_init(|| {
523
- call_count_clone.set(call_count_clone.get() + 1);
524
- Complex {
525
- data: vec![("a".to_string(), 1), ("b".to_string(), 2)],
526
- }
527
- });
528
-
529
- assert_eq!(value.data.len(), 2);
530
- assert_eq!(value.data[0].0, "a");
531
- assert_eq!(call_count.get(), 1);
532
-
533
- // Second access doesn't reinit
534
- let _ = cache.get_or_init(|| {
535
- call_count.set(1000); // Would fail if called
536
- unreachable!()
537
- });
538
- assert_eq!(call_count.get(), 1);
539
- }
540
-
541
- #[test]
542
- fn test_lifetime_binding() {
543
- // This test verifies that the returned reference is properly bound
544
- // to the cache's lifetime
545
- let cache = LazyCache::new();
546
- let reference = cache.get_or_init(|| 123);
547
- assert_eq!(*reference, 123);
548
-
549
- // Reference should be valid for the entire cache's lifetime
550
- let reference2 = cache.get_or_init(|| 456);
551
- assert_eq!(*reference2, 123); // Still the cached value
552
- }
553
-
554
- #[test]
555
- fn test_zero_overhead_when_cached() {
556
- // This is more of a conceptual test; actual performance would require benchmarking
557
- let cache = LazyCache::new();
558
- let _ = cache.get_or_init(|| "initial".to_string());
559
-
560
- // Accessing cached value should be minimal overhead
561
- for _ in 0..1000 {
562
- let _ = cache.get_or_init(|| {
563
- panic!("Should not be called");
564
- });
565
- }
566
- }
567
-
568
- #[test]
569
- fn test_multiple_sequential_invalidations() {
570
- let cache = LazyCache::new();
571
- let call_count = Rc::new(Cell::new(0));
572
-
573
- for i in 0..3 {
574
- let call_count_clone = call_count.clone();
575
- let value = cache.get_or_init(|| {
576
- call_count_clone.set(call_count_clone.get() + 1);
577
- i * 100
578
- });
579
- assert_eq!(*value, i * 100);
580
-
581
- cache.invalidate();
582
- assert!(!cache.is_cached());
583
- }
584
-
585
- assert_eq!(call_count.get(), 3);
586
- }
587
- }
@@ -1,33 +0,0 @@
1
- //! Shared utilities for language bindings
2
- //!
3
- //! This crate provides common functionality used across all language bindings
4
- //! (Python, Node.js, Ruby, PHP, WASM) to eliminate code duplication and ensure
5
- //! consistent behavior.
6
-
7
- pub mod config_extractor;
8
- pub mod conversion_traits;
9
- pub mod di_traits;
10
- pub mod error_response;
11
- pub mod grpc_metadata;
12
- pub mod handler_base;
13
- pub mod json_conversion;
14
- pub mod lazy_cache;
15
- pub mod lifecycle_base;
16
- pub mod lifecycle_executor;
17
- pub mod response_builder;
18
- pub mod response_interpreter;
19
- pub mod test_client_base;
20
- pub mod validation_helpers;
21
-
22
- pub use config_extractor::{ConfigExtractor, ConfigSource};
23
- pub use di_traits::{FactoryDependencyAdapter, ValueDependencyAdapter};
24
- pub use error_response::ErrorResponseBuilder;
25
- pub use grpc_metadata::{extract_metadata_to_hashmap, hashmap_to_metadata};
26
- pub use handler_base::{HandlerError, HandlerExecutor, LanguageHandler};
27
- pub use json_conversion::{JsonConversionError, JsonConversionHelper, JsonConverter, JsonPrimitive};
28
- pub use lazy_cache::LazyCache;
29
- pub use lifecycle_executor::{
30
- HookResultData, LanguageLifecycleHook, LifecycleExecutor, RequestModifications, extract_body,
31
- };
32
- pub use response_builder::{build_optimized_response, build_optimized_response_bytes};
33
- pub use response_interpreter::{InterpretedResponse, ResponseInterpreter, StreamSource};