renox_core/provided.rs
1//! Values the app provides with `App::provide`.
2
3use std::any::{Any, TypeId};
4use std::collections::HashMap;
5use std::ops::Deref;
6use std::sync::Arc;
7
8use axum::extract::FromRequestParts;
9use axum::http::request::Parts;
10
11use crate::{AppState, Error};
12
13pub(crate) type ProvidedMap = Arc<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>;
14
15impl AppState {
16 /// A value given to `App::provide`, if one of type `T` was.
17 pub fn provided<T: Send + Sync + 'static>(&self) -> Option<Arc<T>> {
18 self.provided
19 .get(&TypeId::of::<T>())
20 .cloned()
21 .and_then(|value| value.downcast::<T>().ok())
22 }
23}
24
25/// The app's own shared values (an API client, a price list), available in
26/// handlers, jobs, listeners, commands and scheduled tasks.
27///
28/// ```
29/// # use renox::prelude::*;
30/// use renox::Provided;
31///
32/// #[derive(Clone)]
33/// struct Payments {
34/// api_key: String,
35/// }
36///
37/// async fn checkout(payments: Provided<Payments>) -> String {
38/// format!("charging with key {}…", &payments.api_key[..3])
39/// }
40///
41/// async fn in_a_job(state: AppState) {
42/// let payments = state.provided::<Payments>().expect("provided at boot");
43/// # let _ = payments;
44/// }
45///
46/// # let _ =
47/// App::new().provide(Payments { api_key: "sk_test_123".into() })
48/// # ;
49/// ```
50///
51/// A handler asking for a type that wasn't provided answers 500, naming the type.
52pub struct Provided<T>(pub Arc<T>);
53
54impl<T> Deref for Provided<T> {
55 type Target = T;
56
57 fn deref(&self) -> &T {
58 &self.0
59 }
60}
61
62impl<T: Send + Sync + 'static> FromRequestParts<AppState> for Provided<T> {
63 type Rejection = Error;
64
65 async fn from_request_parts(_: &mut Parts, state: &AppState) -> Result<Self, Error> {
66 state.provided::<T>().map(Provided).ok_or_else(|| {
67 Error::Internal(anyhow::anyhow!(
68 "no {} was provided: add `App::provide(…)`",
69 std::any::type_name::<T>()
70 ))
71 })
72 }
73}