Skip to main content

agent_uri/
error.rs

1//! Error types for agent URI parsing.
2
3use std::fmt;
4
5/// Errors that can occur when parsing an agent URI.
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct ParseError {
8    /// The input that failed to parse
9    pub input: String,
10    /// The specific error that occurred
11    pub kind: ParseErrorKind,
12}
13
14/// Specific parsing error types.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ParseErrorKind {
17    /// URI is empty
18    Empty,
19    /// URI exceeds maximum length
20    TooLong {
21        /// Maximum allowed length
22        max: usize,
23        /// Actual length
24        actual: usize,
25    },
26    /// Missing or invalid scheme (expected "agent://")
27    InvalidScheme {
28        /// The scheme that was found, if any
29        found: Option<String>,
30    },
31    /// Trust root parsing failed
32    InvalidTrustRoot(TrustRootError),
33    /// Capability path parsing failed
34    InvalidCapabilityPath(CapabilityPathError),
35    /// Agent ID parsing failed
36    InvalidAgentId(AgentIdError),
37    /// Query string parsing failed
38    InvalidQuery(QueryError),
39    /// Fragment parsing failed
40    InvalidFragment(FragmentError),
41    /// Missing required component
42    MissingComponent {
43        /// Name of the missing component
44        component: &'static str,
45    },
46    /// Unexpected character at position
47    UnexpectedChar {
48        /// The unexpected character
49        char: char,
50        /// Position in the input
51        position: usize,
52    },
53}
54
55impl fmt::Display for ParseError {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        write!(f, "failed to parse agent URI '{}': ", self.input)?;
58        match &self.kind {
59            ParseErrorKind::Empty => write!(f, "input is empty"),
60            ParseErrorKind::TooLong { max, actual } => {
61                write!(
62                    f,
63                    "URI length {actual} exceeds maximum {max}; consider shorter capability path or trust root"
64                )
65            }
66            ParseErrorKind::InvalidScheme { found } => match found {
67                Some(s) => write!(f, "expected scheme 'agent://', found '{s}'"),
68                None => write!(f, "missing scheme; URI must start with 'agent://'"),
69            },
70            ParseErrorKind::InvalidTrustRoot(e) => write!(f, "invalid trust root: {e}"),
71            ParseErrorKind::InvalidCapabilityPath(e) => write!(f, "invalid capability path: {e}"),
72            ParseErrorKind::InvalidAgentId(e) => write!(f, "invalid agent ID: {e}"),
73            ParseErrorKind::InvalidQuery(e) => write!(f, "invalid query string: {e}"),
74            ParseErrorKind::InvalidFragment(e) => write!(f, "invalid fragment: {e}"),
75            ParseErrorKind::MissingComponent { component } => {
76                write!(f, "missing required component: {component}")
77            }
78            ParseErrorKind::UnexpectedChar { char, position } => {
79                write!(f, "unexpected character '{char}' at position {position}")
80            }
81        }
82    }
83}
84
85impl std::error::Error for ParseError {}
86
87/// Errors for trust root parsing.
88#[derive(Debug, Clone, PartialEq, Eq)]
89pub enum TrustRootError {
90    /// Trust root is empty
91    Empty,
92    /// Trust root exceeds maximum length
93    TooLong {
94        /// Maximum allowed length
95        max: usize,
96        /// Actual length
97        actual: usize,
98    },
99    /// Invalid domain name
100    InvalidDomain {
101        /// The invalid domain
102        domain: String,
103        /// Reason for invalidity
104        reason: &'static str,
105    },
106    /// Invalid IP address
107    InvalidIpAddress {
108        /// The invalid value
109        value: String,
110        /// Reason for invalidity
111        reason: &'static str,
112    },
113    /// Invalid port number
114    InvalidPort {
115        /// The invalid value
116        value: String,
117        /// Reason for invalidity
118        reason: &'static str,
119    },
120    /// DNS label too long
121    LabelTooLong {
122        /// The too-long label
123        label: String,
124        /// Maximum allowed length
125        max: usize,
126        /// Actual length
127        actual: usize,
128    },
129    /// Invalid character in domain
130    InvalidChar {
131        /// The invalid character
132        char: char,
133        /// Position in the input
134        position: usize,
135    },
136}
137
138impl fmt::Display for TrustRootError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        match self {
141            Self::Empty => write!(f, "trust root cannot be empty"),
142            Self::TooLong { max, actual } => {
143                write!(f, "trust root length {actual} exceeds maximum {max}")
144            }
145            Self::InvalidDomain { domain, reason } => {
146                write!(f, "invalid domain '{domain}': {reason}")
147            }
148            Self::InvalidIpAddress { value, reason } => {
149                write!(f, "invalid IP address '{value}': {reason}")
150            }
151            Self::InvalidPort { value, reason } => {
152                write!(f, "invalid port '{value}': {reason}")
153            }
154            Self::LabelTooLong { label, max, actual } => {
155                write!(f, "DNS label '{label}' is {actual} chars, max is {max}")
156            }
157            Self::InvalidChar { char, position } => {
158                write!(f, "invalid character '{char}' at position {position}")
159            }
160        }
161    }
162}
163
164impl std::error::Error for TrustRootError {}
165
166/// Errors for capability path parsing.
167#[derive(Debug, Clone, PartialEq, Eq)]
168pub enum CapabilityPathError {
169    /// Path is empty
170    Empty,
171    /// Path exceeds maximum length
172    TooLong {
173        /// Maximum allowed length
174        max: usize,
175        /// Actual length
176        actual: usize,
177    },
178    /// Too many segments
179    TooManySegments {
180        /// Maximum allowed segments
181        max: usize,
182        /// Actual segment count
183        actual: usize,
184    },
185    /// Invalid segment
186    InvalidSegment {
187        /// The invalid segment
188        segment: String,
189        /// Index of the segment
190        index: usize,
191        /// Reason for invalidity
192        reason: PathSegmentError,
193    },
194}
195
196impl fmt::Display for CapabilityPathError {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        match self {
199            Self::Empty => write!(
200                f,
201                "capability path cannot be empty; at least one segment required"
202            ),
203            Self::TooLong { max, actual } => {
204                write!(f, "capability path length {actual} exceeds maximum {max}")
205            }
206            Self::TooManySegments { max, actual } => {
207                write!(f, "path has {actual} segments, maximum is {max}")
208            }
209            Self::InvalidSegment {
210                segment,
211                index,
212                reason,
213            } => {
214                write!(f, "invalid segment '{segment}' at index {index}: {reason}")
215            }
216        }
217    }
218}
219
220impl std::error::Error for CapabilityPathError {}
221
222/// Errors for path segment parsing.
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub enum PathSegmentError {
225    /// Segment is empty
226    Empty,
227    /// Segment exceeds maximum length
228    TooLong {
229        /// Maximum allowed length
230        max: usize,
231        /// Actual length
232        actual: usize,
233    },
234    /// Invalid character (not lowercase alphanumeric or hyphen)
235    InvalidChar {
236        /// The invalid character
237        char: char,
238        /// Position in the input
239        position: usize,
240    },
241}
242
243impl fmt::Display for PathSegmentError {
244    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
245        match self {
246            Self::Empty => write!(f, "segment cannot be empty"),
247            Self::TooLong { max, actual } => {
248                write!(f, "segment length {actual} exceeds maximum {max}")
249            }
250            Self::InvalidChar { char, position } => {
251                write!(
252                    f,
253                    "invalid character '{char}' at position {position}; only lowercase letters, digits, and hyphens allowed"
254                )
255            }
256        }
257    }
258}
259
260impl std::error::Error for PathSegmentError {}
261
262/// Errors for agent ID parsing.
263#[derive(Debug, Clone, PartialEq, Eq)]
264pub enum AgentIdError {
265    /// Agent ID is empty
266    Empty,
267    /// Agent ID exceeds maximum length
268    TooLong {
269        /// Maximum allowed length
270        max: usize,
271        /// Actual length
272        actual: usize,
273    },
274    /// Invalid prefix
275    InvalidPrefix(AgentPrefixError),
276    /// Invalid `TypeID` suffix
277    InvalidSuffix {
278        /// The invalid value
279        value: String,
280        /// Reason for invalidity
281        reason: &'static str,
282    },
283    /// Missing underscore separator
284    MissingSeparator,
285    /// `TypeID` parsing failed
286    TypeIdError(String),
287}
288
289impl fmt::Display for AgentIdError {
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        match self {
292            Self::Empty => write!(f, "agent ID cannot be empty"),
293            Self::TooLong { max, actual } => {
294                write!(f, "agent ID length {actual} exceeds maximum {max}")
295            }
296            Self::InvalidPrefix(e) => write!(f, "invalid prefix: {e}"),
297            Self::InvalidSuffix { value, reason } => {
298                write!(f, "invalid suffix '{value}': {reason}")
299            }
300            Self::MissingSeparator => {
301                write!(f, "missing underscore separator between prefix and suffix")
302            }
303            Self::TypeIdError(msg) => write!(f, "`TypeID` error: {msg}"),
304        }
305    }
306}
307
308impl std::error::Error for AgentIdError {}
309
310/// Errors for agent prefix parsing.
311#[derive(Debug, Clone, PartialEq, Eq)]
312pub enum AgentPrefixError {
313    /// Prefix is empty
314    Empty,
315    /// Prefix exceeds maximum length
316    TooLong {
317        /// Maximum allowed length
318        max: usize,
319        /// Actual length
320        actual: usize,
321    },
322    /// Invalid character (not lowercase letter or underscore)
323    InvalidChar {
324        /// The invalid character
325        char: char,
326        /// Position in the input
327        position: usize,
328    },
329    /// Prefix must start with a letter
330    MustStartWithLetter {
331        /// The character found
332        found: char,
333    },
334    /// Prefix must end with a letter
335    MustEndWithLetter {
336        /// The character found
337        found: char,
338    },
339    /// Contains digits (not allowed per `TypeID` spec)
340    ContainsDigit {
341        /// Position of the digit
342        position: usize,
343    },
344}
345
346impl fmt::Display for AgentPrefixError {
347    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
348        match self {
349            Self::Empty => write!(f, "prefix cannot be empty"),
350            Self::TooLong { max, actual } => {
351                write!(f, "prefix length {actual} exceeds maximum {max}")
352            }
353            Self::InvalidChar { char, position } => {
354                write!(
355                    f,
356                    "invalid character '{char}' at position {position}; only lowercase letters and underscores allowed"
357                )
358            }
359            Self::MustStartWithLetter { found } => {
360                write!(f, "prefix must start with a letter, found '{found}'")
361            }
362            Self::MustEndWithLetter { found } => {
363                write!(f, "prefix must end with a letter, found '{found}'")
364            }
365            Self::ContainsDigit { position } => {
366                write!(f, "prefix cannot contain digits (found at position {position})")
367            }
368        }
369    }
370}
371
372impl std::error::Error for AgentPrefixError {}
373
374/// Errors for query string parsing.
375#[derive(Debug, Clone, PartialEq, Eq)]
376pub enum QueryError {
377    /// Invalid parameter name
378    InvalidParamName {
379        /// The invalid name
380        name: String,
381        /// Reason for invalidity
382        reason: &'static str,
383    },
384    /// Invalid parameter value
385    InvalidParamValue {
386        /// Parameter name
387        name: String,
388        /// The invalid value
389        value: String,
390        /// Reason for invalidity
391        reason: &'static str,
392    },
393    /// Duplicate parameter
394    DuplicateParam {
395        /// The duplicated name
396        name: String,
397    },
398    /// Invalid percent encoding
399    InvalidPercentEncoding {
400        /// The invalid value
401        value: String,
402    },
403}
404
405impl fmt::Display for QueryError {
406    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
407        match self {
408            Self::InvalidParamName { name, reason } => {
409                write!(f, "invalid parameter name '{name}': {reason}")
410            }
411            Self::InvalidParamValue { name, value, reason } => {
412                write!(f, "invalid value '{value}' for parameter '{name}': {reason}")
413            }
414            Self::DuplicateParam { name } => {
415                write!(f, "duplicate parameter '{name}'")
416            }
417            Self::InvalidPercentEncoding { value } => {
418                write!(f, "invalid percent encoding in '{value}'")
419            }
420        }
421    }
422}
423
424impl std::error::Error for QueryError {}
425
426/// Errors for fragment parsing.
427#[derive(Debug, Clone, PartialEq, Eq)]
428pub enum FragmentError {
429    /// Invalid character in fragment
430    InvalidChar {
431        /// The invalid character
432        char: char,
433        /// Position in the input
434        position: usize,
435    },
436}
437
438impl fmt::Display for FragmentError {
439    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440        match self {
441            Self::InvalidChar { char, position } => {
442                write!(
443                    f,
444                    "invalid character '{char}' at position {position}; allowed: alphanumeric, hyphen, underscore, dot, slash"
445                )
446            }
447        }
448    }
449}
450
451impl std::error::Error for FragmentError {}
452
453/// Errors that can occur when building an agent URI.
454#[derive(Debug, Clone, PartialEq, Eq)]
455pub enum BuilderError {
456    /// The resulting URI would exceed the maximum length.
457    UriTooLong {
458        /// Maximum allowed length
459        max: usize,
460        /// Actual length
461        actual: usize,
462    },
463}
464
465impl fmt::Display for BuilderError {
466    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
467        match self {
468            Self::UriTooLong { max, actual } => {
469                write!(
470                    f,
471                    "resulting URI length {actual} exceeds maximum {max}; \
472                     consider shorter component values"
473                )
474            }
475        }
476    }
477}
478
479impl std::error::Error for BuilderError {}