1use crate::{env::EnvBinding, Result};
2use serde::{Deserialize, Serialize};
3use serde_json::Value as JsonValue;
4use wasm_bindgen::{JsCast, JsValue};
5use worker_sys::TailEvent as TailEventSys;
6
7#[derive(Debug, Clone)]
8pub struct TailEvent(TailEventSys);
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub enum TailEventKind {
13 Outcome,
14 SpanOpen,
15 SpanClose,
16 DiagnosticChannel,
17 Exception,
18 Log,
19 Return,
20 Attributes,
21 Unknown(String),
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct TypedTailEvent {
26 pub kind: TailEventKind,
27 pub raw: JsonValue,
28}
29
30unsafe impl Send for TailEvent {}
31unsafe impl Sync for TailEvent {}
32
33impl TailEvent {
34 pub fn invocation_id(&self) -> Option<String> {
35 self.0.invocation_id()
36 }
37
38 pub fn span_context(&self) -> JsValue {
39 self.0.span_context()
40 }
41
42 pub fn timestamp(&self) -> Option<f64> {
43 self.0.timestamp()
44 }
45
46 pub fn sequence(&self) -> Option<u64> {
47 sequence_from_js(self.0.sequence())
48 }
49
50 pub fn event_raw(&self) -> JsValue {
51 self.0.event()
52 }
53
54 pub fn event_typed(&self) -> Result<TypedTailEvent> {
55 let raw: JsonValue = serde_wasm_bindgen::from_value(self.0.event())?;
56 Ok(parse_typed_tail_event(raw))
57 }
58}
59
60fn sequence_from_js(sequence: Option<f64>) -> Option<u64> {
61 sequence.map(|value| value.round() as u64)
62}
63
64fn parse_typed_tail_event(raw: JsonValue) -> TypedTailEvent {
65 let kind = raw
66 .get("type")
67 .and_then(JsonValue::as_str)
68 .map(parse_tail_event_kind)
69 .unwrap_or_else(|| TailEventKind::Unknown("unknown".to_string()));
70 TypedTailEvent { kind, raw }
71}
72
73fn parse_tail_event_kind(raw_type: &str) -> TailEventKind {
74 match raw_type {
75 "outcome" => TailEventKind::Outcome,
76 "spanOpen" => TailEventKind::SpanOpen,
77 "spanClose" => TailEventKind::SpanClose,
78 "diagnosticChannel" => TailEventKind::DiagnosticChannel,
79 "exception" => TailEventKind::Exception,
80 "log" => TailEventKind::Log,
81 "return" => TailEventKind::Return,
82 "attributes" => TailEventKind::Attributes,
83 other => TailEventKind::Unknown(other.to_string()),
84 }
85}
86
87impl From<TailEventSys> for TailEvent {
88 fn from(value: TailEventSys) -> Self {
89 Self(value)
90 }
91}
92
93impl EnvBinding for TailEvent {
94 const TYPE_NAME: &'static str = "Object";
95
96 fn get(val: JsValue) -> Result<Self> {
97 if !val.is_object() {
98 return Err("Binding cannot be cast to TailEvent from non-object value".into());
99 }
100
101 let has_event = js_sys::Reflect::has(&val, &JsValue::from("event"))?;
102 if !has_event {
103 return Err("Binding cannot be cast to TailEvent: missing `event` field".into());
104 }
105
106 Ok(Self(val.unchecked_into()))
107 }
108}
109
110impl AsRef<JsValue> for TailEvent {
111 fn as_ref(&self) -> &JsValue {
112 &self.0
113 }
114}
115
116impl JsCast for TailEvent {
117 fn instanceof(val: &JsValue) -> bool {
118 val.is_object()
119 }
120
121 fn unchecked_from_js(val: JsValue) -> Self {
122 Self(val.unchecked_into())
123 }
124
125 fn unchecked_from_js_ref(val: &JsValue) -> &Self {
126 unsafe { &*(val as *const JsValue as *const Self) }
127 }
128}
129
130impl From<TailEvent> for JsValue {
131 fn from(value: TailEvent) -> Self {
132 value.0.into()
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::{parse_typed_tail_event, sequence_from_js, TailEventKind};
139
140 #[test]
141 fn known_tail_variant_is_parsed() {
142 let parsed = parse_typed_tail_event(serde_json::json!({
143 "type": "log",
144 "message": "hello"
145 }));
146 assert_eq!(parsed.kind, TailEventKind::Log);
147 }
148
149 #[test]
150 fn unknown_tail_variant_falls_back_safely() {
151 let parsed = parse_typed_tail_event(serde_json::json!({
152 "type": "newFutureType",
153 "payload": { "x": 1 }
154 }));
155 assert_eq!(
156 parsed.kind,
157 TailEventKind::Unknown("newFutureType".to_string())
158 );
159 }
160
161 #[test]
162 fn sequence_conversion_rounds_from_js_number() {
163 assert_eq!(sequence_from_js(Some(10.2)), Some(10));
164 assert_eq!(sequence_from_js(Some(10.7)), Some(11));
165 assert_eq!(sequence_from_js(None), None);
166 }
167}