1use std::path::PathBuf;
2
3#[derive(Debug, Clone)]
8pub struct ErrorContext {
9 pub file: Option<PathBuf>,
11 pub macro_stack: Vec<String>,
13 pub include_stack: Vec<PathBuf>,
15}
16
17impl From<crate::eval::LocationContext> for ErrorContext {
18 fn from(loc: crate::eval::LocationContext) -> Self {
19 ErrorContext {
20 file: loc.file,
21 macro_stack: loc.macro_stack,
22 include_stack: loc.include_stack,
23 }
24 }
25}
26
27impl core::fmt::Display for ErrorContext {
28 fn fmt(
29 &self,
30 f: &mut core::fmt::Formatter<'_>,
31 ) -> core::fmt::Result {
32 if !self.macro_stack.is_empty() {
34 writeln!(f, " Macro stack:")?;
35 for (i, macro_name) in self.macro_stack.iter().rev().enumerate() {
36 if i == 0 {
37 writeln!(f, " in macro: {}", macro_name)?;
38 } else {
39 writeln!(f, " called from: {}", macro_name)?;
40 }
41 }
42 }
43
44 if !self.include_stack.is_empty() {
46 writeln!(f, " File stack:")?;
47 for (i, file_path) in self.include_stack.iter().rev().enumerate() {
48 let file_str = file_path.to_str().unwrap_or("???");
49 if i == 0 {
50 writeln!(f, " in file: {}", file_str)?;
51 } else {
52 writeln!(f, " included from: {}", file_str)?;
53 }
54 }
55 } else if let Some(file) = &self.file {
56 writeln!(f, " File: {}", file.display())?;
57 }
58
59 Ok(())
60 }
61}
62
63#[derive(Debug, thiserror::Error)]
64pub enum XacroError {
65 #[error("{source}\n\nContext:\n{context}")]
70 WithContext {
71 #[source]
72 source: Box<XacroError>,
73 context: ErrorContext,
74 },
75 #[error("IO error: {0}")]
76 Io(#[from] std::io::Error),
77
78 #[error("XML error: {0}")]
79 Xml(#[from] xmltree::ParseError),
80
81 #[error("Include error: {0}")]
82 Include(String),
83
84 #[error("Macro error: {0}")]
85 UndefinedMacro(String),
86
87 #[error("Missing parameter '{param}' in macro '{macro_name}'")]
88 MissingParameter { macro_name: String, param: String },
89
90 #[error("Missing attribute '{attribute}' in element '{element}'")]
91 MissingAttribute { element: String, attribute: String },
92
93 #[error("Macro error: {0}")]
94 PropertyNotFound(String),
95
96 #[error("Evaluation error in '{expr}': {source}")]
97 EvalError {
98 expr: String,
99 #[source]
100 source: crate::eval::EvalError,
101 },
102
103 #[error("XML write error: {0}")]
104 XmlWrite(#[from] xmltree::Error),
105
106 #[error("UTF-8 conversion error: {0}")]
107 Utf8(#[from] std::string::FromUtf8Error),
108
109 #[error("Macro recursion limit exceeded: depth {depth} > {limit} (possible infinite loop)")]
110 MacroRecursionLimit { depth: usize, limit: usize },
111
112 #[error("Block parameter '{param}' cannot have a default value")]
113 BlockParameterWithDefault { param: String },
114
115 #[error("Invalid parameter name: '{param}' (parameter names cannot be empty)")]
116 InvalidParameterName { param: String },
117
118 #[error("Unbalanced quote in macro parameters: unclosed {quote_char} quote in '{params_str}'")]
119 UnbalancedQuote {
120 quote_char: char,
121 params_str: String,
122 },
123
124 #[error("Missing block parameter '{param}' in macro '{macro_name}'")]
125 MissingBlockParameter { macro_name: String, param: String },
126
127 #[error("Unused block in macro '{macro_name}' (provided {extra_count} extra child elements)")]
128 UnusedBlock {
129 macro_name: String,
130 extra_count: usize,
131 },
132
133 #[error("Undefined block '{name}'")]
134 UndefinedBlock { name: String },
135
136 #[error("Duplicate parameter declaration: '{param}'\n\nParameter names must be unique. Duplicate parameters are ambiguous and can lead to\nunexpected behavior in other xacro implementations.\n\nTo accept duplicates (last declaration wins), use:\n xacro --compat <file>")]
137 DuplicateParamDeclaration { param: String },
138
139 #[error("Block parameter '{param}' cannot be specified as an attribute (it must be provided as a child element)")]
140 BlockParameterAttributeCollision { param: String },
141
142 #[error("Invalid macro parameter '{param}': {reason}")]
143 InvalidMacroParameter { param: String, reason: String },
144
145 #[error("Invalid forward syntax in parameter '{param}': {hint}")]
146 InvalidForwardSyntax { param: String, hint: String },
147
148 #[error("Macro '{macro_name}' parameter '{param}' declared with ^ to forward '{forward_name}' but not found in parent scope")]
149 UndefinedPropertyToForward {
150 macro_name: String,
151 param: String,
152 forward_name: String,
153 },
154
155 #[error(
156 "Invalid scope attribute '{scope}' for property '{property}': must be 'parent' or 'global'"
157 )]
158 InvalidScopeAttribute { property: String, scope: String },
159
160 #[cfg(feature = "yaml")]
162 #[error("Failed to load YAML file '{path}': {source}")]
163 YamlLoadError {
164 path: String,
165 #[source]
166 source: std::io::Error,
167 },
168
169 #[cfg(feature = "yaml")]
171 #[error("Failed to parse YAML file '{path}': {message}")]
172 YamlParseError { path: String, message: String },
173
174 #[cfg(not(feature = "yaml"))]
176 #[error(
177 "load_yaml() requires 'yaml' feature.\n\
178 \n\
179 To enable YAML support, rebuild with:\n\
180 cargo build --features yaml"
181 )]
182 YamlFeatureDisabled,
183
184 #[error("Unimplemented xacro feature: {0}")]
185 UnimplementedFeature(String),
186
187 #[error("Missing xacro namespace declaration: {0}")]
188 MissingNamespace(String),
189
190 #[error("Circular property dependency detected: {chain}")]
195 CircularPropertyDependency { chain: String },
196
197 #[error("Undefined property: '{0}'")]
198 UndefinedProperty(String),
199
200 #[error(
205 "Undefined argument: '{name}'.\n\
206 \n\
207 To fix this:\n\
208 1. Define it in XML: <xacro:arg name=\"{name}\" default=\"...\"/>\n\
209 2. Or pass it via CLI: {name}:=value"
210 )]
211 UndefinedArgument { name: String },
212
213 #[error("Unknown extension type: '$({} ...)'", ext_type)]
225 UnknownExtension { ext_type: String },
226
227 #[error(
229 "Failed to resolve extension: '$({})'.\n\
230 \n\
231 {}",
232 content,
233 reason
234 )]
235 InvalidExtension { content: String, reason: String },
236
237 #[error("Property substitution exceeded maximum depth of {depth} iterations. Remaining unresolved expressions in: {snippet}")]
243 MaxSubstitutionDepth { depth: usize, snippet: String },
244
245 #[error("Invalid root element: {0}")]
250 InvalidRoot(String),
251
252 #[error("Invalid XML: {0}")]
257 InvalidXml(String),
258}
259
260pub use crate::eval::EvalError;
262impl From<crate::eval::EvalError> for XacroError {
263 fn from(e: crate::eval::EvalError) -> Self {
264 XacroError::EvalError {
265 expr: match &e {
266 crate::eval::EvalError::PyishEval { expr, .. } => expr.clone(),
267 crate::eval::EvalError::InvalidBoolean { condition, .. } => condition.clone(),
268 },
269 source: e,
270 }
271 }
272}
273
274impl XacroError {
275 pub fn with_context(
288 self,
289 context: ErrorContext,
290 ) -> Self {
291 if matches!(self, XacroError::WithContext { .. }) {
293 return self;
294 }
295 XacroError::WithContext {
296 source: Box::new(self),
297 context,
298 }
299 }
300}
301
302pub trait EnrichError<T> {
307 fn with_loc(
316 self,
317 loc: &crate::eval::LocationContext,
318 ) -> Result<T, XacroError>;
319}
320
321impl<T> EnrichError<T> for Result<T, XacroError> {
322 fn with_loc(
323 self,
324 loc: &crate::eval::LocationContext,
325 ) -> Result<T, XacroError> {
326 self.map_err(|e| e.with_context(loc.clone().into()))
327 }
328}
329
330pub use crate::directives::{IMPLEMENTED_FEATURES, UNIMPLEMENTED_FEATURES};
333
334pub fn unimplemented_feature_error(feature: &str) -> XacroError {
336 XacroError::UnimplementedFeature(format!(
337 "<xacro:{}> is not implemented yet.\n\
338 \n\
339 Currently implemented: {}\n\
340 Not yet implemented: {}",
341 feature,
342 IMPLEMENTED_FEATURES.join(", "),
343 UNIMPLEMENTED_FEATURES.join(", ")
344 ))
345}