serdes_ai_output/
error.rs1use thiserror::Error;
4
5#[derive(Debug, Error)]
7pub enum OutputParseError {
8 #[error("Failed to parse JSON: {0}")]
10 JsonParse(#[from] serde_json::Error),
11
12 #[error("Missing required field: {0}")]
14 MissingField(String),
15
16 #[error("Invalid value for field '{field}': {message}")]
18 InvalidField {
19 field: String,
21 message: String,
23 },
24
25 #[error("Unexpected tool call: expected '{expected}', got '{actual}'")]
27 UnexpectedTool {
28 expected: String,
30 actual: String,
32 },
33
34 #[error("Output is not valid JSON")]
36 NotJson,
37
38 #[error("No JSON object or array found in output")]
40 NoJsonFound,
41
42 #[error("Output does not match required pattern: {pattern}")]
44 PatternMismatch {
45 pattern: String,
47 },
48
49 #[error("Output length {actual} is {direction} allowed {limit}")]
51 LengthViolation {
52 actual: usize,
54 limit: usize,
56 direction: &'static str,
58 },
59
60 #[error("Invalid regex pattern: {0}")]
62 RegexError(#[from] regex::Error),
63
64 #[error("Parse error: {0}")]
66 Custom(String),
67}
68
69impl OutputParseError {
70 pub fn custom(msg: impl Into<String>) -> Self {
72 Self::Custom(msg.into())
73 }
74
75 pub fn missing_field(field: impl Into<String>) -> Self {
77 Self::MissingField(field.into())
78 }
79
80 pub fn invalid_field(field: impl Into<String>, message: impl Into<String>) -> Self {
82 Self::InvalidField {
83 field: field.into(),
84 message: message.into(),
85 }
86 }
87
88 pub fn unexpected_tool(expected: impl Into<String>, actual: impl Into<String>) -> Self {
90 Self::UnexpectedTool {
91 expected: expected.into(),
92 actual: actual.into(),
93 }
94 }
95
96 pub fn too_short(actual: usize, min: usize) -> Self {
98 Self::LengthViolation {
99 actual,
100 limit: min,
101 direction: "below",
102 }
103 }
104
105 pub fn too_long(actual: usize, max: usize) -> Self {
107 Self::LengthViolation {
108 actual,
109 limit: max,
110 direction: "above",
111 }
112 }
113}
114
115#[derive(Debug, Error)]
117pub enum OutputValidationError {
118 #[error("Validation failed: {message}")]
120 Failed {
121 message: String,
123 retry: bool,
125 },
126
127 #[error("Model retry requested: {0}")]
129 ModelRetry(String),
130
131 #[error(transparent)]
133 Parse(#[from] OutputParseError),
134}
135
136impl OutputValidationError {
137 pub fn retry(msg: impl Into<String>) -> Self {
139 Self::ModelRetry(msg.into())
140 }
141
142 pub fn failed(msg: impl Into<String>) -> Self {
144 Self::Failed {
145 message: msg.into(),
146 retry: false,
147 }
148 }
149
150 pub fn failed_retry(msg: impl Into<String>) -> Self {
152 Self::Failed {
153 message: msg.into(),
154 retry: true,
155 }
156 }
157
158 pub fn should_retry(&self) -> bool {
160 match self {
161 Self::Failed { retry, .. } => *retry,
162 Self::ModelRetry(_) => true,
163 Self::Parse(_) => false,
164 }
165 }
166
167 pub fn retry_message(&self) -> Option<&str> {
169 match self {
170 Self::ModelRetry(msg) => Some(msg),
171 Self::Failed {
172 message,
173 retry: true,
174 } => Some(message),
175 _ => None,
176 }
177 }
178}
179
180pub type ParseResult<T> = Result<T, OutputParseError>;
182
183pub type ValidationResult<T> = Result<T, OutputValidationError>;
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_parse_error_custom() {
192 let err = OutputParseError::custom("Something went wrong");
193 assert!(err.to_string().contains("Something went wrong"));
194 }
195
196 #[test]
197 fn test_parse_error_missing_field() {
198 let err = OutputParseError::missing_field("name");
199 assert!(err.to_string().contains("name"));
200 }
201
202 #[test]
203 fn test_parse_error_invalid_field() {
204 let err = OutputParseError::invalid_field("age", "must be positive");
205 assert!(err.to_string().contains("age"));
206 assert!(err.to_string().contains("must be positive"));
207 }
208
209 #[test]
210 fn test_parse_error_unexpected_tool() {
211 let err = OutputParseError::unexpected_tool("final_result", "search");
212 assert!(err.to_string().contains("final_result"));
213 assert!(err.to_string().contains("search"));
214 }
215
216 #[test]
217 fn test_parse_error_too_short() {
218 let err = OutputParseError::too_short(5, 10);
219 assert!(err.to_string().contains("5"));
220 assert!(err.to_string().contains("10"));
221 assert!(err.to_string().contains("below"));
222 }
223
224 #[test]
225 fn test_validation_error_retry() {
226 let err = OutputValidationError::retry("Please provide a valid email");
227 assert!(err.should_retry());
228 assert_eq!(err.retry_message(), Some("Please provide a valid email"));
229 }
230
231 #[test]
232 fn test_validation_error_failed_no_retry() {
233 let err = OutputValidationError::failed("Invalid data");
234 assert!(!err.should_retry());
235 assert!(err.retry_message().is_none());
236 }
237
238 #[test]
239 fn test_validation_error_failed_retry() {
240 let err = OutputValidationError::failed_retry("Try again");
241 assert!(err.should_retry());
242 assert_eq!(err.retry_message(), Some("Try again"));
243 }
244}