1use crate::Path;
4use thiserror::Error;
5
6pub type TireaResult<T> = Result<T, TireaError>;
8
9#[derive(Debug, Error)]
11pub enum TireaError {
12 #[error("path not found: {path}")]
14 PathNotFound {
15 path: Path,
17 },
18
19 #[error("index {index} out of bounds (len: {len}) at path {path}")]
21 IndexOutOfBounds {
22 path: Path,
24 index: usize,
26 len: usize,
28 },
29
30 #[error("type mismatch at {path}: expected {expected}, found {found}")]
32 TypeMismatch {
33 path: Path,
35 expected: &'static str,
37 found: &'static str,
39 },
40
41 #[error("numeric operation requires number at {path}")]
43 NumericOperationOnNonNumber {
44 path: Path,
46 },
47
48 #[error("merge requires object value at {path}")]
50 MergeRequiresObject {
51 path: Path,
53 },
54
55 #[error("append requires array value at {path}")]
57 AppendRequiresArray {
58 path: Path,
60 },
61
62 #[error("invalid operation: {message}")]
64 InvalidOperation {
65 message: String,
67 },
68
69 #[error("serialization error: {0}")]
71 Serialization(#[from] serde_json::Error),
72}
73
74impl TireaError {
75 #[inline]
77 pub fn path_not_found(path: Path) -> Self {
78 TireaError::PathNotFound { path }
79 }
80
81 #[inline]
83 pub fn index_out_of_bounds(path: Path, index: usize, len: usize) -> Self {
84 TireaError::IndexOutOfBounds { path, index, len }
85 }
86
87 #[inline]
89 pub fn type_mismatch(path: Path, expected: &'static str, found: &'static str) -> Self {
90 TireaError::TypeMismatch {
91 path,
92 expected,
93 found,
94 }
95 }
96
97 #[inline]
99 pub fn numeric_on_non_number(path: Path) -> Self {
100 TireaError::NumericOperationOnNonNumber { path }
101 }
102
103 #[inline]
105 pub fn merge_requires_object(path: Path) -> Self {
106 TireaError::MergeRequiresObject { path }
107 }
108
109 #[inline]
111 pub fn append_requires_array(path: Path) -> Self {
112 TireaError::AppendRequiresArray { path }
113 }
114
115 #[inline]
117 pub fn invalid_operation(message: impl Into<String>) -> Self {
118 TireaError::InvalidOperation {
119 message: message.into(),
120 }
121 }
122
123 pub fn with_prefix(self, prefix: &Path) -> Self {
130 match self {
131 TireaError::PathNotFound { path } => {
132 let mut new_path = prefix.clone();
133 for seg in path.iter() {
134 new_path.push(seg.clone());
135 }
136 TireaError::PathNotFound { path: new_path }
137 }
138 TireaError::TypeMismatch {
139 path,
140 expected,
141 found,
142 } => {
143 let mut new_path = prefix.clone();
144 for seg in path.iter() {
145 new_path.push(seg.clone());
146 }
147 TireaError::TypeMismatch {
148 path: new_path,
149 expected,
150 found,
151 }
152 }
153 TireaError::IndexOutOfBounds { path, index, len } => {
154 let mut new_path = prefix.clone();
155 for seg in path.iter() {
156 new_path.push(seg.clone());
157 }
158 TireaError::IndexOutOfBounds {
159 path: new_path,
160 index,
161 len,
162 }
163 }
164 other => other,
166 }
167 }
168}
169
170#[inline]
172pub fn value_type_name(v: &serde_json::Value) -> &'static str {
173 match v {
174 serde_json::Value::Null => "null",
175 serde_json::Value::Bool(_) => "boolean",
176 serde_json::Value::Number(_) => "number",
177 serde_json::Value::String(_) => "string",
178 serde_json::Value::Array(_) => "array",
179 serde_json::Value::Object(_) => "object",
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::path;
187
188 #[test]
189 fn test_error_display() {
190 let err = TireaError::path_not_found(path!("users", 0, "name"));
191 assert!(err.to_string().contains("path not found"));
192 }
193
194 #[test]
195 fn test_value_type_name() {
196 use serde_json::json;
197
198 assert_eq!(value_type_name(&json!(null)), "null");
199 assert_eq!(value_type_name(&json!(true)), "boolean");
200 assert_eq!(value_type_name(&json!(42)), "number");
201 assert_eq!(value_type_name(&json!("hello")), "string");
202 assert_eq!(value_type_name(&json!([1, 2, 3])), "array");
203 assert_eq!(value_type_name(&json!({"a": 1})), "object");
204 }
205}