relux_runtime/observe/structured/
failure.rs1use serde::Deserialize;
2use serde::Serialize;
3use ts_rs::TS;
4
5use super::MatchContext;
6use super::SourceLocation;
7use super::event::EventSeq;
8use super::event::TimeoutValue;
9use super::span::SpanId;
10
11#[derive(Debug, Clone, Serialize, Deserialize, TS)]
12#[cfg_attr(
13 feature = "ts-export",
14 ts(export, export_to = "../../../viewer/src/types/")
15)]
16pub struct StackFrame {
17 pub span: SpanId,
18 pub kind: String,
20 pub name: Option<String>,
22 pub args: Vec<(String, String)>,
23 pub alias: Option<String>,
26 pub location: Option<SourceLocation>,
27}
28
29impl StackFrame {
30 pub fn is_fn_call(&self) -> bool {
34 self.kind == "fn-call" || self.kind == "pure-fn-call"
35 }
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, TS)]
48#[cfg_attr(
49 feature = "ts-export",
50 ts(export, export_to = "../../../viewer/src/types/")
51)]
52#[serde(tag = "type", rename_all = "kebab-case")]
53pub enum FailureRecord {
54 MatchTimeout {
55 span: SpanId,
56 event_seq: EventSeq,
57 shell: String,
58 pattern: String,
59 effective: TimeoutValue,
61 call_stack: Vec<StackFrame>,
62 buffer_tail: String,
63 vars_in_scope: Vec<(String, String)>,
64 },
65 FailPatternMatched {
66 span: SpanId,
67 event_seq: EventSeq,
68 shell: String,
69 pattern: String,
70 matched_line: String,
71 call_stack: Vec<StackFrame>,
72 buffer_tail: String,
73 vars_in_scope: Vec<(String, String)>,
74 },
75 ShellExited {
76 span: SpanId,
77 event_seq: EventSeq,
78 shell: String,
79 exit_code: Option<i32>,
80 call_stack: Vec<StackFrame>,
81 buffer_tail: String,
82 vars_in_scope: Vec<(String, String)>,
83 },
84 Runtime {
85 span: Option<SpanId>,
86 event_seq: Option<EventSeq>,
87 shell: Option<String>,
88 message: String,
89 call_stack: Vec<StackFrame>,
90 vars_in_scope: Vec<(String, String)>,
91 },
92 PureMatch {
93 span: SpanId,
94 event_seq: EventSeq,
95 match_context: MatchContext,
96 value: String,
97 pattern: String,
98 is_regex: bool,
99 call_stack: Vec<StackFrame>,
100 vars_in_scope: Vec<(String, String)>,
101 },
102 MultiMatch {
103 span: SpanId,
104 event_seq: EventSeq,
105 shell: String,
106 patterns: Vec<super::event::MultiMatchPattern>,
108 matched: Vec<usize>,
111 effective: TimeoutValue,
113 call_stack: Vec<StackFrame>,
114 buffer_tail: String,
115 vars_in_scope: Vec<(String, String)>,
116 },
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122 use crate::observe::structured::event::MultiMatchPattern;
123
124 #[test]
125 fn failure_record_multimatch_round_trips_serde() {
126 let original = FailureRecord::MultiMatch {
127 span: 3,
128 event_seq: 11,
129 shell: "default".into(),
130 patterns: vec![
131 MultiMatchPattern {
132 pattern: "^a$".into(),
133 is_regex: true,
134 },
135 MultiMatchPattern {
136 pattern: "b".into(),
137 is_regex: false,
138 },
139 ],
140 matched: vec![1],
141 effective: TimeoutValue::Assertion {
142 duration: "5s".into(),
143 source: None,
144 },
145 call_stack: vec![],
146 buffer_tail: "tail".into(),
147 vars_in_scope: vec![("k".into(), "v".into())],
148 };
149 let json = serde_json::to_string(&original).unwrap();
150 assert!(json.contains("\"type\":\"multi-match\""), "json: {json}");
151 let parsed: FailureRecord = serde_json::from_str(&json).unwrap();
152 match parsed {
153 FailureRecord::MultiMatch {
154 matched, patterns, ..
155 } => {
156 assert_eq!(matched, vec![1usize]);
157 assert_eq!(patterns.len(), 2);
158 }
159 other => panic!("expected MultiMatch, got {other:?}"),
160 }
161 }
162
163 fn stack_frame(kind: &str) -> StackFrame {
164 StackFrame {
165 span: 1,
166 kind: kind.to_string(),
167 name: None,
168 args: vec![],
169 alias: None,
170 location: None,
171 }
172 }
173
174 #[test]
175 fn is_fn_call_true_for_fn_call() {
176 assert!(stack_frame("fn-call").is_fn_call());
177 }
178
179 #[test]
180 fn is_fn_call_true_for_pure_fn_call() {
181 assert!(stack_frame("pure-fn-call").is_fn_call());
182 }
183
184 #[test]
185 fn is_fn_call_false_for_other_kinds() {
186 assert!(!stack_frame("shell-block").is_fn_call());
187 }
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, TS)]
195#[cfg_attr(
196 feature = "ts-export",
197 ts(export, export_to = "../../../viewer/src/types/")
198)]
199pub struct CancellationRecord {
200 pub reason: super::event::CancelReasonRecord,
201 pub span: Option<SpanId>,
202 pub event_seq: Option<EventSeq>,
203 pub shell: Option<String>,
204 pub call_stack: Vec<StackFrame>,
205}