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}