1use crate::node::{ChainNode, CommandNode, Nodes, PipeNode};
2use gtmpl_value::{FuncError, Value};
3use std::{fmt, num::ParseIntError, string::FromUtf8Error};
4use thiserror::Error;
5
6#[derive(Debug, Clone)]
7pub struct ErrorContext {
8 pub name: String,
9 pub line: usize,
10 pub col: usize,
11 pub len: usize,
12}
13
14impl fmt::Display for ErrorContext {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 write!(f, "{}:{}:{}:{}", self.name, self.line, self.col, self.len)
17 }
18}
19
20#[derive(Debug)]
22pub struct StructuredError {
23 pub context: ErrorContext,
24 pub message: String,
25 pub cause: Option<Box<StructuredError>>,
26}
27
28impl StructuredError {
29 pub fn new(
30 name: impl ToString,
31 line: usize,
32 col: usize,
33 len: usize,
34 message: impl ToString,
35 ) -> Self {
36 Self {
37 context: ErrorContext {
38 name: name.to_string(),
39 line,
40 col,
41 len,
42 },
43 message: message.to_string(),
44 cause: None,
45 }
46 }
47
48 pub fn with_cause(mut self, cause: StructuredError) -> Self {
49 self.cause = Some(Box::new(cause));
50 self
51 }
52
53 pub fn chain(&self) -> ErrorChainIter<'_> {
55 ErrorChainIter {
56 current: Some(self),
57 }
58 }
59}
60
61pub struct ErrorChainIter<'a> {
62 current: Option<&'a StructuredError>,
63}
64
65impl<'a> Iterator for ErrorChainIter<'a> {
66 type Item = &'a StructuredError;
67
68 fn next(&mut self) -> Option<Self::Item> {
69 let current = self.current?;
70 self.current = current.cause.as_deref();
71 Some(current)
72 }
73}
74
75impl fmt::Display for StructuredError {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 write!(f, "template: {}:{}", self.context, self.message)?;
78 if let Some(ref cause) = self.cause {
79 write!(f, "\n caused by: {}", cause)?;
80 }
81 Ok(())
82 }
83}
84
85#[derive(Error, Debug)]
86pub enum ParseError {
87 #[error("unexpected {0} in define clause")]
88 UnexpectedInDefineClause(Nodes),
89 #[error("unexpected end")]
90 UnexpectedEnd,
91 #[error("template: {0}:{1}")]
92 WithContext(ErrorContext, String),
93 #[error("no tree")]
94 NoTree,
95 #[error(transparent)]
96 NodeError(#[from] NodeError),
97 #[error("enable gtmpl_dynamic_template to use a pipeline as name")]
98 NoDynamicTemplate,
99 #[error("unable to parse string: {0}")]
100 UnableToParseString(String),
101}
102
103impl ParseError {
104 pub fn with_context(
105 name: impl ToString,
106 line: usize,
107 col: usize,
108 len: usize,
109 msg: impl ToString,
110 ) -> Self {
111 Self::WithContext(
112 ErrorContext {
113 name: name.to_string(),
114 line,
115 col,
116 len,
117 },
118 msg.to_string(),
119 )
120 }
121}
122
123#[derive(Error, Debug)]
124pub enum NodeError {
125 #[error("unable to unquote")]
126 UnquoteError,
127 #[error("NaN")]
128 NaN,
129 #[error("not a tree node")]
130 NaTN,
131}
132
133#[derive(Error, Debug)]
134pub enum PrintError {
135 #[error("unable to process verb: {0}")]
136 UnableToProcessVerb(String),
137 #[error("{0:X} is not a valid char")]
138 NotAValidChar(i128),
139 #[error("unable to format {0} as {1}")]
140 UnableToFormat(Value, char),
141 #[error("unable to terminate format arg: {0}")]
142 UnableToTerminateFormatArg(String),
143 #[error("missing ] in {0}")]
144 MissingClosingBracket(String),
145 #[error("unable to parse index: {0}")]
146 UnableToParseIndex(ParseIntError),
147 #[error("unable to parse width: {0}")]
148 UnableToParseWidth(ParseIntError),
149 #[error("width after index (e.g. %[3]2d)")]
150 WithAfterIndex,
151 #[error("precision after index (e.g. %[3].2d)")]
152 PrecisionAfterIndex,
153}
154
155#[derive(Error, Debug)]
156pub enum ExecError {
157 #[error("{0}")]
158 Structured(StructuredError),
159 #[error("{0} is an incomplete or empty template")]
160 IncompleteTemplate(String),
161 #[error("{0}")]
162 IOError(#[from] std::io::Error),
163 #[error("unknown node: {0}")]
164 UnknownNode(Nodes),
165 #[error("expected if or with node, got {0}")]
166 ExpectedIfOrWith(Nodes),
167 #[error("unable to convert output to uft-8: {0}")]
168 Utf8ConversionFailed(FromUtf8Error),
169 #[error("empty var stack")]
170 EmptyStack,
171 #[error("var context smaller than {0}")]
172 VarContextToSmall(usize),
173 #[error("invalid range {0:?}")]
174 InvalidRange(Value),
175 #[error("pipeline must yield a String")]
176 PipelineMustYieldString,
177 #[error("template {0} not defined")]
178 TemplateNotDefined(String),
179 #[error("exceeded max template depth")]
180 MaxTemplateDepth,
181 #[error("error evaluating pipe: {0}")]
182 ErrorEvaluatingPipe(PipeNode),
183 #[error("no arguments for command node: {0}")]
184 NoArgsForCommandNode(CommandNode),
185 #[error("cannot evaluate command: {0}")]
186 CannotEvaluateCommand(Nodes),
187 #[error("field chain without fields :/")]
188 FieldChainWithoutFields,
189 #[error("{0} has arguments but cannot be invoked as function")]
190 NotAFunctionButArguments(String),
191 #[error("no fields in eval_chain_node")]
192 NoFieldsInEvalChainNode,
193 #[error("indirection through explicit nul in {0}")]
194 NullInChain(ChainNode),
195 #[error("cannot handle {0} as argument")]
196 InvalidArgument(Nodes),
197 #[error("{0} is not a defined function")]
198 UndefinedFunction(String),
199 #[error(transparent)]
200 FuncError(#[from] FuncError),
201 #[error("can't give argument to non-function {0}")]
202 ArgumentForNonFunction(Nodes),
203 #[error("only maps and objects have fields")]
204 OnlyMapsAndObjectsHaveFields,
205 #[error("no field `{0}` in {1}")]
206 NoFieldFor(String, Value),
207 #[error("variable {0} not found")]
208 VariableNotFound(String),
209}
210
211impl ExecError {
212 pub fn with_context(
216 name: impl ToString,
217 line: usize,
218 col: usize,
219 len: usize,
220 error: ExecError,
221 ) -> Self {
222 let new_context = ErrorContext {
223 name: name.to_string(),
224 line,
225 col,
226 len,
227 };
228
229 match error {
230 ExecError::Structured(mut inner) => {
233 let outer_cause = StructuredError {
234 context: new_context,
235 message: "while evaluating".to_string(),
236 cause: inner.cause.take(),
237 };
238 inner.cause = Some(Box::new(outer_cause));
239 ExecError::Structured(inner)
240 }
241 other => {
243 let structured = StructuredError {
244 context: new_context,
245 message: other.to_string(),
246 cause: None,
247 };
248 ExecError::Structured(structured)
249 }
250 }
251 }
252
253 pub fn as_structured(&self) -> Option<&StructuredError> {
255 match self {
256 ExecError::Structured(s) => Some(s),
257 _ => None,
258 }
259 }
260}
261
262#[derive(Error, Debug)]
263pub enum TemplateError {
264 #[error(transparent)]
265 ExecError(#[from] ExecError),
266 #[error(transparent)]
267 ParseError(#[from] ParseError),
268}