1use std::sync::Arc;
2use thiserror::Error;
3
4#[derive(Debug, Clone)]
5pub struct Span {
6 pub start: usize,
7 pub end: usize,
8}
9
10#[derive(Debug, Clone)]
11pub struct SourceLocation {
12 pub file: Option<String>,
13 pub line: usize,
14 pub column: usize,
15 pub span: Option<Span>,
16}
17
18impl std::fmt::Display for SourceLocation {
19 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20 match &self.file {
21 Some(file) => write!(f, "{}:{}:{}", file, self.line, self.column),
22 None => write!(f, "line {}:{}", self.line, self.column),
23 }
24 }
25}
26
27#[derive(Debug, Clone)]
28pub struct StackFrame {
29 pub function_name: String,
30 pub file: Option<String>,
31 pub line: usize,
32 pub column: usize,
33}
34
35impl std::fmt::Display for StackFrame {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 let location = match &self.file {
38 Some(file) => format!("{}:{}:{}", file, self.line, self.column),
39 None => format!("line {}:{}", self.line, self.column),
40 };
41 write!(f, " at {} ({})", self.function_name, location)
42 }
43}
44
45#[derive(Debug, Clone, Default)]
46pub struct StackTrace {
47 pub frames: Vec<StackFrame>,
48}
49
50impl StackTrace {
51 pub fn new() -> Self {
52 Self { frames: Vec::new() }
53 }
54
55 pub fn push(&mut self, frame: StackFrame) {
56 self.frames.push(frame);
57 }
58
59 pub fn is_empty(&self) -> bool {
60 self.frames.is_empty()
61 }
62}
63
64impl std::fmt::Display for StackTrace {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 if self.frames.is_empty() {
67 return Ok(());
68 }
69 writeln!(f, "Stack trace (most recent call last):")?;
70 for frame in &self.frames {
71 writeln!(f, "{}", frame)?;
72 }
73 Ok(())
74 }
75}
76
77#[derive(Debug, Clone, Error)]
78pub enum BlueprintError {
79 #[error("Parse error at {location}: {message}")]
80 ParseError {
81 location: SourceLocation,
82 message: String,
83 },
84
85 #[error("Type error: expected {expected}, got {actual}")]
86 TypeError { expected: String, actual: String },
87
88 #[error("Name error: undefined variable '{name}'")]
89 NameError { name: String },
90
91 #[error("Import error: {message}")]
92 ImportError { message: String },
93
94 #[error("Attribute error: '{type_name}' has no attribute '{attr}'")]
95 AttributeError { type_name: String, attr: String },
96
97 #[error("Index error: {message}")]
98 IndexError { message: String },
99
100 #[error("Key error: key not found: {key}")]
101 KeyError { key: String },
102
103 #[error("Value error: {message}")]
104 ValueError { message: String },
105
106 #[error("Argument error: {message}")]
107 ArgumentError { message: String },
108
109 #[error("Division by zero")]
110 DivisionByZero,
111
112 #[error("I/O error: {path}: {message}")]
113 IoError { path: String, message: String },
114
115 #[error("HTTP error: {url}: {message}")]
116 HttpError { url: String, message: String },
117
118 #[error("Process error: {command}: {message}")]
119 ProcessError { command: String, message: String },
120
121 #[error("JSON error: {message}")]
122 JsonError { message: String },
123
124 #[error("Glob error: {message}")]
125 GlobError { message: String },
126
127 #[error("Assertion failed: {message}")]
128 AssertionError { message: String },
129
130 #[error("{message}")]
131 UserError { message: String },
132
133 #[error("Not callable: {type_name}")]
134 NotCallable { type_name: String },
135
136 #[error("Internal error: {message}")]
137 InternalError { message: String },
138
139 #[error("Unsupported: {message}")]
140 Unsupported { message: String },
141
142 #[error("break")]
143 Break,
144
145 #[error("continue")]
146 Continue,
147
148 #[error("return")]
149 Return { value: Arc<crate::Value> },
150
151 #[error("exit with code {code}")]
152 Exit { code: i32 },
153
154 #[error("")]
155 Silent,
156
157 #[error("{error}")]
158 WithStack {
159 error: Box<BlueprintError>,
160 stack: StackTrace,
161 location: Option<SourceLocation>,
162 },
163}
164
165impl BlueprintError {
166 pub fn with_file(self, file: String) -> Self {
167 match self {
168 BlueprintError::ParseError { location, message } => BlueprintError::ParseError {
169 location: SourceLocation {
170 file: Some(file),
171 ..location
172 },
173 message,
174 },
175 other => other,
176 }
177 }
178
179 pub fn is_control_flow(&self) -> bool {
180 matches!(
181 self,
182 BlueprintError::Break | BlueprintError::Continue | BlueprintError::Return { .. } | BlueprintError::Exit { .. }
183 )
184 }
185
186 pub fn with_stack_frame(self, frame: StackFrame) -> Self {
187 if self.is_control_flow() {
188 return self;
189 }
190
191 match self {
192 BlueprintError::WithStack { error, mut stack, location } => {
193 stack.push(frame);
194 BlueprintError::WithStack { error, stack, location }
195 }
196 other => {
197 let mut stack = StackTrace::new();
198 stack.push(frame);
199 BlueprintError::WithStack {
200 error: Box::new(other),
201 stack,
202 location: None,
203 }
204 }
205 }
206 }
207
208 pub fn with_location(self, loc: SourceLocation) -> Self {
209 if self.is_control_flow() {
210 return self;
211 }
212
213 match self {
214 BlueprintError::WithStack { error, stack, location: _ } => {
215 BlueprintError::WithStack { error, stack, location: Some(loc) }
216 }
217 other => {
218 BlueprintError::WithStack {
219 error: Box::new(other),
220 stack: StackTrace::new(),
221 location: Some(loc),
222 }
223 }
224 }
225 }
226
227 pub fn stack_trace(&self) -> Option<&StackTrace> {
228 match self {
229 BlueprintError::WithStack { stack, .. } => Some(stack),
230 _ => None,
231 }
232 }
233
234 pub fn error_location(&self) -> Option<&SourceLocation> {
235 match self {
236 BlueprintError::WithStack { location, .. } => location.as_ref(),
237 BlueprintError::ParseError { location, .. } => Some(location),
238 _ => None,
239 }
240 }
241
242 pub fn inner_error(&self) -> &BlueprintError {
243 match self {
244 BlueprintError::WithStack { error, .. } => error.inner_error(),
245 other => other,
246 }
247 }
248
249 pub fn format_with_stack(&self) -> String {
250 let mut result = String::new();
251
252 if let Some(loc) = self.error_location() {
253 result.push_str(&format!("Error at {}: ", loc));
254 } else {
255 result.push_str("Error: ");
256 }
257
258 result.push_str(&format!("{}", self.inner_error()));
259
260 if let Some(stack) = self.stack_trace() {
261 if !stack.is_empty() {
262 result.push_str("\n\n");
263 result.push_str(&format!("{}", stack));
264 }
265 }
266
267 result
268 }
269}
270
271pub type Result<T> = std::result::Result<T, BlueprintError>;