1use std::borrow::Cow;
14
15use crate::pos::Span;
16
17#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct EventMeta<'input> {
25 pub anchor: Option<&'input str>,
27 pub anchor_loc: Option<Span>,
30 pub tag: Option<Cow<'input, str>>,
35 pub tag_loc: Option<Span>,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub enum Chomp {
43 Strip,
45 Clip,
47 Keep,
49}
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum CollectionStyle {
57 Block,
59 Flow,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ScalarStyle {
66 Plain,
68 SingleQuoted,
70 DoubleQuoted,
72 Literal(Chomp),
74 Folded(Chomp),
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum Event<'input> {
89 StreamStart,
94 StreamEnd,
100 Comment {
110 text: &'input str,
112 },
113 Alias {
120 name: &'input str,
123 },
124 DocumentStart {
129 explicit: bool,
131 version: Option<(u8, u8)>,
135 tag_directives: Vec<(String, String)>,
140 },
141 DocumentEnd {
146 explicit: bool,
148 },
149 SequenceStart {
154 style: CollectionStyle,
156 meta: Option<Box<EventMeta<'input>>>,
159 },
160 SequenceEnd,
164 MappingStart {
169 style: CollectionStyle,
171 meta: Option<Box<EventMeta<'input>>>,
174 },
175 MappingEnd,
179 Scalar {
185 value: Cow<'input, str>,
187 style: ScalarStyle,
189 meta: Option<Box<EventMeta<'input>>>,
192 },
193}
194
195impl Event<'_> {
196 #[must_use]
198 #[inline]
199 pub fn anchor(&self) -> Option<&str> {
200 match self {
201 Self::Scalar { meta, .. }
202 | Self::SequenceStart { meta, .. }
203 | Self::MappingStart { meta, .. } => meta.as_ref().and_then(|m| m.anchor),
204 Self::StreamStart
205 | Self::StreamEnd
206 | Self::Comment { .. }
207 | Self::Alias { .. }
208 | Self::DocumentStart { .. }
209 | Self::DocumentEnd { .. }
210 | Self::SequenceEnd
211 | Self::MappingEnd => None,
212 }
213 }
214
215 #[must_use]
217 #[inline]
218 pub fn anchor_loc(&self) -> Option<Span> {
219 match self {
220 Self::Scalar { meta, .. }
221 | Self::SequenceStart { meta, .. }
222 | Self::MappingStart { meta, .. } => meta.as_ref().and_then(|m| m.anchor_loc),
223 Self::StreamStart
224 | Self::StreamEnd
225 | Self::Comment { .. }
226 | Self::Alias { .. }
227 | Self::DocumentStart { .. }
228 | Self::DocumentEnd { .. }
229 | Self::SequenceEnd
230 | Self::MappingEnd => None,
231 }
232 }
233
234 #[must_use]
236 #[inline]
237 pub fn tag(&self) -> Option<&str> {
238 match self {
239 Self::Scalar { meta, .. }
240 | Self::SequenceStart { meta, .. }
241 | Self::MappingStart { meta, .. } => meta.as_ref().and_then(|m| m.tag.as_deref()),
242 Self::StreamStart
243 | Self::StreamEnd
244 | Self::Comment { .. }
245 | Self::Alias { .. }
246 | Self::DocumentStart { .. }
247 | Self::DocumentEnd { .. }
248 | Self::SequenceEnd
249 | Self::MappingEnd => None,
250 }
251 }
252
253 #[must_use]
255 #[inline]
256 pub fn tag_loc(&self) -> Option<Span> {
257 match self {
258 Self::Scalar { meta, .. }
259 | Self::SequenceStart { meta, .. }
260 | Self::MappingStart { meta, .. } => meta.as_ref().and_then(|m| m.tag_loc),
261 Self::StreamStart
262 | Self::StreamEnd
263 | Self::Comment { .. }
264 | Self::Alias { .. }
265 | Self::DocumentStart { .. }
266 | Self::DocumentEnd { .. }
267 | Self::SequenceEnd
268 | Self::MappingEnd => None,
269 }
270 }
271}
272
273#[expect(
277 clippy::redundant_pub_crate,
278 reason = "pub(crate) inside private module — accessibility requires crate-wide visibility"
279)]
280#[inline]
281pub(crate) fn make_meta<'input>(
282 anchor: Option<&'input str>,
283 anchor_loc: Option<Span>,
284 tag: Option<Cow<'input, str>>,
285 tag_loc: Option<Span>,
286) -> Option<Box<EventMeta<'input>>> {
287 if anchor.is_none() && tag.is_none() {
288 None
289 } else {
290 Some(Box::new(EventMeta {
291 anchor,
292 anchor_loc,
293 tag,
294 tag_loc,
295 }))
296 }
297}
298
299const _: () = assert!(
300 std::mem::size_of::<Event<'_>>() <= 56,
301 "Event must be at most 56 bytes after EventMeta boxing"
302);
303
304#[cfg(test)]
305mod tests {
306 use std::borrow::Cow;
307
308 use rstest::rstest;
309
310 use super::*;
311 use crate::pos::Span;
312
313 const SPAN: Span = Span { start: 0, end: 4 };
314 const SPAN2: Span = Span { start: 5, end: 9 };
315
316 #[test]
318 fn make_meta_returns_none_when_all_fields_absent() {
319 let meta = make_meta(None, None, None, None);
320 assert!(
321 meta.is_none(),
322 "make_meta must return None when anchor and tag are both None"
323 );
324 }
325
326 #[test]
328 fn make_meta_returns_some_when_anchor_only() {
329 let meta = make_meta(Some("a"), Some(SPAN), None, None).unwrap();
330 assert_eq!(meta.anchor, Some("a"));
331 assert_eq!(meta.anchor_loc, Some(SPAN));
332 assert!(meta.tag.is_none());
333 assert!(meta.tag_loc.is_none());
334 }
335
336 #[test]
338 fn make_meta_returns_some_when_tag_only() {
339 let meta = make_meta(None, None, Some(Cow::Borrowed("!str")), Some(SPAN)).unwrap();
340 assert!(meta.anchor.is_none());
341 assert!(meta.anchor_loc.is_none());
342 assert_eq!(meta.tag.as_deref(), Some("!str"));
343 assert_eq!(meta.tag_loc, Some(SPAN));
344 }
345
346 #[test]
348 fn make_meta_returns_some_when_both_anchor_and_tag() {
349 let meta = make_meta(
350 Some("a"),
351 Some(SPAN),
352 Some(Cow::Borrowed("!str")),
353 Some(SPAN),
354 )
355 .unwrap();
356 assert_eq!(meta.anchor, Some("a"));
357 assert_eq!(meta.tag.as_deref(), Some("!str"));
358 }
359
360 #[test]
362 fn event_size_at_most_56_bytes() {
363 assert!(
364 std::mem::size_of::<Event<'_>>() <= 56,
365 "Event size {} exceeds 56 bytes",
366 std::mem::size_of::<Event<'_>>()
367 );
368 }
369
370 fn meta_anchor_only() -> Option<Box<EventMeta<'static>>> {
379 make_meta(Some("anc"), Some(SPAN), None, None)
380 }
381
382 fn meta_tag_only() -> Option<Box<EventMeta<'static>>> {
383 make_meta(None, None, Some(Cow::Borrowed("!str")), Some(SPAN2))
384 }
385
386 fn meta_both() -> Option<Box<EventMeta<'static>>> {
387 make_meta(
388 Some("anc"),
389 Some(SPAN),
390 Some(Cow::Borrowed("!str")),
391 Some(SPAN2),
392 )
393 }
394
395 #[rstest]
398 #[case::scalar_no_meta(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: None }, None)]
400 #[case::sequence_start_no_meta(Event::SequenceStart { style: CollectionStyle::Block, meta: None }, None)]
401 #[case::mapping_start_no_meta(Event::MappingStart { style: CollectionStyle::Block, meta: None }, None)]
402 #[case::scalar_anchor_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_anchor_only() }, Some("anc"))]
404 #[case::sequence_start_anchor_only(Event::SequenceStart { style: CollectionStyle::Block, meta: meta_anchor_only() }, Some("anc"))]
405 #[case::mapping_start_anchor_only(Event::MappingStart { style: CollectionStyle::Block, meta: meta_anchor_only() }, Some("anc"))]
406 #[case::scalar_tag_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_tag_only() }, None)]
408 #[case::scalar_both(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_both() }, Some("anc"))]
410 #[case::stream_start(Event::StreamStart, None)]
412 #[case::stream_end(Event::StreamEnd, None)]
413 #[case::comment(Event::Comment { text: "hi" }, None)]
414 #[case::alias(Event::Alias { name: "x" }, None)]
415 #[case::document_start(Event::DocumentStart { explicit: false, version: None, tag_directives: vec![] }, None)]
416 #[case::document_end(Event::DocumentEnd { explicit: false }, None)]
417 #[case::sequence_end(Event::SequenceEnd, None)]
418 #[case::mapping_end(Event::MappingEnd, None)]
419 fn anchor_returns_expected(#[case] event: Event<'_>, #[case] expected: Option<&str>) {
420 assert_eq!(event.anchor(), expected);
421 }
422
423 #[rstest]
426 #[case::scalar_no_meta(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: None }, None)]
428 #[case::sequence_start_no_meta(Event::SequenceStart { style: CollectionStyle::Block, meta: None }, None)]
429 #[case::mapping_start_no_meta(Event::MappingStart { style: CollectionStyle::Block, meta: None }, None)]
430 #[case::scalar_anchor_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_anchor_only() }, Some(SPAN))]
432 #[case::sequence_start_anchor_only(Event::SequenceStart { style: CollectionStyle::Block, meta: meta_anchor_only() }, Some(SPAN))]
433 #[case::mapping_start_anchor_only(Event::MappingStart { style: CollectionStyle::Block, meta: meta_anchor_only() }, Some(SPAN))]
434 #[case::scalar_tag_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_tag_only() }, None)]
436 #[case::scalar_both(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_both() }, Some(SPAN))]
438 #[case::stream_start(Event::StreamStart, None)]
440 #[case::stream_end(Event::StreamEnd, None)]
441 #[case::comment(Event::Comment { text: "hi" }, None)]
442 #[case::alias(Event::Alias { name: "x" }, None)]
443 #[case::document_start(Event::DocumentStart { explicit: false, version: None, tag_directives: vec![] }, None)]
444 #[case::document_end(Event::DocumentEnd { explicit: false }, None)]
445 #[case::sequence_end(Event::SequenceEnd, None)]
446 #[case::mapping_end(Event::MappingEnd, None)]
447 fn anchor_loc_returns_expected(#[case] event: Event<'_>, #[case] expected: Option<Span>) {
448 assert_eq!(event.anchor_loc(), expected);
449 }
450
451 #[rstest]
454 #[case::scalar_no_meta(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: None }, None)]
456 #[case::sequence_start_no_meta(Event::SequenceStart { style: CollectionStyle::Block, meta: None }, None)]
457 #[case::mapping_start_no_meta(Event::MappingStart { style: CollectionStyle::Block, meta: None }, None)]
458 #[case::scalar_anchor_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_anchor_only() }, None)]
460 #[case::scalar_tag_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_tag_only() }, Some("!str"))]
462 #[case::sequence_start_tag_only(Event::SequenceStart { style: CollectionStyle::Block, meta: meta_tag_only() }, Some("!str"))]
463 #[case::mapping_start_tag_only(Event::MappingStart { style: CollectionStyle::Block, meta: meta_tag_only() }, Some("!str"))]
464 #[case::scalar_both(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_both() }, Some("!str"))]
466 #[case::stream_start(Event::StreamStart, None)]
468 #[case::stream_end(Event::StreamEnd, None)]
469 #[case::comment(Event::Comment { text: "hi" }, None)]
470 #[case::alias(Event::Alias { name: "x" }, None)]
471 #[case::document_start(Event::DocumentStart { explicit: false, version: None, tag_directives: vec![] }, None)]
472 #[case::document_end(Event::DocumentEnd { explicit: false }, None)]
473 #[case::sequence_end(Event::SequenceEnd, None)]
474 #[case::mapping_end(Event::MappingEnd, None)]
475 fn tag_returns_expected(#[case] event: Event<'_>, #[case] expected: Option<&str>) {
476 assert_eq!(event.tag(), expected);
477 }
478
479 #[rstest]
482 #[case::scalar_no_meta(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: None }, None)]
484 #[case::sequence_start_no_meta(Event::SequenceStart { style: CollectionStyle::Block, meta: None }, None)]
485 #[case::mapping_start_no_meta(Event::MappingStart { style: CollectionStyle::Block, meta: None }, None)]
486 #[case::scalar_anchor_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_anchor_only() }, None)]
488 #[case::scalar_tag_only(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_tag_only() }, Some(SPAN2))]
490 #[case::sequence_start_tag_only(Event::SequenceStart { style: CollectionStyle::Block, meta: meta_tag_only() }, Some(SPAN2))]
491 #[case::mapping_start_tag_only(Event::MappingStart { style: CollectionStyle::Block, meta: meta_tag_only() }, Some(SPAN2))]
492 #[case::scalar_both(Event::Scalar { value: Cow::Borrowed("v"), style: ScalarStyle::Plain, meta: meta_both() }, Some(SPAN2))]
494 #[case::stream_start(Event::StreamStart, None)]
496 #[case::stream_end(Event::StreamEnd, None)]
497 #[case::comment(Event::Comment { text: "hi" }, None)]
498 #[case::alias(Event::Alias { name: "x" }, None)]
499 #[case::document_start(Event::DocumentStart { explicit: false, version: None, tag_directives: vec![] }, None)]
500 #[case::document_end(Event::DocumentEnd { explicit: false }, None)]
501 #[case::sequence_end(Event::SequenceEnd, None)]
502 #[case::mapping_end(Event::MappingEnd, None)]
503 fn tag_loc_returns_expected(#[case] event: Event<'_>, #[case] expected: Option<Span>) {
504 assert_eq!(event.tag_loc(), expected);
505 }
506}