Skip to main content

jpx_core/
error.rs

1//! Error types for jpx-core.
2
3use std::fmt;
4
5/// A JMESPath error with position information.
6#[derive(Debug, Clone, PartialEq)]
7pub struct JmespathError {
8    /// Character offset in the expression where the error occurred.
9    pub offset: usize,
10    /// The expression that caused the error.
11    pub expression: String,
12    /// The reason for the error.
13    pub reason: ErrorReason,
14}
15
16impl JmespathError {
17    /// Creates a new error.
18    pub fn new(expression: &str, offset: usize, reason: ErrorReason) -> Self {
19        Self {
20            offset,
21            expression: expression.to_owned(),
22            reason,
23        }
24    }
25
26    /// Creates an error from a Context, using its current offset and expression.
27    pub fn from_ctx(ctx: &crate::Context<'_>, reason: ErrorReason) -> Self {
28        Self {
29            offset: ctx.offset,
30            expression: ctx.expression.to_owned(),
31            reason,
32        }
33    }
34
35    /// Returns the line number of the error (1-indexed).
36    pub fn line(&self) -> usize {
37        self.expression[..self.offset.min(self.expression.len())]
38            .chars()
39            .filter(|c| *c == '\n')
40            .count()
41            + 1
42    }
43
44    /// Returns the column number of the error (0-indexed).
45    pub fn column(&self) -> usize {
46        let before = &self.expression[..self.offset.min(self.expression.len())];
47        match before.rfind('\n') {
48            Some(pos) => self.offset - pos - 1,
49            None => self.offset,
50        }
51    }
52}
53
54impl fmt::Display for JmespathError {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56        let col = self.column();
57        write!(
58            f,
59            "{}\n{}\n{}",
60            self.reason,
61            self.expression,
62            " ".repeat(col)
63        )?;
64        write!(f, "^")
65    }
66}
67
68impl std::error::Error for JmespathError {}
69
70/// The reason for a JMESPath error.
71#[derive(Debug, Clone, PartialEq)]
72pub enum ErrorReason {
73    /// A parse-time error.
74    Parse(String),
75    /// A runtime error.
76    Runtime(RuntimeError),
77}
78
79impl fmt::Display for ErrorReason {
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        match self {
82            ErrorReason::Parse(msg) => write!(f, "Parse error: {msg}"),
83            ErrorReason::Runtime(err) => write!(f, "Runtime error: {err}"),
84        }
85    }
86}
87
88/// Runtime errors that can occur during expression evaluation.
89#[derive(Debug, Clone, PartialEq, thiserror::Error)]
90pub enum RuntimeError {
91    /// A slice expression with step of 0.
92    #[error("Invalid slice: step cannot be 0")]
93    InvalidSlice,
94    /// Too many arguments provided to a function.
95    #[error("Too many arguments: expected {expected}, got {actual}")]
96    TooManyArguments { expected: usize, actual: usize },
97    /// Not enough arguments provided to a function.
98    #[error("Not enough arguments: expected {expected}, got {actual}")]
99    NotEnoughArguments { expected: usize, actual: usize },
100    /// An unknown function was called.
101    #[error("Unknown function: {0}")]
102    UnknownFunction(String),
103    /// Invalid type provided to a function.
104    #[error("Invalid type at position {position}: expected {expected}, got {actual}")]
105    InvalidType {
106        expected: String,
107        actual: String,
108        position: usize,
109    },
110    /// Invalid return type from an expression reference.
111    #[error(
112        "Invalid return type at position {position}, invocation {invocation}: expected {expected}, got {actual}"
113    )]
114    InvalidReturnType {
115        expected: String,
116        actual: String,
117        position: usize,
118        invocation: usize,
119    },
120}