1#[cfg(feature = "std")]
4use alloc::sync::Arc;
5use alloc::{
6 string::{String, ToString},
7 vec::Vec,
8};
9use core::fmt;
10
11use crate::scanner::Marker;
12
13#[derive(Clone, Debug)]
28pub struct InputIoError {
29 message: String,
30 #[cfg(feature = "std")]
31 source: Option<Arc<std::io::Error>>,
32}
33
34impl InputIoError {
35 #[must_use]
39 pub fn from_message(message: impl Into<String>) -> Self {
40 Self {
41 message: message.into(),
42 #[cfg(feature = "std")]
43 source: None,
44 }
45 }
46
47 #[cfg(feature = "std")]
49 #[must_use]
50 pub fn from_io(error: std::io::Error) -> Self {
51 Self {
52 message: error.to_string(),
53 source: Some(Arc::new(error)),
54 }
55 }
56
57 #[must_use]
59 pub fn message(&self) -> &str {
60 &self.message
61 }
62
63 #[cfg(feature = "std")]
65 #[must_use]
66 pub fn io_error(&self) -> Option<&std::io::Error> {
67 self.source.as_deref()
68 }
69
70 #[cfg(feature = "std")]
76 pub fn try_into_io_error(self) -> Result<std::io::Error, Self> {
77 let Self { message, source } = self;
78 let Some(source) = source else {
79 return Err(Self {
80 message,
81 source: None,
82 });
83 };
84
85 match Arc::try_unwrap(source) {
86 Ok(error) => Ok(error),
87 Err(source) => Err(Self {
88 message,
89 source: Some(source),
90 }),
91 }
92 }
93}
94
95#[cfg(feature = "std")]
96impl From<std::io::Error> for InputIoError {
97 fn from(error: std::io::Error) -> Self {
98 Self::from_io(error)
99 }
100}
101
102impl PartialEq for InputIoError {
103 fn eq(&self, other: &Self) -> bool {
104 self.message == other.message
105 }
106}
107
108impl Eq for InputIoError {}
109
110impl core::hash::Hash for InputIoError {
111 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
112 core::hash::Hash::hash(&self.message, state);
113 }
114}
115
116impl fmt::Display for InputIoError {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 f.write_str(&self.message)
119 }
120}
121
122impl core::error::Error for InputIoError {
123 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
124 #[cfg(feature = "std")]
125 {
126 self.source
127 .as_deref()
128 .map(|error| error as &(dyn core::error::Error + 'static))
129 }
130
131 #[cfg(not(feature = "std"))]
132 {
133 None
134 }
135 }
136}
137
138#[derive(Clone, PartialEq, Debug, Eq, Hash)]
140#[non_exhaustive]
141pub enum ErrorKind {
142 TooManyComments,
144 InputIo {
146 error: InputIoError,
148 },
149 InputDecoding {
151 message: String,
153 },
154 InputByteLimitExceeded {
156 limit: usize,
158 },
159 UnexpectedEofFlowSequence,
161 UnexpectedEofFlowMapping,
163 UnexpectedEofImplicitFlowMapping,
165 UnexpectedEofBlockSequence,
167 UnexpectedEofBlockMapping,
169 UnexpectedEof,
171 ExpectedStreamStart,
173 DuplicateVersionDirective,
175 UnsupportedYamlMajorVersion,
177 DuplicateTagDirective,
179 ExpectedDocumentStart,
181 MissingDocumentEndBeforeDirective,
183 AnchorCountOverflow,
185 UnknownAnchor,
187 ExpectedNodeContent,
189 ExpectedBlockMappingKey,
191 ExpectedFlowMappingSeparator,
193 ExpectedFlowSequenceSeparator,
195 ExpectedBlockSequenceEntry,
197 UndeclaredTagHandle,
199 MissingIncludeResolver,
201 Custom(String),
203 MultipleDocumentsUnsupported,
205 InputOffsetsWithoutSlice,
207 InputSlicingUnavailable,
209 ExpectedTagBang,
211 ExpectedTagDirectiveBang,
213 InvalidGlobalTagCharacter,
215 SimpleKeyExpected,
217 InvalidSimpleKey,
219 InvalidDocumentEnd,
221 InvalidIndentation,
223 BomInsideDocument,
225 UnexpectedCharacter {
227 character: char,
229 },
230 TabNotAllowed,
232 TabInBlockIndentation,
234 CommentInterceptedScalar,
236 ExpectedWhitespace,
238 CommentNotSeparated,
240 InvalidDirectiveTerminator,
242 MissingYamlVersionSeparator,
244 MissingDirectiveName,
246 InvalidDirectiveName,
248 DirectiveByteLimitExceeded {
250 limit: usize,
252 },
253 TooManyReservedDirectiveParams {
255 limit: usize,
257 },
258 YamlVersionTooLong,
260 MissingYamlVersion,
262 InvalidTagDirectiveTerminator,
264 InvalidTagTerminator,
266 MissingTagUri,
268 UnclosedVerbatimTag,
270 InvalidTagEscape,
272 InvalidTagUtf8LeadingByte,
274 InvalidTagUtf8TrailingByte,
276 InvalidTagUtf8,
278 MissingAnchorOrAliasName,
280 MisplacedFlowCollectionEnd,
282 MismatchedFlowCollectionEnd {
284 open: char,
286 close: char,
288 },
289 UnclosedFlowCollection {
291 open: char,
293 },
294 RecursionLimitExceeded,
296 BlockEntryInFlowCollection,
298 BlockSequenceEntryNotAllowed,
300 InvalidBlockEntryWhitespace,
302 ZeroBlockScalarIndent,
304 InvalidBlockScalarHeader,
306 TabAtBlockScalarStart,
308 InvalidBlockScalarIndent,
310 DocumentIndicatorInQuotedScalar,
312 UnclosedQuotedScalar,
314 TabInIndentation,
316 InvalidQuotedScalarIndent,
318 InvalidTrailingSingleQuotedScalar,
320 InvalidTrailingDoubleQuotedScalar,
322 UnknownQuotedScalarEscape,
324 InvalidQuotedScalarHexEscape,
326 InvalidLowSurrogateHexEscape,
328 InvalidLowSurrogate,
330 MissingLowSurrogate,
332 UnpairedLowSurrogate,
334 InvalidUnicodeEscape,
336 InvalidFlowScalarIndent,
338 PlainScalarStartsWithDashFlowIndicator,
340 TabInPlainScalar,
342 UnexpectedEndOfPlainScalar,
344 MappingKeyNotAllowed,
346 FlowMappingValueAdjacentCollection,
348 InvalidMappingValueWhitespace,
350 InvalidColonPlacement,
352 MappingValueNotAllowed,
354}
355
356#[cfg(feature = "error_messages")]
357impl fmt::Display for ErrorKind {
358 #[allow(clippy::too_many_lines)]
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 match self {
361 Self::TooManyComments => {
362 f.write_str("too many consecutive comments before resolving collection entry")
363 }
364 Self::InputIo { error } => write!(f, "input I/O error: {error}"),
365 Self::InputDecoding { message } => {
366 write!(f, "input decoding error: {message}")
367 }
368 Self::InputByteLimitExceeded { limit } => {
369 write!(f, "input exceeds the configured limit of {limit} bytes")
370 }
371 Self::UnexpectedEofFlowSequence => {
372 f.write_str("unexpected EOF while parsing a flow sequence")
373 }
374 Self::UnexpectedEofFlowMapping => {
375 f.write_str("unexpected EOF while parsing a flow mapping")
376 }
377 Self::UnexpectedEofImplicitFlowMapping => {
378 f.write_str("unexpected EOF while parsing an implicit flow mapping")
379 }
380 Self::UnexpectedEofBlockSequence => {
381 f.write_str("unexpected EOF while parsing a block sequence")
382 }
383 Self::UnexpectedEofBlockMapping => {
384 f.write_str("unexpected EOF while parsing a block mapping")
385 }
386 Self::UnexpectedEof => f.write_str("unexpected eof"),
387 Self::ExpectedStreamStart => f.write_str("did not find expected <stream-start>"),
388 Self::DuplicateVersionDirective => f.write_str("duplicate version directive"),
389 Self::UnsupportedYamlMajorVersion => {
390 f.write_str("unsupported YAML major version")
391 }
392 Self::DuplicateTagDirective => f.write_str(
393 "the TAG directive must only be given at most once per handle in the same document",
394 ),
395 Self::ExpectedDocumentStart => {
396 f.write_str("did not find expected <document start>")
397 }
398 Self::MissingDocumentEndBeforeDirective => {
399 f.write_str("missing explicit document end marker before directive")
400 }
401 Self::AnchorCountOverflow => {
402 f.write_str("while parsing anchor, anchor count exceeded supported limit")
403 }
404 Self::UnknownAnchor => f.write_str("while parsing node, found unknown anchor"),
405 Self::ExpectedNodeContent => {
406 f.write_str("while parsing a node, did not find expected node content")
407 }
408 Self::ExpectedBlockMappingKey => {
409 f.write_str("while parsing a block mapping, did not find expected key")
410 }
411 Self::ExpectedFlowMappingSeparator => {
412 f.write_str("while parsing a flow mapping, did not find expected ',' or '}'")
413 }
414 Self::ExpectedFlowSequenceSeparator => {
415 f.write_str("while parsing a flow sequence, expected ',' or ']'")
416 }
417 Self::ExpectedBlockSequenceEntry => f.write_str(
418 "while parsing a block collection, did not find expected '-' indicator",
419 ),
420 Self::UndeclaredTagHandle => f.write_str("the handle wasn't declared"),
421 Self::MissingIncludeResolver => {
422 f.write_str("No include resolver set for parser stack.")
423 }
424 Self::Custom(message) => f.write_str(message),
425 Self::MultipleDocumentsUnsupported => {
426 f.write_str("multiple documents not supported here")
427 }
428 Self::InputOffsetsWithoutSlice => f.write_str(
429 "internal error: input advertised offsets but did not provide a slice",
430 ),
431 Self::InputSlicingUnavailable => f.write_str(
432 "internal error: input advertised slicing but did not provide a slice",
433 ),
434 Self::ExpectedTagBang => {
435 f.write_str("while scanning a tag, did not find expected '!'")
436 }
437 Self::ExpectedTagDirectiveBang => {
438 f.write_str("while parsing a tag directive, did not find expected '!'")
439 }
440 Self::InvalidGlobalTagCharacter => f.write_str("invalid global tag character"),
441 Self::SimpleKeyExpected => f.write_str("simple key expected ':'"),
442 Self::InvalidSimpleKey => f.write_str("simple key is no longer valid"),
443 Self::InvalidDocumentEnd => {
444 f.write_str("invalid content after document end marker")
445 }
446 Self::InvalidIndentation => f.write_str("invalid indentation"),
447 Self::BomInsideDocument => {
448 f.write_str("a BOM must not appear inside a document")
449 }
450 Self::UnexpectedCharacter { character } => {
451 write!(f, "unexpected character: `{}'", character.escape_default())
452 }
453 Self::TabNotAllowed => f.write_str("tabs disallowed in this context"),
454 Self::TabInBlockIndentation => {
455 f.write_str("tabs disallowed within this context (block indentation)")
456 }
457 Self::CommentInterceptedScalar => {
458 f.write_str("comment intercepting the multiline text")
459 }
460 Self::ExpectedWhitespace => f.write_str("expected whitespace"),
461 Self::CommentNotSeparated => {
462 f.write_str("comments must be separated from other tokens by whitespace")
463 }
464 Self::InvalidDirectiveTerminator => f.write_str(
465 "while scanning a directive, did not find expected comment or line break",
466 ),
467 Self::MissingYamlVersionSeparator => f.write_str(
468 "while scanning a YAML directive, did not find expected digit or '.' character",
469 ),
470 Self::MissingDirectiveName => f.write_str(
471 "while scanning a directive, could not find expected directive name",
472 ),
473 Self::InvalidDirectiveName => f.write_str(
474 "while scanning a directive, found unexpected non-alphabetical character",
475 ),
476 Self::DirectiveByteLimitExceeded { limit } => write!(
477 f,
478 "directive exceeds the configured limit of {limit} bytes"
479 ),
480 Self::TooManyReservedDirectiveParams { limit } => write!(
481 f,
482 "reserved directive exceeds the configured limit of {limit} parameters"
483 ),
484 Self::YamlVersionTooLong => {
485 f.write_str("while scanning a YAML directive, found extremely long version number")
486 }
487 Self::MissingYamlVersion => f.write_str(
488 "while scanning a YAML directive, did not find expected version number",
489 ),
490 Self::InvalidTagDirectiveTerminator => {
491 f.write_str("while scanning TAG, did not find expected whitespace or line break")
492 }
493 Self::InvalidTagTerminator => f.write_str(
494 "while scanning a tag, did not find expected whitespace or line break",
495 ),
496 Self::MissingTagUri => {
497 f.write_str("while parsing a tag, did not find expected tag URI")
498 }
499 Self::UnclosedVerbatimTag => {
500 f.write_str("while scanning a verbatim tag, did not find the expected '>'")
501 }
502 Self::InvalidTagEscape => {
503 f.write_str("while parsing a tag, found an invalid escape sequence")
504 }
505 Self::InvalidTagUtf8LeadingByte => {
506 f.write_str("while parsing a tag, found an incorrect leading UTF-8 byte")
507 }
508 Self::InvalidTagUtf8TrailingByte => {
509 f.write_str("while parsing a tag, found an incorrect trailing UTF-8 byte")
510 }
511 Self::InvalidTagUtf8 => {
512 f.write_str("while parsing a tag, found an invalid UTF-8 codepoint")
513 }
514 Self::MissingAnchorOrAliasName => f.write_str(
515 "while scanning an anchor or alias, did not find expected alphabetic or numeric character",
516 ),
517 Self::MisplacedFlowCollectionEnd => f.write_str("misplaced bracket"),
518 Self::MismatchedFlowCollectionEnd { open, close } => {
519 write!(f, "mismatched bracket '{open}' closed by '{close}'")
520 }
521 Self::UnclosedFlowCollection { open } => {
522 write!(f, "unclosed bracket '{open}'")
523 }
524 Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
525 Self::BlockEntryInFlowCollection => {
526 f.write_str(r#""-" is only valid inside a block"#)
527 }
528 Self::BlockSequenceEntryNotAllowed => {
529 f.write_str("block sequence entries are not allowed in this context")
530 }
531 Self::InvalidBlockEntryWhitespace => {
532 f.write_str("'-' must be followed by a valid YAML whitespace")
533 }
534 Self::ZeroBlockScalarIndent => f.write_str(
535 "while scanning a block scalar, found an indentation indicator equal to 0",
536 ),
537 Self::InvalidBlockScalarHeader => f.write_str(
538 "while scanning a block scalar, did not find expected comment or line break",
539 ),
540 Self::TabAtBlockScalarStart => {
541 f.write_str("a block scalar content cannot start with a tab")
542 }
543 Self::InvalidBlockScalarIndent => {
544 f.write_str("wrongly indented line in block scalar")
545 }
546 Self::DocumentIndicatorInQuotedScalar => f.write_str(
547 "while scanning a quoted scalar, found unexpected document indicator",
548 ),
549 Self::UnclosedQuotedScalar => f.write_str("unclosed quote"),
550 Self::TabInIndentation => f.write_str("tab cannot be used as indentation"),
551 Self::InvalidQuotedScalarIndent => {
552 f.write_str("invalid indentation in multiline quoted scalar")
553 }
554 Self::InvalidTrailingSingleQuotedScalar => {
555 f.write_str("invalid trailing content after single-quoted scalar")
556 }
557 Self::InvalidTrailingDoubleQuotedScalar => {
558 f.write_str("invalid trailing content after double-quoted scalar")
559 }
560 Self::UnknownQuotedScalarEscape => {
561 f.write_str("while parsing a quoted scalar, found unknown escape character")
562 }
563 Self::InvalidQuotedScalarHexEscape => f.write_str(
564 "while parsing a quoted scalar, did not find expected hexadecimal number",
565 ),
566 Self::InvalidLowSurrogateHexEscape => f.write_str(
567 "while parsing a quoted scalar, did not find expected hexadecimal number for low surrogate",
568 ),
569 Self::InvalidLowSurrogate => {
570 f.write_str("while parsing a quoted scalar, found invalid low surrogate")
571 }
572 Self::MissingLowSurrogate => f.write_str(
573 "while parsing a quoted scalar, found high surrogate without following low surrogate",
574 ),
575 Self::UnpairedLowSurrogate => {
576 f.write_str("while parsing a quoted scalar, found unpaired low surrogate")
577 }
578 Self::InvalidUnicodeEscape => f.write_str(
579 "while parsing a quoted scalar, found invalid Unicode character escape code",
580 ),
581 Self::InvalidFlowScalarIndent => {
582 f.write_str("invalid indentation in flow construct")
583 }
584 Self::PlainScalarStartsWithDashFlowIndicator => {
585 f.write_str("plain scalar cannot start with '-' followed by ,[]{}")
586 }
587 Self::TabInPlainScalar => {
588 f.write_str("while scanning a plain scalar, found a tab")
589 }
590 Self::UnexpectedEndOfPlainScalar => f.write_str("unexpected end of plain scalar"),
591 Self::MappingKeyNotAllowed => {
592 f.write_str("mapping keys are not allowed in this context")
593 }
594 Self::FlowMappingValueAdjacentCollection => {
595 f.write_str("':' may not precede any of `[{` in flow mapping")
596 }
597 Self::InvalidMappingValueWhitespace => {
598 f.write_str("':' must be followed by a valid YAML whitespace")
599 }
600 Self::InvalidColonPlacement => f.write_str("illegal placement of ':' indicator"),
601 Self::MappingValueNotAllowed => {
602 f.write_str("mapping values are not allowed in this context")
603 }
604 }
605 }
606}
607
608#[cfg(not(feature = "error_messages"))]
609impl fmt::Display for ErrorKind {
610 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611 f.write_str("")
612 }
613}
614
615#[derive(Clone, PartialEq, Debug, Eq)]
617pub struct ScanError {
618 mark: Marker,
620 kind: ErrorKind,
622 source_stack: Vec<String>,
624}
625
626impl ScanError {
627 #[must_use]
633 #[cold]
634 pub fn new(loc: Marker, message: impl Into<String>) -> ScanError {
635 Self::from_kind(loc, ErrorKind::Custom(message.into()))
636 }
637
638 #[must_use]
639 #[cold]
640 pub(crate) fn from_kind(loc: Marker, kind: ErrorKind) -> ScanError {
641 ScanError {
642 mark: loc,
643 kind,
644 source_stack: Vec::new(),
645 }
646 }
647
648 #[must_use]
649 pub(crate) fn with_source_stack(mut self, source_stack: Vec<String>) -> Self {
650 self.source_stack = source_stack;
651 self
652 }
653
654 #[cold]
655 pub(crate) fn into_result<T>(self) -> Result<T, ScanError> {
656 Err(self)
657 }
658
659 #[must_use]
661 pub fn marker(&self) -> &Marker {
662 &self.mark
663 }
664
665 #[must_use]
667 pub fn kind(&self) -> &ErrorKind {
668 &self.kind
669 }
670
671 pub fn try_into_input_io_error(self) -> Result<InputIoError, Self> {
676 let Self {
677 mark,
678 kind,
679 source_stack,
680 } = self;
681
682 match kind {
683 ErrorKind::InputIo { error } => Ok(error),
684 kind => Err(Self {
685 mark,
686 kind,
687 source_stack,
688 }),
689 }
690 }
691
692 #[must_use]
694 pub fn source_stack(&self) -> &[String] {
695 &self.source_stack
696 }
697
698 #[must_use]
703 pub fn info(&self) -> String {
704 let mut info = self.kind.to_string();
705 if !info.is_empty() && self.source_stack().len() > 1 {
706 info.push_str("\nwhile parsing ");
707 info.push_str(&self.source_stack().join(" -> "));
708 }
709 info
710 }
711}
712
713impl fmt::Display for ScanError {
714 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
715 write!(
716 f,
717 "{} at char {} line {} column {}",
718 self.info(),
719 self.mark.index(),
720 self.mark.line(),
721 self.mark.col() + 1
722 )
723 }
724}
725
726impl core::error::Error for ScanError {
727 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
728 match &self.kind {
729 ErrorKind::InputIo { error } => Some(error),
730 _ => None,
731 }
732 }
733}
734
735#[cfg(test)]
736mod tests {
737 #[cfg(feature = "error_messages")]
738 use alloc::format;
739 #[cfg(feature = "error_messages")]
740 use alloc::string::String;
741 use alloc::string::ToString;
742
743 use super::{ErrorKind, InputIoError, ScanError};
744 use crate::scanner::Marker;
745
746 #[cfg(feature = "error_messages")]
747 #[test]
748 fn constructor_retains_kind_and_derives_info() {
749 let marker = Marker::new(3, 2, 1);
750 let error = ScanError::from_kind(marker, ErrorKind::ExpectedWhitespace);
751
752 assert_eq!(error.kind(), &ErrorKind::ExpectedWhitespace);
753 assert_eq!(error.kind().to_string(), "expected whitespace");
754 assert_eq!(error.info(), "expected whitespace");
755 }
756
757 #[cfg(feature = "error_messages")]
758 #[test]
759 fn parameterized_kind_constructs_info() {
760 let marker = Marker::new(3, 2, 1);
761 let error = ScanError::from_kind(
762 marker,
763 ErrorKind::MismatchedFlowCollectionEnd {
764 open: '[',
765 close: '}',
766 },
767 );
768
769 assert_eq!(error.info(), "mismatched bracket '[' closed by '}'");
770 assert_eq!(
771 format!("{error}"),
772 "mismatched bracket '[' closed by '}' at char 3 line 2 column 2"
773 );
774 }
775
776 #[cfg(feature = "error_messages")]
777 #[test]
778 fn input_error_kinds_construct_info() {
779 assert_eq!(
780 ErrorKind::InputIo {
781 error: InputIoError::from_message("connection reset")
782 }
783 .to_string(),
784 "input I/O error: connection reset"
785 );
786 assert_eq!(
787 ErrorKind::InputDecoding {
788 message: String::from("invalid utf-8")
789 }
790 .to_string(),
791 "input decoding error: invalid utf-8"
792 );
793 assert_eq!(
794 ErrorKind::InputByteLimitExceeded { limit: 4096 }.to_string(),
795 "input exceeds the configured limit of 4096 bytes"
796 );
797 }
798
799 #[test]
800 fn message_only_input_io_error_has_no_source() {
801 use core::error::Error as _;
802
803 let error = InputIoError::from_message("portable failure");
804
805 assert_eq!(error.message(), "portable failure");
806 assert!(error.source().is_none());
807 }
808
809 #[cfg(feature = "std")]
810 #[test]
811 fn std_input_io_error_is_retained_in_scan_error_source_chain() {
812 use core::error::Error as _;
813 use std::io;
814
815 let details = InputIoError::from(io::Error::new(io::ErrorKind::BrokenPipe, "pipe closed"));
816 assert_eq!(details.message(), "pipe closed");
817 assert_eq!(
818 details
819 .io_error()
820 .expect("std construction should retain io::Error")
821 .kind(),
822 io::ErrorKind::BrokenPipe
823 );
824
825 let error = ScanError::from_kind(
826 Marker::new(3, 2, 1),
827 ErrorKind::InputIo {
828 error: details.clone(),
829 },
830 );
831 let input_error = error
832 .source()
833 .and_then(|source| source.downcast_ref::<InputIoError>())
834 .expect("ScanError should expose InputIoError as its source");
835 let io_error = input_error
836 .source()
837 .and_then(|source| source.downcast_ref::<io::Error>())
838 .expect("InputIoError should expose the retained io::Error");
839
840 assert_eq!(io_error.kind(), io::ErrorKind::BrokenPipe);
841 assert_eq!(details, *input_error);
842 }
843
844 #[cfg(feature = "std")]
845 #[test]
846 fn unique_std_input_io_error_can_be_recovered() {
847 use std::io;
848
849 let details = InputIoError::from(io::Error::from_raw_os_error(12_345));
850 let error = details
851 .try_into_io_error()
852 .expect("a uniquely owned io::Error should be recoverable");
853
854 assert_eq!(error.raw_os_error(), Some(12_345));
855 }
856
857 #[cfg(feature = "std")]
858 #[test]
859 fn shared_std_input_io_error_can_be_recovered_after_other_clone_is_dropped() {
860 use std::io;
861
862 let details = InputIoError::from(io::Error::from_raw_os_error(12_345));
863 let other = details.clone();
864 let details = details
865 .try_into_io_error()
866 .expect_err("a shared io::Error cannot be moved out");
867
868 drop(other);
869
870 let error = details
871 .try_into_io_error()
872 .expect("the last owner should recover the io::Error");
873 assert_eq!(error.raw_os_error(), Some(12_345));
874 }
875
876 #[cfg(feature = "std")]
877 #[test]
878 fn scan_error_moves_input_io_error_out_without_cloning() {
879 use std::io;
880
881 let error = ScanError::from_kind(
882 Marker::new(3, 2, 1),
883 ErrorKind::InputIo {
884 error: InputIoError::from(io::Error::from_raw_os_error(12_345)),
885 },
886 );
887 let details = error
888 .try_into_input_io_error()
889 .expect("input I/O details should be extractable");
890 let error = details
891 .try_into_io_error()
892 .expect("extracting the scan error should retain unique ownership");
893
894 assert_eq!(error.raw_os_error(), Some(12_345));
895 }
896
897 #[test]
898 fn extracting_input_io_error_preserves_other_scan_errors() {
899 let error = ScanError::from_kind(Marker::new(3, 2, 1), ErrorKind::ExpectedWhitespace);
900 let error = error
901 .try_into_input_io_error()
902 .expect_err("a non-I/O scan error should be returned unchanged");
903
904 assert_eq!(error.marker(), &Marker::new(3, 2, 1));
905 assert_eq!(error.kind(), &ErrorKind::ExpectedWhitespace);
906 }
907
908 #[cfg(feature = "error_messages")]
909 #[test]
910 fn public_constructor_copies_custom_message() {
911 let marker = Marker::new(3, 2, 1);
912 let mut message = String::from("adapter failed");
913 let error = ScanError::new(marker, &message);
914 message.clear();
915
916 assert_eq!(
917 error.kind(),
918 &ErrorKind::Custom(String::from("adapter failed"))
919 );
920 assert_eq!(error.info(), "adapter failed");
921 }
922
923 #[cfg(not(feature = "error_messages"))]
924 #[test]
925 fn disabled_error_messages_are_empty() {
926 let marker = Marker::new(3, 2, 1);
927 let error = ScanError::from_kind(
928 marker,
929 ErrorKind::MismatchedFlowCollectionEnd {
930 open: '[',
931 close: '}',
932 },
933 );
934
935 assert!(error.kind().to_string().is_empty());
936 assert!(error.info().is_empty());
937 }
938}