1use std::fmt::Display;
2use std::sync::Arc;
3
4use cookie::Key;
5
6use crate::auth::{Gates, LoginThrottle};
7use crate::cache::Cache;
8use crate::db::Db;
9use crate::events::Listeners;
10use crate::i18n::Translator;
11use crate::mail::Mailer;
12use crate::queue::Queue;
13use crate::storage::Storage;
14use crate::{Config, Result, RouteTable, Views};
15
16#[derive(Clone)]
24pub struct AppState(Arc<AppStateInner>);
25
26impl std::ops::Deref for AppState {
27 type Target = AppStateInner;
28
29 fn deref(&self) -> &AppStateInner {
30 &self.0
31 }
32}
33
34impl AppState {
35 pub(crate) fn new(inner: AppStateInner) -> Self {
37 AppState(Arc::new(inner))
38 }
39}
40
41#[non_exhaustive]
44pub struct AppStateInner {
45 pub config: Arc<Config>,
47 pub(crate) routes: Arc<RouteTable>,
49 pub(crate) views: Views,
51 pub db: Db,
53 pub mailer: Mailer,
55 pub(crate) mailers: Arc<std::collections::HashMap<String, Mailer>>,
57 pub queue: Queue,
59 pub cache: Cache,
61 pub storage: Storage,
63 pub(crate) disks: Arc<std::collections::HashMap<String, Storage>>,
65 pub http: crate::http::Http,
67 pub(crate) translator: Arc<Translator>,
69 pub(crate) live: Option<Arc<crate::live::Live>>,
71 pub(crate) notification_hub: Arc<crate::auth::notifications::Hub>,
73 pub(crate) listeners: Listeners,
74 pub(crate) key: Key,
75 pub(crate) session_mirror:
78 Option<Arc<std::sync::Mutex<std::collections::HashMap<String, String>>>>,
79 pub(crate) gates: Gates,
80 pub(crate) async_gates: Arc<std::collections::HashMap<String, crate::auth::AsyncGate>>,
81 pub(crate) throttle: Arc<LoginThrottle>,
82 pub(crate) second_factor: Option<Arc<crate::auth::second_factor::SecondFactor>>,
84 pub(crate) account_sections: Arc<Vec<crate::auth::account::AccountSection>>,
86 pub(crate) auth: Option<Arc<crate::auth::module::Settings>>,
88 pub(crate) detect_locale: bool,
90 pub(crate) security: Arc<crate::security::Security>,
91 pub(crate) webhooks: crate::webhook::Handlers,
92 pub(crate) shares: Arc<Vec<(String, crate::view::ShareFn)>>,
94 pub(crate) channels:
96 Arc<std::collections::HashMap<String, crate::auth::notifications::ChannelFn>>,
97 pub(crate) provided: crate::provided::ProvidedMap,
99 pub(crate) inspector: Option<Arc<crate::inspector::Inspector>>,
101 pub(crate) limiters: Arc<std::collections::HashMap<String, crate::rate_limit::NamedLimiter>>,
103 pub(crate) reporters: Arc<Vec<crate::report::ReportFn>>,
105 pub(crate) fakes: Arc<Fakes>,
107}
108
109#[derive(Debug, Clone)]
111#[non_exhaustive]
112pub struct SentNotification {
113 pub kind: &'static str,
115 pub to: crate::auth::Recipient,
117}
118
119#[derive(Debug, Clone, PartialEq)]
122#[non_exhaustive]
123pub struct SentBroadcast {
124 pub user_id: Option<i64>,
126 pub event: String,
128 pub data: serde_json::Value,
130}
131
132#[derive(Default)]
135pub(crate) struct Fakes {
136 pub events: std::sync::Mutex<Option<Vec<Box<dyn std::any::Any + Send>>>>,
137 pub notifications: std::sync::Mutex<Option<Vec<SentNotification>>>,
138 pub broadcasts: std::sync::Mutex<Option<Vec<SentBroadcast>>>,
139}
140
141impl Fakes {
142 pub(crate) fn record_event<E: Send + 'static>(&self, event: E) -> bool {
144 let mut events = self.events.lock().unwrap_or_else(|e| e.into_inner());
145 match events.as_mut() {
146 Some(list) => {
147 list.push(Box::new(event));
148 true
149 }
150 None => false,
151 }
152 }
153
154 pub(crate) fn record_broadcast(&self, broadcast: SentBroadcast) -> bool {
156 let mut sent = self.broadcasts.lock().unwrap_or_else(|e| e.into_inner());
157 match sent.as_mut() {
158 Some(list) => {
159 list.push(broadcast);
160 true
161 }
162 None => false,
163 }
164 }
165
166 pub(crate) fn record_notification(
168 &self,
169 kind: &'static str,
170 to: &crate::auth::Recipient,
171 ) -> bool {
172 let mut sent = self.notifications.lock().unwrap_or_else(|e| e.into_inner());
173 match sent.as_mut() {
174 Some(list) => {
175 list.push(SentNotification {
176 kind,
177 to: to.clone(),
178 });
179 true
180 }
181 None => false,
182 }
183 }
184}
185
186impl AppState {
187 pub fn url(&self, name: &str, params: &[&dyn Display]) -> Result<String> {
189 Ok(self.routes.url(name, params)?)
190 }
191
192 pub fn encrypt(&self, plain: &str) -> String {
205 crate::crypto::seal(&self.key, plain)
206 }
207
208 pub fn decrypt(&self, sealed: &str) -> Result<String> {
211 Ok(crate::crypto::open(&self.key, sealed)?)
212 }
213}
214
215impl AppState {
216 pub fn disk_named(&self, name: &str) -> Result<&Storage> {
219 self.disks.get(name).ok_or_else(|| {
220 anyhow::anyhow!("no disk named `{name}`: add it with `App::disk(\"{name}\", …)`").into()
221 })
222 }
223}