Skip to main content

topcoat_core/
context.rs

1mod context_map;
2mod id;
3
4pub use context_map::*;
5pub use id::*;
6
7use std::{any::Any, ops::Deref, sync::Arc};
8
9use crate::{abort::AbortStore, memoize::MemoizeCache};
10
11/// The request context.
12///
13/// Pages, layouts, components, and routes can take `cx: &Cx` as an optional
14/// parameter when they need request-scoped information; Topcoat passes it
15/// automatically. Use it to read values registered for the request with the
16/// app and request context helpers, such as [`app_context`] and
17/// [`request_context`].
18#[derive(Debug, Default)]
19pub struct Cx {
20    id: CxId,
21    app_context: Arc<ContextMap>,
22    request_context: ContextMap,
23    memoize_cache: MemoizeCache,
24    abort_store: AbortStore,
25}
26
27impl Cx {
28    /// Creates a `Cx` from the given app and request context maps.
29    fn new(app_context: Arc<ContextMap>, request_context: ContextMap) -> Self {
30        Self {
31            id: CxId::new(),
32            app_context,
33            request_context,
34            memoize_cache: MemoizeCache::new(),
35            abort_store: AbortStore::new(),
36        }
37    }
38
39    /// Returns this context's unique [`CxId`].
40    #[inline]
41    pub fn id(&self) -> CxId {
42        self.id
43    }
44}
45
46/// Assembles the request context for an in-flight request.
47///
48/// The router creates a `CxBuilder` over the shared app context, then threads
49/// `&mut CxBuilder` through the layers wrapping the matched route so each can
50/// register request-scoped values with [`insert`](Self::insert) before the
51/// route runs. Because a `CxBuilder` dereferences to the [`Cx`] it is building,
52/// app and request context can be read through it (with helpers such as
53/// [`app_context`] and [`request_context`]) while it is still being assembled.
54#[derive(Debug, Default)]
55pub struct CxBuilder {
56    cx: Cx,
57}
58
59impl CxBuilder {
60    /// Creates a builder over the shared app context, with an empty request
61    /// context.
62    #[must_use]
63    pub fn new(app_context: Arc<ContextMap>) -> Self {
64        Self {
65            cx: Cx::new(app_context, ContextMap::new()),
66        }
67    }
68
69    /// Registers `value` on the request context, returning the value previously
70    /// registered for `T`, if any.
71    ///
72    /// A type can hold only one value at a time, so registering a type that is
73    /// already present replaces it and hands back the displaced value.
74    pub fn insert<T>(&mut self, value: T) -> Option<T>
75    where
76        T: Any + Send + Sync,
77    {
78        self.cx.request_context.insert(value)
79    }
80
81    /// Returns `true` if a value of type `T` has been registered on the request
82    /// context.
83    #[must_use]
84    pub fn contains<T>(&self) -> bool
85    where
86        T: Any + Send + Sync,
87    {
88        self.cx.request_context.contains::<T>()
89    }
90
91    /// Returns a reference to the request context value of type `T`, or `None`
92    /// if no such value has been registered.
93    #[must_use]
94    pub fn get<T>(&self) -> Option<&T>
95    where
96        T: Any + Send + Sync,
97    {
98        self.cx.request_context.get::<T>()
99    }
100
101    /// Returns a mutable reference to the request context value of type `T`, or
102    /// `None` if no such value has been registered.
103    #[must_use]
104    pub fn get_mut<T>(&mut self) -> Option<&mut T>
105    where
106        T: Any + Send + Sync,
107    {
108        self.cx.request_context.get_mut::<T>()
109    }
110
111    /// Consumes the builder, returning the finished [`Cx`].
112    #[must_use]
113    pub fn build(self) -> Cx {
114        self.cx
115    }
116}
117
118impl Deref for CxBuilder {
119    type Target = Cx;
120
121    fn deref(&self) -> &Cx {
122        &self.cx
123    }
124}
125
126/// Assembles a [`Cx`] from scratch, for tests.
127///
128/// Unlike [`CxBuilder`], which only configures request context over an existing
129/// shared app context, `CxTestBuilder` populates both app and request context.
130#[derive(Debug, Default)]
131pub struct CxTestBuilder {
132    app_context: ContextMap,
133    request_context: ContextMap,
134}
135
136impl CxTestBuilder {
137    /// Creates an empty builder.
138    #[must_use]
139    pub fn new() -> Self {
140        Self::default()
141    }
142
143    /// Registers `value` on the app context.
144    #[must_use]
145    pub fn app_context<T>(mut self, value: T) -> Self
146    where
147        T: Any + Send + Sync,
148    {
149        self.app_context.insert(value);
150        self
151    }
152
153    /// Registers `value` on the request context.
154    #[must_use]
155    pub fn request_context<T>(mut self, value: T) -> Self
156    where
157        T: Any + Send + Sync,
158    {
159        self.request_context.insert(value);
160        self
161    }
162
163    /// Consumes the builder, returning the assembled [`Cx`].
164    #[must_use]
165    pub fn build(self) -> Cx {
166        Cx::new(Arc::new(self.app_context), self.request_context)
167    }
168}
169
170#[inline]
171#[doc(hidden)]
172pub fn memoize_cache(cx: &Cx) -> &MemoizeCache {
173    &cx.memoize_cache
174}
175
176#[inline]
177#[doc(hidden)]
178pub fn abort_store(cx: &Cx) -> &AbortStore {
179    &cx.abort_store
180}