Skip to main content

agent_first_data/document/
error.rs

1//! Error types with context and helpful hints.
2
3use std::fmt;
4use std::io;
5
6pub type DocumentResult<T> = Result<T, DocumentError>;
7
8#[derive(Debug, Clone)]
9pub enum DocumentError {
10    EmptyPath,
11    EmptyValues,
12    UnknownSegment {
13        path: String,
14        segment: String,
15    },
16    UnregisteredArray {
17        path: String,
18    },
19    SlugNotFound {
20        prefix: String,
21        slug: String,
22    },
23    SlugAlreadyExists {
24        prefix: String,
25        slug: String,
26    },
27    NotTraversable {
28        path: String,
29        got: String,
30    },
31    TypeMismatch {
32        path: String,
33        expected: String,
34        got: String,
35        hint: Option<String>,
36    },
37    PathNotFound {
38        path: String,
39    },
40    IndexOutOfBounds {
41        path: String,
42        index: usize,
43        len: usize,
44    },
45    ParseError {
46        format: String,
47        detail: String,
48    },
49    IoError {
50        detail: String,
51    },
52    UnsupportedOperation {
53        format: String,
54        operation: String,
55        detail: String,
56    },
57}
58
59impl fmt::Display for DocumentError {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        match self {
62            DocumentError::EmptyPath => {
63                write!(f, "empty path provided")
64            }
65            DocumentError::EmptyValues => {
66                write!(f, "at least one value required")
67            }
68            DocumentError::UnknownSegment { path, segment } => {
69                write!(f, "path `{}` segment `{}` not found", path, segment)
70            }
71            DocumentError::UnregisteredArray { path } => {
72                write!(f, "array at `{}` not registered in KeyedList", path)
73            }
74            DocumentError::SlugNotFound { prefix, slug } => {
75                write!(f, "no element with slug `{}` found in `{}`", slug, prefix)
76            }
77            DocumentError::SlugAlreadyExists { prefix, slug } => {
78                write!(f, "slug `{}` already exists in `{}`", slug, prefix)
79            }
80            DocumentError::NotTraversable { path, got } => {
81                write!(f, "path `{}` is {}, cannot traverse further", path, got)
82            }
83            DocumentError::TypeMismatch {
84                path,
85                expected,
86                got,
87                hint,
88            } => {
89                write!(f, "field `{}` expects {}, got `{}`", path, expected, got)?;
90                if let Some(h) = hint {
91                    write!(f, "\n  hint: {}", h)?;
92                }
93                Ok(())
94            }
95            DocumentError::PathNotFound { path } => {
96                write!(f, "path `{}` not found in config", path)
97            }
98            DocumentError::IndexOutOfBounds { path, index, len } => {
99                write!(
100                    f,
101                    "index {} out of bounds at `{}` (len {})",
102                    index, path, len
103                )
104            }
105            DocumentError::ParseError { format, detail } => {
106                write!(f, "failed to parse {}: {}", format, detail)
107            }
108            DocumentError::IoError { detail } => {
109                write!(f, "io error: {}", detail)
110            }
111            DocumentError::UnsupportedOperation {
112                format,
113                operation,
114                detail,
115            } => write!(f, "{} does not support {}: {}", format, operation, detail),
116        }
117    }
118}
119
120impl std::error::Error for DocumentError {}
121
122impl DocumentError {
123    /// Wrap a serde deserialization failure as a `TypeMismatch` so callers that
124    /// do a read-modify-write cycle (set_path → serde round-trip) surface a
125    /// consistent error style rather than a raw serde message.
126    pub fn from_serde(path: impl Into<String>, err: impl std::fmt::Display) -> Self {
127        let msg = err.to_string();
128        // serde messages look like "invalid type: string \"x\", expected u16 at …"
129        // Strip the trailing " at line N column M" to keep the hint concise.
130        let hint = msg
131            .split(" at line ")
132            .next()
133            .unwrap_or(&msg)
134            .trim()
135            .to_string();
136        DocumentError::TypeMismatch {
137            path: path.into(),
138            expected: String::new(),
139            got: hint,
140            hint: None,
141        }
142    }
143}
144
145impl From<io::Error> for DocumentError {
146    fn from(err: io::Error) -> Self {
147        DocumentError::IoError {
148            detail: err.to_string(),
149        }
150    }
151}