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