use serde::Deserialize;
use serde::Serialize;
use ts_rs::TS;
use super::MatchContext;
use super::SourceLocation;
use super::event::EventSeq;
use super::event::TimeoutValue;
use super::span::SpanId;
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct StackFrame {
pub span: SpanId,
pub kind: String,
pub name: Option<String>,
pub args: Vec<(String, String)>,
pub alias: Option<String>,
pub location: Option<SourceLocation>,
}
impl StackFrame {
pub fn is_fn_call(&self) -> bool {
self.kind == "fn-call" || self.kind == "pure-fn-call"
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
#[serde(tag = "type", rename_all = "kebab-case")]
pub enum FailureRecord {
MatchTimeout {
span: SpanId,
event_seq: EventSeq,
shell: String,
pattern: String,
effective: TimeoutValue,
call_stack: Vec<StackFrame>,
buffer_tail: String,
vars_in_scope: Vec<(String, String)>,
},
FailPatternMatched {
span: SpanId,
event_seq: EventSeq,
shell: String,
pattern: String,
matched_line: String,
call_stack: Vec<StackFrame>,
buffer_tail: String,
vars_in_scope: Vec<(String, String)>,
},
ShellExited {
span: SpanId,
event_seq: EventSeq,
shell: String,
exit_code: Option<i32>,
call_stack: Vec<StackFrame>,
buffer_tail: String,
vars_in_scope: Vec<(String, String)>,
},
Runtime {
span: Option<SpanId>,
event_seq: Option<EventSeq>,
shell: Option<String>,
message: String,
call_stack: Vec<StackFrame>,
vars_in_scope: Vec<(String, String)>,
},
PureMatch {
span: SpanId,
event_seq: EventSeq,
match_context: MatchContext,
value: String,
pattern: String,
is_regex: bool,
call_stack: Vec<StackFrame>,
vars_in_scope: Vec<(String, String)>,
},
MultiMatch {
span: SpanId,
event_seq: EventSeq,
shell: String,
patterns: Vec<super::event::MultiMatchPattern>,
matched: Vec<usize>,
effective: TimeoutValue,
call_stack: Vec<StackFrame>,
buffer_tail: String,
vars_in_scope: Vec<(String, String)>,
},
}
#[cfg(test)]
mod tests {
use super::*;
use crate::observe::structured::event::MultiMatchPattern;
#[test]
fn failure_record_multimatch_round_trips_serde() {
let original = FailureRecord::MultiMatch {
span: 3,
event_seq: 11,
shell: "default".into(),
patterns: vec![
MultiMatchPattern {
pattern: "^a$".into(),
is_regex: true,
},
MultiMatchPattern {
pattern: "b".into(),
is_regex: false,
},
],
matched: vec![1],
effective: TimeoutValue::Assertion {
duration: "5s".into(),
source: None,
},
call_stack: vec![],
buffer_tail: "tail".into(),
vars_in_scope: vec![("k".into(), "v".into())],
};
let json = serde_json::to_string(&original).unwrap();
assert!(json.contains("\"type\":\"multi-match\""), "json: {json}");
let parsed: FailureRecord = serde_json::from_str(&json).unwrap();
match parsed {
FailureRecord::MultiMatch {
matched, patterns, ..
} => {
assert_eq!(matched, vec![1usize]);
assert_eq!(patterns.len(), 2);
}
other => panic!("expected MultiMatch, got {other:?}"),
}
}
fn stack_frame(kind: &str) -> StackFrame {
StackFrame {
span: 1,
kind: kind.to_string(),
name: None,
args: vec![],
alias: None,
location: None,
}
}
#[test]
fn is_fn_call_true_for_fn_call() {
assert!(stack_frame("fn-call").is_fn_call());
}
#[test]
fn is_fn_call_true_for_pure_fn_call() {
assert!(stack_frame("pure-fn-call").is_fn_call());
}
#[test]
fn is_fn_call_false_for_other_kinds() {
assert!(!stack_frame("shell-block").is_fn_call());
}
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[cfg_attr(
feature = "ts-export",
ts(export, export_to = "../../../viewer/src/types/")
)]
pub struct CancellationRecord {
pub reason: super::event::CancelReasonRecord,
pub span: Option<SpanId>,
pub event_seq: Option<EventSeq>,
pub shell: Option<String>,
pub call_stack: Vec<StackFrame>,
}