Skip to main content

base_message/
field_error.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::FieldValueError;
5use std::fmt;
6
7/// Why a field section could not be interpreted, naming the field and its position in the original section.
8///
9/// The name is the section's own lowercase spelling of a declared or forbidden field. The name of an unknown field is untrusted input, so it is never included; nor is any value.
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct FieldError {
12    name: Option<&'static str>,
13    position: Option<usize>,
14    kind: FieldErrorKind,
15}
16
17/// The kind of [`FieldError`].
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19#[non_exhaustive]
20pub enum FieldErrorKind {
21    /// A singleton field appeared again.
22    Duplicate {
23        /// The position of the field's first instance.
24        first: usize,
25    },
26    /// A required field is absent.
27    Missing,
28    /// A value is invalid, and the field treats an invalid value as an error.
29    Invalid(FieldValueError),
30    /// The section forbids this field.
31    Forbidden,
32    /// The section rejects fields it neither declares nor forbids.
33    Unknown,
34}
35
36impl FieldError {
37    /// The field's lowercase name, or `None` for an unknown field, whose name is untrusted.
38    pub fn name(&self) -> Option<&'static str> {
39        self.name
40    }
41
42    /// The position of the offending field in the original section, or `None` for a missing field, which has no position.
43    pub fn position(&self) -> Option<usize> {
44        self.position
45    }
46
47    /// What went wrong.
48    pub fn kind(&self) -> FieldErrorKind {
49        self.kind
50    }
51
52    pub(crate) fn duplicate(name: &'static str, position: usize, first: usize) -> Self {
53        Self {
54            name: Some(name),
55            position: Some(position),
56            kind: FieldErrorKind::Duplicate { first },
57        }
58    }
59
60    pub(crate) fn invalid(name: &'static str, position: usize, error: FieldValueError) -> Self {
61        Self {
62            name: Some(name),
63            position: Some(position),
64            kind: FieldErrorKind::Invalid(error),
65        }
66    }
67
68    pub(crate) fn unknown(position: usize) -> Self {
69        Self {
70            name: None,
71            position: Some(position),
72            kind: FieldErrorKind::Unknown,
73        }
74    }
75
76    pub(crate) fn missing(name: &'static str) -> Self {
77        Self {
78            name: Some(name),
79            position: None,
80            kind: FieldErrorKind::Missing,
81        }
82    }
83
84    pub(crate) fn forbidden(name: &'static str, position: usize) -> Self {
85        Self {
86            name: Some(name),
87            position: Some(position),
88            kind: FieldErrorKind::Forbidden,
89        }
90    }
91}
92
93impl fmt::Display for FieldError {
94    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
95        let name = self.name.unwrap_or("unknown");
96        let position = self.position.unwrap_or_default();
97        match self.kind {
98            FieldErrorKind::Duplicate { first } => write!(
99                formatter,
100                "duplicate {name} field at position {position}, first at {first}"
101            ),
102            FieldErrorKind::Missing => write!(formatter, "missing required {name} field"),
103            FieldErrorKind::Invalid(error) => write!(
104                formatter,
105                "invalid {name} field at position {position}: {error}"
106            ),
107            FieldErrorKind::Forbidden => {
108                write!(formatter, "forbidden {name} field at position {position}")
109            }
110            FieldErrorKind::Unknown => write!(formatter, "unexpected field at position {position}"),
111        }
112    }
113}
114
115impl std::error::Error for FieldError {
116    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
117        match &self.kind {
118            FieldErrorKind::Invalid(error) => Some(error),
119            _ => None,
120        }
121    }
122}