Gnosco is a Rust-based escrow and badging application that integrates with the AT Protocol ecosystem..
1//! Web application context and dependency injection.
2//!
3//! Provides [`WebContext`] with all HTTP handler dependencies:
4//! OAuth config, storage, templates, and i18n. Uses Axum's `FromRef`.
5//!
6//! ## Dependency Injection Pattern
7//! The [`WebContext`] acts as a service container that implements Axum's
8//! `FromRef` trait, allowing handlers to extract specific dependencies
9//! (like `IdentityResolver`, `KeyProvider`) directly from function parameters
10//! without manual dependency passing.
11//!
12//! ## Available Dependencies
13//! - OAuth client configuration and request storage
14//! - AT Protocol identity resolution and key management
15//! - Escrow record storage backends
16//! - Template engines and i18n localization
17//! - HTTP client for external requests
18
19use atproto_identity::{
20 key::KeyProvider, resolve::SharedIdentityResolver, storage::DidDocumentStorage,
21};
22use atproto_oauth::storage::OAuthRequestStorage;
23use atproto_oauth_axum::state::OAuthClientConfig;
24use axum::extract::FromRef;
25use axum_template::engine::Engine;
26use std::{ops::Deref, sync::Arc};
27use unic_langid::LanguageIdentifier;
28
29#[cfg(feature = "reload")]
30use minijinja_autoreload::AutoReloader;
31
32#[cfg(feature = "reload")]
33pub type AppEngine = Engine<AutoReloader>;
34
35#[cfg(feature = "embed")]
36use minijinja::Environment;
37
38#[cfg(feature = "embed")]
39pub type AppEngine = Engine<Environment<'static>>;
40
41use crate::{config::Config, i18n::Locales, storage::escrow::EscrowRecordDataSource};
42
43pub struct I18nContext {
44 pub(crate) supported_languages: Vec<LanguageIdentifier>,
45 pub(crate) locales: Locales,
46}
47
48pub struct InnerWebContext {
49 pub(crate) engine: AppEngine,
50 pub(crate) http_client: reqwest::Client,
51 pub(crate) config: Config,
52 pub(crate) i18n_context: I18nContext,
53 pub(crate) oauth_client_config: OAuthClientConfig,
54 pub(crate) identity_resolver: SharedIdentityResolver,
55 pub(crate) key_provider: Arc<dyn KeyProvider + Send + Sync>,
56 pub(crate) oauth_storage: Arc<dyn OAuthRequestStorage + Send + Sync>,
57 pub(crate) document_storage: Arc<dyn DidDocumentStorage + Send + Sync>,
58 pub(crate) escrow_storage: Arc<dyn EscrowRecordDataSource + Send + Sync>,
59}
60
61#[derive(Clone, FromRef)]
62pub struct WebContext(pub(crate) Arc<InnerWebContext>);
63
64impl Deref for WebContext {
65 type Target = InnerWebContext;
66
67 fn deref(&self) -> &Self::Target {
68 &self.0
69 }
70}
71
72impl WebContext {
73 #[allow(clippy::too_many_arguments)]
74 pub fn new(
75 engine: AppEngine,
76 http_client: &reqwest::Client,
77 config: Config,
78 i18n_context: I18nContext,
79 oauth_client_config: OAuthClientConfig,
80 identity_resolver: SharedIdentityResolver,
81 key_provider: Arc<dyn KeyProvider + Send + Sync>,
82 oauth_storage: Arc<dyn OAuthRequestStorage + Send + Sync>,
83 document_storage: Arc<dyn DidDocumentStorage + Send + Sync>,
84 escrow_storage: Arc<dyn EscrowRecordDataSource + Send + Sync>,
85 ) -> Self {
86 Self(Arc::new(InnerWebContext {
87 engine,
88 http_client: http_client.clone(),
89 config,
90 i18n_context,
91 oauth_client_config,
92 identity_resolver,
93 key_provider,
94 oauth_storage,
95 document_storage,
96 escrow_storage,
97 }))
98 }
99}
100
101impl I18nContext {
102 pub fn new(supported_languages: Vec<LanguageIdentifier>, locales: Locales) -> Self {
103 Self {
104 supported_languages,
105 locales,
106 }
107 }
108}
109
110impl FromRef<WebContext> for SharedIdentityResolver {
111 fn from_ref(context: &WebContext) -> Self {
112 context.0.identity_resolver.clone()
113 }
114}
115
116impl FromRef<WebContext> for Arc<dyn KeyProvider + Send + Sync> {
117 fn from_ref(context: &WebContext) -> Self {
118 context.0.key_provider.clone()
119 }
120}
121
122impl FromRef<WebContext> for OAuthClientConfig {
123 fn from_ref(context: &WebContext) -> Self {
124 context.0.oauth_client_config.clone()
125 }
126}
127
128impl FromRef<WebContext> for Arc<dyn DidDocumentStorage + Send + Sync> {
129 fn from_ref(context: &WebContext) -> Self {
130 context.0.document_storage.clone()
131 }
132}
133
134impl FromRef<WebContext> for Arc<dyn atproto_identity::resolve::IdentityResolver + Send + Sync> {
135 fn from_ref(context: &WebContext) -> Self {
136 context.0.identity_resolver.0.clone()
137 }
138}
139
140impl FromRef<WebContext> for Arc<dyn DidDocumentStorage> {
141 fn from_ref(context: &WebContext) -> Self {
142 context.0.document_storage.clone()
143 }
144}
145
146impl FromRef<WebContext> for Arc<dyn atproto_identity::resolve::IdentityResolver> {
147 fn from_ref(context: &WebContext) -> Self {
148 context.0.identity_resolver.0.clone()
149 }
150}