blueprint_core/extract/
context.rs

1use crate::extract::{FromJobCallParts, FromRef};
2use crate::job::call::Parts;
3use core::{
4    convert::Infallible,
5    ops::{Deref, DerefMut},
6};
7
8/// Extractor for context.
9///
10/// Context is global and used in every request a router with context receives.
11/// For accessing data derived from calls, see [`Extension`].
12///
13/// [`Extension`]: crate::extract::Extension
14///
15/// # With `Router`
16///
17/// ```
18/// use blueprint_sdk::extract::Context;
19/// use blueprint_sdk::{Job, Router};
20///
21/// // The application context
22/// //
23/// // Here you can put configuration, database connection pools, or whatever
24/// // context you need
25/// #[derive(Clone)]
26/// struct AppContext {}
27///
28/// let context = AppContext {};
29///
30/// const MY_JOB_ID: u32 = 0;
31///
32/// // create a `Router` that holds our context
33/// let app = Router::new()
34///     .route(MY_JOB_ID, handler)
35///     // provide the context so the router can access it
36///     .with_context(context);
37///
38/// async fn handler(
39///     // access the context via the `Context` extractor
40///     // extracting a context of the wrong type results in a compile error
41///     Context(context): Context<AppContext>,
42/// ) {
43///     // use `context`...
44/// }
45/// # let _: Router = app;
46/// ```
47///
48/// Note that [`Context`] is an extractor, so be sure to put it before any body
49/// extractors, see ["the order of extractors"][order-of-extractors].
50///
51/// [order-of-extractors]: crate::extract#the-order-of-extractors
52///
53/// # With `Job`
54///
55/// ```
56/// use blueprint_sdk::Job;
57/// use blueprint_sdk::extract::Context;
58///
59/// #[derive(Clone)]
60/// struct AppContext {}
61///
62/// let context = AppContext {};
63///
64/// async fn job(Context(context): Context<AppContext>) {
65///     // use `context`...
66/// }
67///
68/// // provide the context so the job can access it
69/// let job_with_context = job.with_context(context);
70/// ```
71///
72/// # Sub-Contexts
73///
74/// [`Context`] only allows a single context type, but you can use [`FromRef`] to extract "sub-contexts":
75///
76/// ```
77/// use blueprint_sdk::Router;
78/// use blueprint_sdk::extract::{Context, FromRef};
79///
80/// // the application context
81/// #[derive(Clone)]
82/// struct AppContext {
83///     // that holds some api specific context
84///     api_state: ApiContext,
85/// }
86///
87/// // the api specific context
88/// #[derive(Clone)]
89/// struct ApiContext {}
90///
91/// // support converting an `AppContext` in an `ApiContext`
92/// impl FromRef<AppContext> for ApiContext {
93///     fn from_ref(app_state: &AppContext) -> ApiContext {
94///         app_state.api_state.clone()
95///     }
96/// }
97///
98/// let context = AppContext {
99///     api_state: ApiContext {},
100/// };
101///
102/// const HANDLER_JOB_ID: u32 = 0;
103/// const FETCH_API_JOB_ID: u32 = 1;
104///
105/// let app = Router::new()
106///     .route(HANDLER_JOB_ID, handler)
107///     .route(FETCH_API_JOB_ID, fetch_api)
108///     .with_context(context);
109///
110/// async fn fetch_api(
111///     // access the api specific context
112///     Context(api_state): Context<ApiContext>,
113/// ) {
114/// }
115///
116/// async fn handler(
117///     // we can still access to top level context
118///     Context(context): Context<AppContext>,
119/// ) {
120/// }
121/// # let _: Router = app;
122/// ```
123///
124/// For convenience `FromRef` can also be derived using `#[derive(FromRef)]`.
125///
126/// # For library authors
127///
128/// If you're writing a library that has an extractor that needs context, this is the recommended way
129/// to do it:
130///
131/// ```rust
132/// use blueprint_sdk::extract::{FromJobCallParts, FromRef};
133/// use blueprint_sdk::job::call::Parts;
134/// use std::convert::Infallible;
135///
136/// // the extractor your library provides
137/// struct MyLibraryExtractor;
138///
139/// impl<S> FromJobCallParts<S> for MyLibraryExtractor
140/// where
141///     // keep `S` generic but require that it can produce a `MyLibraryContext`
142///     // this means users will have to implement `FromRef<UserContext> for MyLibraryContext`
143///     MyLibraryContext: FromRef<S>,
144///     S: Send + Sync,
145/// {
146///     type Rejection = Infallible;
147///
148///     async fn from_job_call_parts(
149///         parts: &mut Parts,
150///         context: &S,
151///     ) -> Result<Self, Self::Rejection> {
152///         // get a `MyLibraryContext` from a reference to the context
153///         let context = MyLibraryContext::from_ref(context);
154///
155///         // ...
156///         # unimplemented!()
157///     }
158/// }
159///
160/// // the context your library needs
161/// struct MyLibraryContext {
162///     // ...
163/// }
164/// ```
165///
166/// # Shared mutable context
167///
168/// [As context is global within a `Router`][global] you can't directly get a mutable reference to
169/// the context.
170///
171/// The most basic solution is to use an `Arc<Mutex<_>>`. Which kind of mutex you need depends on
172/// your use case. See [the tokio docs] for more details.
173///
174/// Note that holding a locked `std::sync::Mutex` across `.await` points will result in `!Send`
175/// futures which are incompatible with `blueprint_sdk`. If you need to hold a mutex across `.await` points,
176/// consider using a `tokio::sync::Mutex` instead.
177///
178/// ## Example
179///
180/// ```
181/// use blueprint_sdk::Router;
182/// use blueprint_sdk::extract::Context;
183/// use std::sync::{Arc, Mutex};
184///
185/// #[derive(Clone)]
186/// struct AppContext {
187///     data: Arc<Mutex<String>>,
188/// }
189///
190/// const MY_JOB_ID: u8 = 0;
191///
192/// async fn job(Context(context): Context<AppContext>) {
193///     {
194///         let mut data = context.data.lock().expect("mutex was poisoned");
195///         *data = "updated foo".to_owned();
196///     }
197///
198///     // ...
199/// }
200///
201/// let context = AppContext {
202///     data: Arc::new(Mutex::new("foo".to_owned())),
203/// };
204///
205/// let app = Router::new().route(MY_JOB_ID, job).with_context(context);
206/// # let _: Router = app;
207/// ```
208///
209/// [global]: https://docs.rs/blueprint-sdk/latest/blueprint_sdk/struct.Router.html#method.with_context
210/// [the tokio docs]: https://docs.rs/tokio/1.25.0/tokio/sync/struct.Mutex.html#which-kind-of-mutex-should-you-use
211#[derive(Debug, Default, Clone, Copy)]
212pub struct Context<S>(pub S);
213
214impl<OuterContext, InnerContext> FromJobCallParts<OuterContext> for Context<InnerContext>
215where
216    InnerContext: FromRef<OuterContext>,
217    OuterContext: Send + Sync,
218{
219    type Rejection = Infallible;
220
221    async fn from_job_call_parts(
222        _parts: &mut Parts,
223        ctx: &OuterContext,
224    ) -> Result<Self, Self::Rejection> {
225        let inner_state = InnerContext::from_ref(ctx);
226        Ok(Self(inner_state))
227    }
228}
229
230impl<S> Deref for Context<S> {
231    type Target = S;
232
233    fn deref(&self) -> &Self::Target {
234        &self.0
235    }
236}
237
238impl<S> DerefMut for Context<S> {
239    fn deref_mut(&mut self) -> &mut Self::Target {
240        &mut self.0
241    }
242}