1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
//! Tier-2 errors of the `Document` API: small, typed, matchable.
use oneyaml_core::{Diagnostic, Path};
use super::NodeId;
/// Why a [`Document`](crate::Document) navigation or edit failed.
/// Every variant says where it happened or what refused it; a
/// failed edit changes nothing.
#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum EditError {
/// A [`NodeId`](crate::NodeId) that names no node of this document:
/// issued by another document, or its node no longer exists (removed
/// or replaced by an edit).
StaleNode,
/// No mapping entry with that key.
#[non_exhaustive]
NotFound {
/// The path of the missing entry.
at: Path,
},
/// A key segment met a node that is not a mapping.
#[non_exhaustive]
NotAMapping {
/// The path of that node.
at: Path,
},
/// An index segment met a node that is not a sequence.
#[non_exhaustive]
NotASequence {
/// The path of that node.
at: Path,
},
/// An index past the end of a sequence.
#[non_exhaustive]
IndexOutOfBounds {
/// The path of the sequence.
at: Path,
/// The index asked for.
index: usize,
/// The sequence's length.
len: usize,
},
/// The walk met an error region of a restricted document.
#[non_exhaustive]
ErrorNode {
/// The path of the error region.
at: Path,
},
/// The edit touches (or is next to) an error region of a restricted
/// document, or the document cannot be edited at all (its input had
/// encoding errors).
#[non_exhaustive]
ReadOnly {
/// The path of the node the edit was aimed at (empty when unknown).
at: Path,
},
/// The syntax layer refused the edit: its text would not read back as
/// the edited tree ("an edited tree always agrees with its own
/// text"), e.g. a block value moved into a flow collection. Nothing
/// was changed.
///
/// One documented 0.2 limitation is also refused this way rather than
/// as [`EditError::Unsupported`]: under
/// `lenient_inline_colons`, an edit whose validation window holds a
/// `Y0028` warning attached to an element the edit moves, even when
/// its result would be well formed (see [`crate::Document`],
/// *Limitations in 0.2*).
#[non_exhaustive]
NotWellFormed {
/// The path of the node the edit was aimed at (empty when unknown).
at: Path,
},
/// This version cannot make that edit (the reason says which part);
/// nothing was changed.
#[non_exhaustive]
Unsupported {
/// The path of the node the edit was aimed at (empty when unknown).
at: Path,
/// What is not supported.
reason: &'static str,
},
/// The mapping already has an entry with that key.
#[non_exhaustive]
KeyExists {
/// The path of the existing entry.
at: Path,
},
/// The value or the edit was rejected with a coded diagnostic: a limit
/// (`Y2005` `max_depth`, `Y2007` `max_typed_depth`, `Y2002`, `Y2006`),
/// an output setting (`Y1012`, `Y1017`), or a value YAML cannot
/// represent (`Y3015`), or a serde error (`Y30xx`).
#[non_exhaustive]
Rejected {
/// The diagnostic, with its stable `Y####` code.
diagnostic: Box<Diagnostic>,
},
/// the anchor is referenced by aliases outside what the
/// edit removes; remove or replace them first
/// ([`Document::aliases_of`](crate::Document::aliases_of)).
#[non_exhaustive]
AnchorInUse {
/// The anchor name.
anchor: String,
/// The aliases that refer to it, in document order.
aliases: Vec<NodeId>,
},
/// an alias can only refer to an anchored node.
#[non_exhaustive]
NotAnchored {
/// The path of the node without an anchor (empty when unknown).
at: Path,
},
/// the edit would place an alias before its anchor
/// (or where its anchor is not defined), which YAML cannot express.
#[non_exhaustive]
AliasBeforeAnchor {
/// The anchor name.
anchor: String,
},
/// the anchor name is already used in this document.
#[non_exhaustive]
DuplicateAnchor {
/// The anchor name.
anchor: String,
},
/// The anchor name is not valid: YAML 1.2.2 production 102 allows any
/// printable character except white space, line breaks, the byte
/// order mark, and `,` `[` `]` `{` `}`, and at least one.
#[non_exhaustive]
InvalidAnchorName {
/// The name asked for.
name: String,
},
/// A [`Removed`](crate::Removed) node was re-inserted into another
/// document than the one it was removed from.
WrongDocument,
/// Text given to a comment setter cannot be a comment:
/// it holds a line break, the byte order mark, or another character
/// YAML 1.2.2 production 75 (`c-nb-comment-text`) does not allow, or
/// U+0085, U+2028 or U+2029, which some readers take for line breaks
/// (every-reader principle). The text is refused, never
/// mangled.
#[non_exhaustive]
InvalidComment {
/// The text given (the view form, without `#`).
text: String,
},
/// A comment written there would not belong to the owner it was
/// written for when the text is read again, or a
/// comment next to it would change owner: e.g. a footer of a root
/// collection at column 0 (rule 7 makes it the document footer), a
/// document footer before a next document's `---` (rule 1), or a
/// leading comment of a root mapping (rule 3 gives it to the first
/// entry). Nothing was changed; [`Document::attach_comment`](crate::Document::attach_comment)
/// re-owns an existing comment explicitly instead.
#[non_exhaustive]
CommentReattaches {
/// The rule that would decide the owner there.
rule: crate::CommentRule,
},
/// A path string given to a path method is not a path (see
/// [`IntoPath`](crate::IntoPath) and [`Path::parse`]). Nothing was
/// changed.
///
/// ```
/// use oneyaml::{Document, EditError};
///
/// let mut doc = Document::parse("a: 1\n").unwrap();
/// let err = doc.set_path("a..b", 2).unwrap_err();
/// assert!(matches!(err, EditError::InvalidPath { offset: 2, .. }));
/// ```
#[non_exhaustive]
InvalidPath {
/// The UTF-8 byte offset in the path string where it went wrong.
offset: usize,
/// What went wrong.
reason: &'static str,
},
/// An internal invariant failed (a bug; `Y9000`).
Internal,
}
impl EditError {
/// The path where the walk or edit stopped, when the variant names
/// one. (The struct-like variants are `#[non_exhaustive]`, match
/// them with `..`.)
#[must_use]
pub fn at(&self) -> Option<&Path> {
match self {
Self::NotFound { at }
| Self::NotAMapping { at }
| Self::NotASequence { at }
| Self::IndexOutOfBounds { at, .. }
| Self::ErrorNode { at }
| Self::ReadOnly { at }
| Self::NotWellFormed { at }
| Self::Unsupported { at, .. }
| Self::KeyExists { at }
| Self::NotAnchored { at } => Some(at),
Self::StaleNode
| Self::Rejected { .. }
| Self::AnchorInUse { .. }
| Self::AliasBeforeAnchor { .. }
| Self::DuplicateAnchor { .. }
| Self::InvalidAnchorName { .. }
| Self::WrongDocument
| Self::InvalidComment { .. }
| Self::CommentReattaches { .. }
| Self::InvalidPath { .. }
| Self::Internal => None,
}
}
/// The coded diagnostic of [`EditError::Rejected`]: e.g. a value
/// deeper than the document's `max_depth` (a limit diagnostic, whose
/// help names the setting to raise).
///
/// ```
/// use oneyaml::{Document, Settings, code, path};
///
/// let settings = Settings::new().max_depth(4);
/// let mut doc = Document::parse_with("a: 1\n", &settings).unwrap();
/// let deep = vec![vec![vec![vec![vec![1]]]]];
/// let err = doc.set_path(&path!["b"], &deep).unwrap_err();
/// let d = err.diagnostic().expect("a coded refusal");
/// assert_eq!(d.code, code::VALUE_DEPTH_EXCEEDED);
/// assert_eq!(doc.to_string(), "a: 1\n"); // nothing changed
/// ```
#[must_use]
pub fn diagnostic(&self) -> Option<&Diagnostic> {
match self {
Self::Rejected { diagnostic } => Some(diagnostic),
_ => None,
}
}
pub(crate) fn rejected(e: &crate::Error) -> Self {
Self::Rejected {
diagnostic: Box::new(e.diagnostic().clone()),
}
}
/// Fills in an empty `at` (errors raised below the path-aware layer).
pub(crate) fn located(mut self, path: &Path) -> Self {
match &mut self {
Self::ReadOnly { at }
| Self::NotWellFormed { at }
| Self::Unsupported { at, .. }
| Self::NotAnchored { at }
if at.is_empty() =>
{
*at = path.clone();
}
_ => {}
}
self
}
}
impl core::fmt::Display for EditError {
/// Text from the input or the caller (anchor names,
/// comment text, path keys) is shown bounded (a segment, a path of at
/// most 8 KiB); the fields keep it whole.
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use crate::error::{MAX_SEGMENT_CHARS, segment, short_chars, shown_path};
let at = |p: &Path| shown_path(p);
match self {
Self::StaleNode => f.write_str("stale node handle: no such node in this document"),
Self::NotFound { at: p } => write!(f, "no entry at {}", at(p)),
Self::NotAMapping { at: p } => write!(f, "not a mapping at {}", at(p)),
Self::NotASequence { at: p } => write!(f, "not a sequence at {}", at(p)),
Self::IndexOutOfBounds { at: p, index, len } => {
write!(f, "index {index} out of bounds at {} (length {len})", at(p))
}
Self::ErrorNode { at: p } => write!(f, "syntax error region at {}", at(p)),
Self::ReadOnly { at: p } => write!(f, "read-only (error region nearby) at {}", at(p)),
Self::NotWellFormed { at: p } => {
write!(
f,
"the edit at {} would not read back as the edited tree",
at(p)
)
}
Self::Unsupported { at: p, reason } => {
write!(f, "unsupported edit at {}: {reason}", at(p))
}
Self::KeyExists { at: p } => write!(f, "an entry already exists at {}", at(p)),
Self::Rejected { diagnostic } => write!(f, "{diagnostic}"),
Self::AnchorInUse { anchor, aliases } => write!(
f,
"anchor `{}` is still referenced by {} alias(es)",
segment(anchor),
aliases.len()
),
Self::NotAnchored { at: p } => write!(f, "the node at {} has no anchor", at(p)),
Self::AliasBeforeAnchor { anchor } => {
write!(
f,
"an alias `*{}` would come before its anchor",
segment(anchor)
)
}
Self::DuplicateAnchor { anchor } => {
write!(f, "the anchor name `{}` is already in use", segment(anchor))
}
Self::InvalidAnchorName { name } => {
write!(f, "`{}` is not a valid anchor name", segment(name))
}
Self::WrongDocument => f.write_str("the removed node belongs to another document"),
Self::InvalidComment { text } => write!(
f,
"{} cannot be a comment",
short_chars(format_args!("{text:?}"), MAX_SEGMENT_CHARS)
),
Self::CommentReattaches { rule } => write!(
f,
"the comment would belong to another owner there (rule {}: {})",
rule.number(),
rule.summary()
),
Self::InvalidPath { offset, reason } => {
write!(f, "invalid path at byte {offset}: {reason}")
}
Self::Internal => f.write_str("internal error in the edit layer (a bug)"),
}
}
}
impl std::error::Error for EditError {}