1mod app_context;
2mod id;
3mod request_context;
4mod tracking;
5
6use std::{any::Any, panic::Location, sync::Arc};
7
8pub use app_context::*;
9pub use id::*;
10pub use request_context::*;
11pub(crate) use tracking::*;
12
13pub use crate::memoize::MemoizeAsRef;
14use crate::{
15 abort::AbortStore,
16 identity::{AmbiguousIdentityError, Identity, IdentityKey, SiteKey},
17 memoize::MemoizeCache,
18};
19
20#[derive(Debug, Default, Clone)]
31pub struct Cx {
32 state: Arc<CxState>,
34 identity: Identity,
36}
37
38impl Cx {
39 #[must_use]
42 pub fn new(app_context: Arc<AppContext>) -> Self {
43 Self::from_parts(app_context, RequestContext::new())
44 }
45
46 fn from_parts(app_context: Arc<AppContext>, request_context: RequestContext) -> Self {
48 Self {
49 state: Arc::new(CxState {
50 shared: Arc::new(RequestShared {
51 id: CxId::new(),
52 app_context,
53 memoize_cache: MemoizeCache::new(),
54 abort_store: AbortStore::new(),
55 }),
56 request_context: Arc::new(request_context),
57 tracker: None,
58 }),
59 identity: Identity::ROOT,
60 }
61 }
62
63 #[inline]
65 #[must_use]
66 pub fn id(&self) -> CxId {
67 self.state.shared.id
68 }
69
70 #[must_use]
91 #[track_caller]
92 pub fn keyed(&self, key: impl IdentityKey) -> Self {
93 let site = SiteKey::from_location(Location::caller());
94 Self {
95 identity: self.identity.keyed_child(site, key),
96 ..self.clone()
97 }
98 }
99
100 #[inline]
102 pub(crate) fn request_context(&self) -> &RequestContext {
103 &self.state.request_context
104 }
105
106 #[inline]
109 pub(crate) fn tracker(&self) -> Option<&ContextTracker> {
110 self.state.tracker.as_deref()
111 }
112
113 #[must_use]
119 pub fn with<T>(&self, value: T) -> Cx
120 where
121 T: Any + Send + Sync,
122 {
123 let mut request_context = (*self.state.request_context).clone();
124 request_context.insert(value);
125 self.scope(request_context)
126 }
127
128 #[must_use]
134 pub fn with_many<V>(&self, values: V) -> Cx
135 where
136 V: ContextValues,
137 {
138 let mut request_context = (*self.state.request_context).clone();
139 values.install(&mut request_context);
140 self.scope(request_context)
141 }
142
143 fn scope(&self, request_context: RequestContext) -> Cx {
146 Cx {
147 state: Arc::new(CxState {
148 shared: Arc::clone(&self.state.shared),
149 request_context: Arc::new(request_context),
150 tracker: self.state.tracker.clone(),
151 }),
152 identity: self.identity,
153 }
154 }
155
156 pub(crate) fn track(&self) -> (Cx, Arc<ContextTracker>) {
164 let tracker = Arc::new(ContextTracker::new(Arc::clone(&self.state.request_context)));
165 let child = Cx {
166 state: Arc::new(CxState {
167 shared: Arc::clone(&self.state.shared),
168 request_context: Arc::clone(&self.state.request_context),
169 tracker: Some(Arc::clone(&tracker)),
170 }),
171 identity: self.identity,
172 };
173 (child, tracker)
174 }
175}
176
177#[derive(Debug, Default)]
179struct CxState {
180 shared: Arc<RequestShared>,
181 request_context: Arc<RequestContext>,
182 tracker: Option<Arc<ContextTracker>>,
183}
184
185#[derive(Debug, Default)]
187struct RequestShared {
188 id: CxId,
189 app_context: Arc<AppContext>,
190 memoize_cache: MemoizeCache,
191 abort_store: AbortStore,
192}
193
194#[derive(Debug, Default)]
198pub struct CxTestBuilder {
199 app_context: AppContext,
200 request_context: RequestContext,
201}
202
203impl CxTestBuilder {
204 #[must_use]
206 pub fn new() -> Self {
207 Self::default()
208 }
209
210 #[must_use]
212 pub fn app_context<T>(mut self, value: T) -> Self
213 where
214 T: Any + Send + Sync,
215 {
216 self.app_context.insert(value);
217 self
218 }
219
220 #[must_use]
222 pub fn request_context<T>(mut self, value: T) -> Self
223 where
224 T: Any + Send + Sync,
225 {
226 self.request_context.insert(value);
227 self
228 }
229
230 #[must_use]
232 pub fn build(self) -> Cx {
233 Cx::from_parts(Arc::new(self.app_context), self.request_context)
234 }
235}
236
237#[must_use]
244#[track_caller]
245pub fn identity(cx: &Cx) -> Identity {
246 match try_identity(cx) {
247 Ok(identity) => identity,
248 Err(error) => panic!("{error}"),
249 }
250}
251
252#[track_caller]
263pub fn try_identity(cx: &Cx) -> Result<Identity, AmbiguousIdentityError> {
264 assert!(
265 cx.state.tracker.is_none(),
266 "identity cannot be read inside memoized functions"
267 );
268 cx.identity.checked()
269}
270
271#[doc(hidden)]
273#[must_use]
274pub fn identity_raw(cx: &Cx) -> Identity {
275 cx.identity
276}
277
278#[doc(hidden)]
280#[must_use]
281pub fn with_identity(cx: Cx, identity: Identity) -> Cx {
282 Cx { identity, ..cx }
283}
284
285#[inline]
286#[must_use]
287#[doc(hidden)]
288pub fn memoize_cache(cx: &Cx) -> &MemoizeCache {
289 &cx.state.shared.memoize_cache
290}
291
292#[inline]
293#[must_use]
294#[doc(hidden)]
295pub fn abort_store(cx: &Cx) -> &AbortStore {
296 &cx.state.shared.abort_store
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302
303 #[derive(Debug, PartialEq)]
304 struct Marker(u32);
305
306 #[derive(Debug, PartialEq)]
307 struct Other(&'static str);
308
309 #[test]
310 fn a_fresh_context_has_a_unique_id() {
311 let first = Cx::new(Arc::new(AppContext::new()));
312 let second = Cx::new(Arc::new(AppContext::new()));
313 assert_ne!(first.id(), second.id());
314 }
315
316 #[test]
317 fn keys_distinguish_locations_and_repetitions() {
318 fn child(cx: &Cx, key: u32) -> Cx {
319 cx.keyed(key)
320 }
321
322 let cx = Cx::default();
323 assert_ne!(identity(&cx.keyed(())), identity(&cx.keyed(())));
324 assert_eq!(
325 identity(&child(&cx, 1)),
326 identity(&child(&Cx::default(), 1))
327 );
328 assert_ne!(identity(&child(&cx, 1)), identity(&child(&cx, 2)));
329 assert_ne!(
330 identity(&child(&child(&cx, 1), 2)),
331 identity(&child(&cx, 2))
332 );
333 assert_eq!(identity(&cx), Identity::ROOT);
334 }
335
336 #[test]
337 fn keyed_contexts_share_request_state_and_preserve_their_scope() {
338 let cx = Cx::default().with(Marker(7));
339 let child = cx.keyed("child");
340 assert!(Arc::ptr_eq(&child.state, &cx.state));
341 assert_eq!(child.id(), cx.id());
342 assert!(std::ptr::eq(memoize_cache(&child), memoize_cache(&cx)));
343 assert!(std::ptr::eq(
344 request_context::<Marker>(&child),
345 request_context::<Marker>(&cx)
346 ));
347
348 let expected = identity(&child);
349 assert_eq!(identity(&child.clone()), expected);
350 assert_eq!(identity(&child.with(Other("value"))), expected);
351 assert_eq!(identity(&child.with_many((Other("value"),))), expected);
352 assert_eq!(identity_raw(&child.track().0), expected);
353 }
354
355 #[test]
356 #[should_panic(expected = "identity cannot be read inside memoized functions")]
357 fn memoized_functions_cannot_read_identity() {
358 let cx = Cx::default();
359 memoize_cache(&cx).memoize(&cx, (), (), |cx, ()| {
360 identity(&cx.keyed(()).with(Marker(7)))
361 });
362 }
363
364 #[test]
365 #[should_panic(expected = "identity cannot be read inside memoized functions")]
366 fn memoized_functions_cannot_try_identity() {
367 let cx = Cx::default();
368 memoize_cache(&cx).memoize(&cx, (), (), |cx, ()| try_identity(&cx));
369 }
370
371 #[tokio::test]
372 #[should_panic(expected = "identity cannot be read inside memoized functions")]
373 async fn memoized_functions_cannot_read_identity_after_suspension() {
374 let cx = Cx::default();
375 memoize_cache(&cx)
376 .memoize_async(&cx, (), (), |cx, ()| async move {
377 tokio::task::yield_now().await;
378 identity(&cx)
379 })
380 .await;
381 }
382
383 #[test]
384 fn with_registers_a_value_on_the_child() {
385 let cx = Cx::default();
386 let child = cx.with(Marker(1));
387
388 assert_eq!(try_request_context::<Marker>(&cx), None);
389 assert_eq!(request_context::<Marker>(&child), &Marker(1));
390 }
391
392 #[test]
393 fn with_shadows_without_touching_the_parent() {
394 let cx = Cx::default().with(Marker(1));
395 let child = cx.with(Marker(2));
396
397 assert_eq!(request_context::<Marker>(&cx), &Marker(1));
398 assert_eq!(request_context::<Marker>(&child), &Marker(2));
399 }
400
401 #[test]
402 fn a_child_inherits_the_parent_context() {
403 let cx = CxTestBuilder::new()
404 .app_context(Other("app"))
405 .request_context(Marker(7))
406 .build();
407 let child = cx.with(Other("request"));
408
409 assert_eq!(request_context::<Marker>(&child), &Marker(7));
410 assert_eq!(request_context::<Other>(&child), &Other("request"));
411 assert_eq!(app_context::<Other>(&child), &Other("app"));
412 }
413
414 #[test]
415 fn with_many_registers_every_value() {
416 let cx = Cx::default().with_many((Marker(1), Other("many")));
417
418 assert_eq!(request_context::<Marker>(&cx), &Marker(1));
419 assert_eq!(request_context::<Other>(&cx), &Other("many"));
420 }
421
422 #[test]
423 fn a_child_shares_the_request_state() {
424 let cx = Cx::default();
425 let child = cx.with(Marker(1));
426
427 assert_eq!(child.id(), cx.id());
428 assert!(std::ptr::eq(memoize_cache(&child), memoize_cache(&cx)));
429 assert!(std::ptr::eq(abort_store(&child), abort_store(&cx)));
430 }
431
432 #[test]
433 fn clones_outlive_the_original() {
434 let cx = CxTestBuilder::new().request_context(Marker(7)).build();
435 let id = cx.id();
436 let handle = cx.clone();
437 drop(cx);
438
439 assert_eq!(request_context::<Marker>(&handle).0, 7);
440 assert_eq!(handle.id(), id);
441 }
442
443 #[test]
444 fn handles_are_send_and_sync() {
445 fn assert_send_sync<T: Send + Sync>() {}
446 assert_send_sync::<Cx>();
447 }
448}