1use std::fmt;
4
5use crate::{RdNode, RdTag};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct RdShapeError {
10 path: crate::RdPath,
11 tag: Option<RdTag>,
12 kind: RdShapeErrorKind,
13}
14
15impl RdShapeError {
16 pub(crate) fn new(path: crate::RdPath, tag: Option<RdTag>, kind: RdShapeErrorKind) -> Self {
17 Self { path, tag, kind }
18 }
19 pub fn path(&self) -> &crate::RdPath {
20 &self.path
21 }
22 pub fn tag(&self) -> Option<&RdTag> {
23 self.tag.as_ref()
24 }
25 pub fn kind(&self) -> &RdShapeErrorKind {
26 &self.kind
27 }
28}
29
30impl fmt::Display for RdShapeError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 write!(f, "{}", self.kind)?;
33 if let Some(tag) = &self.tag {
34 write!(f, " for {}", tag.as_rd_tag())?;
35 }
36 write!(f, " at {}", self.path)
37 }
38}
39
40impl std::error::Error for RdShapeError {}
41
42#[derive(Debug, Clone, PartialEq, Eq)]
44#[non_exhaustive]
45pub enum RdShapeErrorKind {
46 UnexpectedNode {
47 expected: RdExpectedNode,
48 actual: RdNodeKind,
49 },
50 UnexpectedOption,
51 MissingOption,
52 WrongArity {
53 expected: RdArity,
54 actual: usize,
55 },
56 Duplicate {
57 construct: RdConstruct,
58 first_path: crate::RdPath,
59 },
60 UnexpectedContent {
61 actual: RdNodeKind,
62 },
63 InvalidValue {
64 construct: RdConstruct,
65 value: String,
66 },
67 CountMismatch {
68 construct: RdConstruct,
69 expected: usize,
70 actual: usize,
71 },
72 MissingFollowing {
73 construct: RdConstruct,
74 },
75 DefinitionMismatch {
76 construct: RdConstruct,
77 },
78 UnsupportedRepresentation {
79 construct: RdConstruct,
80 },
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq)]
84#[non_exhaustive]
85pub enum RdNodeKind {
86 Text,
87 RCode,
88 Verb,
89 Comment,
90 Tagged,
91 Group,
92 Raw,
93}
94
95impl RdNodeKind {
96 pub fn of(node: &RdNode) -> Self {
97 match node {
98 RdNode::Text(_) => Self::Text,
99 RdNode::RCode(_) => Self::RCode,
100 RdNode::Verb(_) => Self::Verb,
101 RdNode::Comment(_) => Self::Comment,
102 RdNode::Tagged(_) => Self::Tagged,
103 RdNode::Group(_) => Self::Group,
104 RdNode::Raw(_) => Self::Raw,
105 }
106 }
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110#[non_exhaustive]
111pub enum RdArity {
112 Exactly(usize),
113 Range { min: usize, max: usize },
114 AtLeast(usize),
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118#[non_exhaustive]
119pub enum RdExpectedNode {
120 Tagged,
121 Link,
122 Href,
123 List,
124 Tabular,
125 Equation,
126 Sexpr,
127 RdOpts,
128 Group,
129 Item,
130 TextLike,
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
134#[non_exhaustive]
135pub enum RdConstruct {
136 Tag(RdTag),
137 OptionKey(String),
138 ColumnSpec,
139 TableRow,
140 SystemMacro(String),
141 SystemMacroExpansion(String),
142}
143
144pub fn is_inter_item_trivia(node: &RdNode) -> bool {
146 matches!(node, RdNode::Text(text) if text.trim().is_empty())
147 || matches!(node, RdNode::Comment(_))
148}
149
150impl fmt::Display for RdShapeErrorKind {
151 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152 match self {
153 Self::UnexpectedNode { expected, actual } => {
154 write!(f, "expected {expected}, found {actual}")
155 }
156 Self::UnexpectedOption => f.write_str("unexpected option"),
157 Self::MissingOption => f.write_str("missing option"),
158 Self::WrongArity { expected, actual } => {
159 write!(f, "expected {expected} children, found {actual}")
160 }
161 Self::Duplicate { construct, .. } => write!(f, "duplicate {construct}"),
162 Self::UnexpectedContent { actual } => write!(f, "unexpected {actual} content"),
163 Self::InvalidValue { construct, value } => {
164 write!(f, "invalid {construct} value '{value}'")
165 }
166 Self::CountMismatch {
167 construct,
168 expected,
169 actual,
170 } => write!(f, "expected {expected} {construct} entries, found {actual}"),
171 Self::MissingFollowing { construct } => write!(f, "missing following {construct}"),
172 Self::DefinitionMismatch { construct } => {
173 write!(f, "definition mismatch for {construct}")
174 }
175 Self::UnsupportedRepresentation { construct } => {
176 write!(f, "unsupported representation for {construct}")
177 }
178 }
179 }
180}
181
182impl fmt::Display for RdNodeKind {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 write!(
185 f,
186 "{} node",
187 match self {
188 Self::Text => "text",
189 Self::RCode => "R code",
190 Self::Verb => "verbatim",
191 Self::Comment => "comment",
192 Self::Tagged => "tagged",
193 Self::Group => "group",
194 Self::Raw => "raw",
195 }
196 )
197 }
198}
199impl fmt::Display for RdExpectedNode {
200 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201 f.write_str(match self {
202 Self::Tagged => "tagged node",
203 Self::Link => r"\link node",
204 Self::Href => r"\href node",
205 Self::List => "list node",
206 Self::Tabular => r"\tabular node",
207 Self::Equation => "equation node",
208 Self::Sexpr => r"\Sexpr node",
209 Self::RdOpts => r"\RdOpts node",
210 Self::Group => "group",
211 Self::Item => "item",
212 Self::TextLike => "text-like node",
213 })
214 }
215}
216impl fmt::Display for RdArity {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 match self {
219 Self::Exactly(n) => write!(f, "exactly {n}"),
220 Self::Range { min, max } => write!(f, "{min}..={max}"),
221 Self::AtLeast(n) => write!(f, "at least {n}"),
222 }
223 }
224}
225impl fmt::Display for RdConstruct {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 match self {
228 Self::Tag(tag) => write!(f, "{}", tag.as_rd_tag()),
229 Self::OptionKey(key) => write!(f, "option {key}"),
230 Self::ColumnSpec => f.write_str("column specification"),
231 Self::TableRow => f.write_str("table row"),
232 Self::SystemMacro(name) => write!(f, "system macro {name}"),
233 Self::SystemMacroExpansion(name) => write!(f, "system macro {name} expansion"),
234 }
235 }
236}
237
238impl UnexpectedRawNode {
239 pub fn shape_error(&self, path: crate::RdPath) -> RdShapeError {
241 RdShapeError::new(
242 path,
243 self.tag().map(RdTag::from_rd_tag),
244 RdShapeErrorKind::UnexpectedNode {
245 expected: RdExpectedNode::Tagged,
246 actual: RdNodeKind::Raw,
247 },
248 )
249 }
250}
251
252use crate::UnexpectedRawNode;
253
254#[cfg(test)]
255mod tests {
256 use super::*;
257 #[test]
258 fn displays_shape_error() {
259 let error = RdShapeError::new(
260 crate::RdPath::new(vec![crate::RdPathSegment::TopLevel(2)]),
261 Some(RdTag::Title),
262 RdShapeErrorKind::UnexpectedOption,
263 );
264 assert_eq!(
265 error.to_string(),
266 r"unexpected option for \title at top-level[2]"
267 );
268 }
269
270 #[test]
271 fn displays_value_and_count_shape_errors() {
272 let path = crate::RdPath::new(vec![crate::RdPathSegment::TopLevel(0)]);
273 let invalid = RdShapeError::new(
274 path.clone(),
275 None,
276 RdShapeErrorKind::InvalidValue {
277 construct: RdConstruct::ColumnSpec,
278 value: "bad".into(),
279 },
280 );
281 assert_eq!(
282 invalid.to_string(),
283 "invalid column specification value 'bad' at top-level[0]"
284 );
285 let count = RdShapeError::new(
286 path,
287 None,
288 RdShapeErrorKind::CountMismatch {
289 construct: RdConstruct::TableRow,
290 expected: 2,
291 actual: 3,
292 },
293 );
294 assert_eq!(
295 count.to_string(),
296 "expected 2 table row entries, found 3 at top-level[0]"
297 );
298 }
299
300 #[cfg(feature = "rds")]
301 #[test]
302 fn unexpected_raw_classifier_bridges_to_shared_shape_error() {
303 let raw = crate::producer::raw_node(Some("OTHER".into()), None, vec![], None, vec![]);
304 let crate::RawNodeClassification::Unexpected(unexpected) = crate::classify_raw_node(&raw)
305 else {
306 panic!("expected an unexpected raw classification");
307 };
308 let path = crate::RdPath::new(vec![crate::RdPathSegment::TopLevel(4)]);
309 let error = unexpected.shape_error(path.clone());
310 assert_eq!(error.path(), &path);
311 assert_eq!(error.tag(), Some(&crate::RdTag::Unknown("OTHER".into())));
312 assert!(matches!(
313 error.kind(),
314 RdShapeErrorKind::UnexpectedNode {
315 expected: RdExpectedNode::Tagged,
316 actual: RdNodeKind::Raw
317 }
318 ));
319 }
320}