1use std::{
14 sync::{Arc, Mutex, PoisonError},
15 time::Duration,
16};
17
18use serde::{Deserialize, Serialize};
19
20use crate::{
21 effect::{EffectFamily, EffectId, HandlerKey},
22 wasm_compat::{WasmCompatSend, WasmCompatSync},
23};
24
25mod adapter;
26pub use adapter::{
27 AdapterAnalysis, AdapterContext, AdapterEnding, AdapterErrorBoundary, AdapterErrorEnvelope,
28 AdapterEvent, AdapterObservation, AdapterUsage, AdapterVerdict, ObservationSink,
29 diagnostic_url_secrets, scrub_diagnostic,
30};
31pub(crate) use adapter::{AdapterSlot, ObservedError};
32
33#[cfg(test)]
34mod tests;
35
36#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
38pub struct Observation {
39 pub seq: u64,
41 pub subject: Subject,
43 pub stage: Stage,
45 pub emitter: Emitter,
47 pub action: Action,
49 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub at: Option<Duration>,
53}
54
55impl Observation {
56 pub fn new(subject: Subject, stage: Stage, emitter: Emitter, action: Action) -> Self {
58 Self {
59 seq: 0,
60 subject,
61 stage,
62 emitter,
63 action,
64 at: None,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
75pub struct Subject {
76 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub scope: Option<String>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub order: Option<u64>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub effect: Option<EffectId>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub parent: Option<EffectId>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub key: Option<HandlerKey>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub family: Option<EffectFamily>,
95}
96
97impl Subject {
98 pub fn scoped(scope: impl Into<String>) -> Self {
100 Self {
101 scope: Some(scope.into()),
102 ..Self::default()
103 }
104 }
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "snake_case")]
111pub enum Stage {
112 Handler,
114 Host,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub struct Emitter {
121 pub name: String,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub version: Option<String>,
127}
128
129impl Emitter {
130 pub fn named(name: impl Into<String>) -> Self {
132 Self {
133 name: name.into(),
134 version: None,
135 }
136 }
137
138 pub fn versioned(name: impl Into<String>, version: impl Into<String>) -> Self {
140 Self {
141 name: name.into(),
142 version: Some(version.into()),
143 }
144 }
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150pub struct Reason {
151 pub code: String,
153 #[serde(default, skip_serializing_if = "Option::is_none")]
155 pub detail: Option<String>,
156}
157
158impl Reason {
159 pub fn with_detail(code: impl Into<String>, detail: impl Into<String>) -> Self {
161 Self {
162 code: code.into(),
163 detail: Some(detail.into()),
164 }
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
171#[serde(tag = "action", rename_all = "snake_case")]
172pub enum Action {
173 Adapter {
175 observation: AdapterObservation,
177 },
178 Host {
181 kind: String,
183 payload: serde_json::Value,
185 },
186}
187
188pub trait HostAction: Serialize + serde::de::DeserializeOwned {
192 const KIND: &'static str;
194
195 fn action(&self) -> Result<Action, serde_json::Error> {
198 Ok(Action::Host {
199 kind: Self::KIND.to_owned(),
200 payload: serde_json::to_value(self)?,
201 })
202 }
203
204 fn from_action(action: &Action) -> Option<Result<Self, serde_json::Error>> {
206 match action {
207 Action::Host { kind, payload } if kind == Self::KIND => {
208 Some(serde_json::from_value(payload.clone()))
209 }
210 _ => None,
211 }
212 }
213}
214
215pub trait Clock: WasmCompatSend + WasmCompatSync {
218 fn elapsed(&self) -> Duration;
220}
221
222pub trait Witness: WasmCompatSend + WasmCompatSync + 'static {
225 fn observe(&self, observation: Observation);
227}
228
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
231pub struct ObservationTrace {
232 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub session: Option<String>,
236 pub observations: Vec<Observation>,
238 #[serde(default)]
241 pub dropped: u64,
242 #[serde(default)]
244 pub finalized: bool,
245}
246
247impl ObservationTrace {
248 pub fn is_complete(&self) -> bool {
250 self.dropped == 0
251 }
252}
253
254pub struct ObservationLog {
263 inner: Mutex<LogState>,
264 capacity: usize,
265 ring: bool,
266 clock: Option<Arc<dyn Clock + Send + Sync>>,
267}
268
269struct LogState {
270 session: Option<String>,
271 observations: std::collections::VecDeque<Observation>,
272 next: u64,
273 dropped: u64,
274 finalized: bool,
275}
276
277impl std::fmt::Debug for ObservationLog {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 let state = self.lock();
280 f.debug_struct("ObservationLog")
281 .field("observations", &state.observations.len())
282 .field("dropped", &state.dropped)
283 .field("capacity", &self.capacity)
284 .finish_non_exhaustive()
285 }
286}
287
288pub const DEFAULT_CAPACITY: usize = 65_536;
290
291impl Default for ObservationLog {
292 fn default() -> Self {
293 Self::with_capacity(DEFAULT_CAPACITY)
294 }
295}
296
297impl ObservationLog {
298 pub fn with_capacity(capacity: usize) -> Self {
301 Self {
302 inner: Mutex::new(LogState {
303 session: None,
304 observations: std::collections::VecDeque::new(),
305 next: 0,
306 dropped: 0,
307 finalized: false,
308 }),
309 capacity,
310 ring: false,
311 clock: None,
312 }
313 }
314
315 pub fn ring(capacity: usize) -> Self {
318 Self {
319 ring: true,
320 ..Self::with_capacity(capacity.max(1))
322 }
323 }
324
325 pub fn drain(&self) -> ObservationTrace {
330 let mut state = self.lock();
331 let trace = ObservationTrace {
332 session: state.session.clone(),
333 observations: state.observations.drain(..).collect(),
334 dropped: state.dropped,
335 finalized: state.finalized,
336 };
337 state.dropped = 0;
338 trace
339 }
340
341 pub fn with_clock(mut self, clock: Arc<dyn Clock + Send + Sync>) -> Self {
343 self.clock = Some(clock);
344 self
345 }
346
347 pub fn with_session(self, session: impl Into<String>) -> Self {
349 self.lock().session = Some(session.into());
350 self
351 }
352
353 fn lock(&self) -> std::sync::MutexGuard<'_, LogState> {
354 self.inner.lock().unwrap_or_else(PoisonError::into_inner)
355 }
356
357 pub fn finalize(&self) {
361 self.lock().finalized = true;
362 }
363
364 pub fn trace(&self) -> ObservationTrace {
366 let state = self.lock();
367 ObservationTrace {
368 session: state.session.clone(),
369 observations: state.observations.iter().cloned().collect(),
370 dropped: state.dropped,
371 finalized: state.finalized,
372 }
373 }
374
375 pub fn len(&self) -> usize {
377 self.lock().observations.len()
378 }
379
380 pub fn is_empty(&self) -> bool {
382 self.lock().observations.is_empty()
383 }
384}
385
386impl Witness for ObservationLog {
387 fn observe(&self, mut observation: Observation) {
388 let at = self.clock.as_ref().map(|clock| clock.elapsed());
389 let mut state = self.lock();
390 state.finalized = false;
391 observation.seq = state.next;
392 state.next += 1;
393 if state.observations.len() >= self.capacity {
394 state.dropped += 1;
395 if !self.ring {
396 return;
397 }
398 state.observations.pop_front();
399 }
400 observation.at = at;
401 state.observations.push_back(observation);
402 }
403}
404
405impl<W: Witness + ?Sized> Witness for Arc<W> {
406 fn observe(&self, observation: Observation) {
407 (**self).observe(observation);
408 }
409}
410
411const _: fn() = || {
413 fn assert_wire<T: Clone + Send + Sync + 'static + Serialize + serde::de::DeserializeOwned>() {}
414 assert_wire::<Observation>();
415 assert_wire::<ObservationTrace>();
416};