1use crate::clock::{Clock, SystemClock};
2use crate::config::{AppConfig, is_local_development_environment};
3use crate::db::DbPool;
4use crate::events::EventPublisher;
5use crate::execution_logs::{ExecutionLogProvider, PostgresExecutionLogProvider};
6use crate::health::HealthRegistry;
7use crate::ids::{IdGenerator, UuidGenerator};
8use crate::runtime_config::{RuntimeConfigProvider, StaticRuntimeConfigProvider};
9use crate::shutdown::Shutdown;
10use crate::telemetry_query::{NoopTelemetrySpanProvider, TelemetrySpanProvider};
11use serde::{Deserialize, Serialize};
12use std::fmt::Debug;
13use std::sync::Arc;
14
15#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
16pub struct CorrelationId(pub String);
17
18impl CorrelationId {
19 pub fn new(value: impl Into<String>) -> Self {
20 Self(value.into())
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
25pub struct RequestId(pub String);
26
27impl RequestId {
28 pub fn new(value: impl Into<String>) -> Self {
29 Self(value.into())
30 }
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
34pub struct TenantId(pub String);
35
36#[derive(Debug, Clone, Default, Deserialize, Serialize)]
37pub struct TraceContext {
38 pub trace_id: Option<String>,
39 pub span_id: Option<String>,
40 pub baggage: Vec<(String, String)>,
41}
42
43#[derive(Debug, Clone, Deserialize, Serialize)]
44#[serde(tag = "kind", rename_all = "snake_case")]
45pub enum ActorContext {
46 Anonymous,
47 User {
48 user_id: String,
49 scopes: Vec<String>,
50 },
51 Service {
52 service_id: String,
53 scopes: Vec<String>,
54 },
55 System,
56}
57
58impl Default for ActorContext {
59 fn default() -> Self {
60 Self::Anonymous
61 }
62}
63
64#[derive(Debug, Clone, Default)]
65pub struct ActorResolutionRequest {
66 pub authorization: Option<String>,
67 pub cookie: Option<String>,
68}
69
70#[async_trait::async_trait]
71pub trait ActorResolver: Debug + Send + Sync {
72 async fn resolve_actor(&self, request: ActorResolutionRequest) -> ActorContext;
73}
74
75#[derive(Debug, Clone)]
76pub struct DevActorResolver {
77 environment: String,
78}
79
80impl DevActorResolver {
81 #[must_use]
82 pub fn new(environment: impl Into<String>) -> Self {
83 Self {
84 environment: environment.into(),
85 }
86 }
87}
88
89#[async_trait::async_trait]
90impl ActorResolver for DevActorResolver {
91 async fn resolve_actor(&self, request: ActorResolutionRequest) -> ActorContext {
92 let _ = request.cookie;
93 request
94 .authorization
95 .and_then(|value| parse_dev_bearer_actor(&value, &self.environment))
96 .unwrap_or_default()
97 }
98}
99
100fn parse_dev_bearer_actor(value: &str, environment: &str) -> Option<ActorContext> {
101 if !is_local_development_environment(environment) {
102 return None;
103 }
104
105 let token = value.strip_prefix("Bearer ")?;
106
107 if let Some(user_id) = token.strip_prefix("dev-user:") {
108 return Some(ActorContext::User {
109 user_id: user_id.to_owned(),
110 scopes: Vec::new(),
111 });
112 }
113
114 if let Some(service_token) = token.strip_prefix("dev-service:") {
115 let (service_id, scopes) = parse_dev_actor_scopes(service_token);
116 return Some(ActorContext::Service { service_id, scopes });
117 }
118
119 None
120}
121
122fn parse_dev_actor_scopes(value: &str) -> (String, Vec<String>) {
123 let Some((id, raw_scopes)) = value.split_once(':') else {
124 return (value.to_owned(), Vec::new());
125 };
126 let scopes = raw_scopes
127 .split(',')
128 .filter(|scope| !scope.is_empty())
129 .map(ToOwned::to_owned)
130 .collect();
131 (id.to_owned(), scopes)
132}
133
134#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)]
135#[serde(default)]
136pub struct ClientRequestMetadata {
137 pub ip: Option<String>,
138 pub user_agent: Option<String>,
139}
140
141impl ClientRequestMetadata {
142 #[must_use]
143 pub fn is_empty(&self) -> bool {
144 self.ip.is_none() && self.user_agent.is_none()
145 }
146}
147
148#[derive(Debug, Clone, Deserialize, Serialize)]
149pub struct RequestContext {
150 pub request_id: RequestId,
151 pub correlation_id: CorrelationId,
152 pub trace: TraceContext,
153 pub actor: ActorContext,
154 pub tenant_id: Option<TenantId>,
155 pub causation_id: Option<String>,
156 #[serde(default)]
157 pub client: ClientRequestMetadata,
158}
159
160impl RequestContext {
161 pub fn new(request_id: RequestId, correlation_id: CorrelationId) -> Self {
162 Self {
163 request_id,
164 correlation_id,
165 trace: TraceContext::default(),
166 actor: ActorContext::Anonymous,
167 tenant_id: None,
168 causation_id: None,
169 client: ClientRequestMetadata::default(),
170 }
171 }
172}
173
174#[derive(Clone)]
175pub struct AppContext {
176 pub config: Arc<AppConfig>,
177 pub db: DbPool,
178 pub redis: Option<crate::RedisConnection>,
179 pub actor_resolver: Arc<dyn ActorResolver>,
180 pub clock: Arc<dyn Clock>,
181 pub ids: Arc<dyn IdGenerator>,
182 pub events: Arc<dyn EventPublisher>,
183 pub telemetry_spans: Arc<dyn TelemetrySpanProvider>,
184 pub execution_logs: Arc<dyn ExecutionLogProvider>,
185 pub runtime_config: Arc<dyn RuntimeConfigProvider>,
186 pub health: HealthRegistry,
187 pub shutdown: Shutdown,
188}
189
190impl Debug for AppContext {
191 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
192 formatter
193 .debug_struct("AppContext")
194 .field("config", &self.config)
195 .field("db", &"<pool>")
196 .field("redis", &self.redis.as_ref().map(|_| "<connection>"))
197 .field("actor_resolver", &self.actor_resolver)
198 .field("telemetry_spans", &self.telemetry_spans)
199 .field("execution_logs", &self.execution_logs)
200 .field("runtime_config", &self.runtime_config)
201 .field("health", &self.health)
202 .field("shutdown", &self.shutdown)
203 .finish_non_exhaustive()
204 }
205}
206
207impl AppContext {
208 pub fn new(config: AppConfig, db: DbPool, events: Arc<dyn EventPublisher>) -> Self {
209 let execution_logs = Arc::new(PostgresExecutionLogProvider::new(db.clone()));
210 let actor_resolver = Arc::new(DevActorResolver::new(config.service.environment.clone()));
211 Self {
212 config: Arc::new(config),
213 db,
214 redis: None,
215 actor_resolver,
216 clock: Arc::new(SystemClock),
217 ids: Arc::new(UuidGenerator),
218 events,
219 telemetry_spans: Arc::new(NoopTelemetrySpanProvider),
220 execution_logs,
221 runtime_config: Arc::new(StaticRuntimeConfigProvider::empty()),
222 health: HealthRegistry::default(),
223 shutdown: Shutdown::new(),
224 }
225 }
226
227 pub fn with_actor_resolver(mut self, actor_resolver: Arc<dyn ActorResolver>) -> Self {
228 self.actor_resolver = actor_resolver;
229 self
230 }
231
232 pub fn with_redis(mut self, redis: Option<crate::RedisConnection>) -> Self {
233 self.redis = redis;
234 self
235 }
236
237 pub fn with_telemetry_span_provider(
238 mut self,
239 telemetry_spans: Arc<dyn TelemetrySpanProvider>,
240 ) -> Self {
241 self.telemetry_spans = telemetry_spans;
242 self
243 }
244
245 pub fn with_execution_log_provider(
246 mut self,
247 execution_logs: Arc<dyn ExecutionLogProvider>,
248 ) -> Self {
249 self.execution_logs = execution_logs;
250 self
251 }
252
253 pub fn with_runtime_config_provider(
254 mut self,
255 runtime_config: Arc<dyn RuntimeConfigProvider>,
256 ) -> Self {
257 self.runtime_config = runtime_config;
258 self
259 }
260}