Skip to main content

stepflow_flow/
error_stack.rs

1// Copyright 2025 DataStax Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
4// in compliance with the License. You may obtain a copy of the License at
5//
6//     http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software distributed under the License
9// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
10// or implied. See the License for the specific language governing permissions and limitations under
11// the License.
12
13/// A single entry in an error stack for detailed error reporting
14#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
15pub struct ErrorStackEntry {
16    /// The error message
17    pub error: String,
18    /// Additional context attached to this error
19    #[serde(skip_serializing_if = "Vec::is_empty", default)]
20    pub attachments: Vec<String>,
21    /// Backtrace if available
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub backtrace: Option<String>,
24}
25
26/// Error stack information for detailed system error debugging
27#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
28pub struct ErrorStack {
29    #[serde(skip_serializing_if = "Vec::is_empty", default)]
30    pub stack: Vec<ErrorStackEntry>,
31}
32
33impl ErrorStack {
34    /// Create an ErrorStack from an error_stack::Report, preserving the full stack trace
35    pub fn from_error_stack<T: error_stack::Context>(report: error_stack::Report<T>) -> Self {
36        let mut stack_entries: Vec<ErrorStackEntry> = Vec::new();
37        let mut current_attachments: Vec<String> = Vec::new();
38
39        // Extract global backtrace if available
40        let global_backtrace = {
41            let mut backtrace_iter = report.frames().filter_map(|frame| {
42                frame
43                    .sources()
44                    .iter()
45                    .find_map(|source| source.downcast_ref::<std::backtrace::Backtrace>())
46            });
47            backtrace_iter.next().map(|bt| bt.to_string())
48        };
49
50        for frame in report.frames() {
51            match frame.kind() {
52                error_stack::FrameKind::Context(context) => {
53                    // If we have accumulated attachments, add them to the previous entry
54                    if !current_attachments.is_empty()
55                        && !stack_entries.is_empty()
56                        && let Some(last_entry) = stack_entries.last_mut()
57                    {
58                        last_entry.attachments.append(&mut current_attachments);
59                    }
60
61                    // Add the context as a new stack entry
62                    // Only include backtrace on the first (top-level) error to avoid duplication
63                    let backtrace = if stack_entries.is_empty() {
64                        global_backtrace.clone()
65                    } else {
66                        None
67                    };
68
69                    stack_entries.push(ErrorStackEntry {
70                        error: context.to_string(),
71                        attachments: vec![],
72                        backtrace,
73                    });
74                }
75                error_stack::FrameKind::Attachment(attachment_kind) => {
76                    if let error_stack::AttachmentKind::Printable(printable) = attachment_kind {
77                        current_attachments.push(printable.to_string());
78                    }
79                }
80            }
81        }
82
83        // Add any remaining attachments to the last entry
84        if !current_attachments.is_empty()
85            && !stack_entries.is_empty()
86            && let Some(last_entry) = stack_entries.last_mut()
87        {
88            last_entry.attachments.extend(current_attachments);
89        }
90
91        Self {
92            stack: stack_entries,
93        }
94    }
95}
96
97impl<T: error_stack::Context> From<error_stack::Report<T>> for ErrorStack {
98    fn from(report: error_stack::Report<T>) -> Self {
99        Self::from_error_stack(report)
100    }
101}