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 #[cfg(feature = "comments")]
144 TooManyComments,
145 InputIo {
147 error: InputIoError,
149 },
150 InputDecoding {
152 message: String,
154 },
155 InputByteLimitExceeded {
157 limit: usize,
159 },
160 UnexpectedEofFlowSequence,
162 UnexpectedEofFlowMapping,
164 UnexpectedEofImplicitFlowMapping,
166 UnexpectedEofBlockSequence,
168 UnexpectedEofBlockMapping,
170 UnexpectedEof,
172 ExpectedStreamStart,
174 DuplicateVersionDirective,
176 UnsupportedYamlMajorVersion,
178 DuplicateTagDirective,
180 ExpectedDocumentStart,
182 MissingDocumentEndBeforeDirective,
184 AnchorCountOverflow,
186 UnknownAnchor,
188 ExpectedNodeContent,
190 ExpectedBlockMappingKey,
192 ExpectedFlowMappingSeparator,
194 ExpectedFlowSequenceSeparator,
196 ExpectedBlockSequenceEntry,
198 UndeclaredTagHandle,
200 MissingIncludeResolver,
202 Custom(String),
204 MultipleDocumentsUnsupported,
206 InputOffsetsWithoutSlice,
208 InputSlicingUnavailable,
210 ExpectedTagBang,
212 ExpectedTagDirectiveBang,
214 InvalidGlobalTagCharacter,
216 SimpleKeyExpected,
218 InvalidSimpleKey,
220 InvalidDocumentEnd,
222 InvalidIndentation,
224 BomInsideDocument,
226 UnexpectedCharacter {
228 character: char,
230 },
231 TabNotAllowed,
233 TabInBlockIndentation,
235 CommentInterceptedScalar,
237 ExpectedWhitespace,
239 CommentNotSeparated,
241 InvalidDirectiveTerminator,
243 DirectiveDisallowed,
245 MissingYamlVersionSeparator,
247 MissingDirectiveName,
249 InvalidDirectiveName,
251 DirectiveByteLimitExceeded {
253 limit: usize,
255 },
256 TooManyReservedDirectiveParams {
258 limit: usize,
260 },
261 YamlVersionTooLong,
263 MissingYamlVersion,
265 InvalidTagDirectiveTerminator,
267 InvalidTagTerminator,
269 MissingTagUri,
271 UnclosedVerbatimTag,
273 InvalidTagEscape,
275 InvalidTagUtf8LeadingByte,
277 InvalidTagUtf8TrailingByte,
279 InvalidTagUtf8,
281 MissingAnchorOrAliasName,
283 MisplacedFlowCollectionEnd,
285 MismatchedFlowCollectionEnd {
287 open: char,
289 close: char,
291 },
292 UnclosedFlowCollection {
294 open: char,
296 },
297 RecursionLimitExceeded,
299 BlockEntryInFlowCollection,
301 BlockSequenceEntryNotAllowed,
303 InvalidBlockEntryWhitespace,
305 ZeroBlockScalarIndent,
307 InvalidBlockScalarHeader,
309 TabAtBlockScalarStart,
311 InvalidBlockScalarIndent,
313 DocumentIndicatorInQuotedScalar,
315 UnclosedQuotedScalar,
317 TabInIndentation,
319 InvalidQuotedScalarIndent,
321 InvalidTrailingSingleQuotedScalar,
323 InvalidTrailingDoubleQuotedScalar,
325 UnknownQuotedScalarEscape,
327 InvalidQuotedScalarHexEscape,
329 InvalidLowSurrogateHexEscape,
331 InvalidLowSurrogate,
333 MissingLowSurrogate,
335 UnpairedLowSurrogate,
337 InvalidUnicodeEscape,
339 InvalidFlowScalarIndent,
341 PlainScalarStartsWithDashFlowIndicator,
343 TabInPlainScalar,
345 UnexpectedEndOfPlainScalar,
347 MappingKeyNotAllowed,
349 FlowMappingValueAdjacentCollection,
351 InvalidMappingValueWhitespace,
353 InvalidColonPlacement,
355 MappingValueNotAllowed,
357}
358
359#[cfg(feature = "error_messages")]
360impl fmt::Display for ErrorKind {
361 #[allow(clippy::too_many_lines)]
362 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
363 match self {
364 #[cfg(feature = "comments")]
365 Self::TooManyComments => {
366 f.write_str("too many buffered comments while looking ahead")
367 }
368 Self::InputIo { error } => write!(f, "input I/O error: {error}"),
369 Self::InputDecoding { message } => {
370 write!(f, "input decoding error: {message}")
371 }
372 Self::InputByteLimitExceeded { limit } => {
373 write!(f, "input exceeds the configured limit of {limit} bytes")
374 }
375 Self::UnexpectedEofFlowSequence => {
376 f.write_str("unexpected EOF while parsing a flow sequence")
377 }
378 Self::UnexpectedEofFlowMapping => {
379 f.write_str("unexpected EOF while parsing a flow mapping")
380 }
381 Self::UnexpectedEofImplicitFlowMapping => {
382 f.write_str("unexpected EOF while parsing an implicit flow mapping")
383 }
384 Self::UnexpectedEofBlockSequence => {
385 f.write_str("unexpected EOF while parsing a block sequence")
386 }
387 Self::UnexpectedEofBlockMapping => {
388 f.write_str("unexpected EOF while parsing a block mapping")
389 }
390 Self::UnexpectedEof => f.write_str("unexpected eof"),
391 Self::ExpectedStreamStart => f.write_str("did not find expected <stream-start>"),
392 Self::DuplicateVersionDirective => f.write_str("duplicate version directive"),
393 Self::UnsupportedYamlMajorVersion => {
394 f.write_str("unsupported YAML major version")
395 }
396 Self::DuplicateTagDirective => f.write_str(
397 "the TAG directive must only be given at most once per handle in the same document",
398 ),
399 Self::ExpectedDocumentStart => {
400 f.write_str("did not find expected <document start>")
401 }
402 Self::MissingDocumentEndBeforeDirective => {
403 f.write_str("missing explicit document end marker before directive")
404 }
405 Self::AnchorCountOverflow => {
406 f.write_str("while parsing anchor, anchor count exceeded supported limit")
407 }
408 Self::UnknownAnchor => f.write_str("while parsing node, found unknown anchor"),
409 Self::ExpectedNodeContent => {
410 f.write_str("while parsing a node, did not find expected node content")
411 }
412 Self::ExpectedBlockMappingKey => {
413 f.write_str("while parsing a block mapping, did not find expected key")
414 }
415 Self::ExpectedFlowMappingSeparator => {
416 f.write_str("while parsing a flow mapping, did not find expected ',' or '}'")
417 }
418 Self::ExpectedFlowSequenceSeparator => {
419 f.write_str("while parsing a flow sequence, expected ',' or ']'")
420 }
421 Self::ExpectedBlockSequenceEntry => f.write_str(
422 "while parsing a block collection, did not find expected '-' indicator",
423 ),
424 Self::UndeclaredTagHandle => f.write_str("the handle wasn't declared"),
425 Self::MissingIncludeResolver => {
426 f.write_str("No include resolver set for parser stack.")
427 }
428 Self::Custom(message) => f.write_str(message),
429 Self::MultipleDocumentsUnsupported => {
430 f.write_str("multiple documents not supported here")
431 }
432 Self::InputOffsetsWithoutSlice => f.write_str(
433 "internal error: input advertised offsets but did not provide a slice",
434 ),
435 Self::InputSlicingUnavailable => f.write_str(
436 "internal error: input advertised slicing but did not provide a slice",
437 ),
438 Self::ExpectedTagBang => {
439 f.write_str("while scanning a tag, did not find expected '!'")
440 }
441 Self::ExpectedTagDirectiveBang => {
442 f.write_str("while parsing a tag directive, did not find expected '!'")
443 }
444 Self::InvalidGlobalTagCharacter => f.write_str("invalid global tag character"),
445 Self::SimpleKeyExpected => f.write_str("simple key expected ':'"),
446 Self::InvalidSimpleKey => f.write_str("simple key is no longer valid"),
447 Self::InvalidDocumentEnd => {
448 f.write_str("invalid content after document end marker")
449 }
450 Self::InvalidIndentation => f.write_str("invalid indentation"),
451 Self::BomInsideDocument => {
452 f.write_str("a BOM must not appear inside a document")
453 }
454 Self::UnexpectedCharacter { character } => {
455 write!(f, "unexpected character: `{}'", character.escape_default())
456 }
457 Self::TabNotAllowed => f.write_str("tabs disallowed in this context"),
458 Self::TabInBlockIndentation => {
459 f.write_str("tabs disallowed within this context (block indentation)")
460 }
461 Self::CommentInterceptedScalar => {
462 f.write_str("comment intercepting the multiline text")
463 }
464 Self::ExpectedWhitespace => f.write_str("expected whitespace"),
465 Self::CommentNotSeparated => {
466 f.write_str("comments must be separated from other tokens by whitespace")
467 }
468 Self::InvalidDirectiveTerminator => f.write_str(
469 "while scanning a directive, did not find expected comment or line break",
470 ),
471 Self::DirectiveDisallowed => f.write_str("YAML directives are disallowed"),
472 Self::MissingYamlVersionSeparator => f.write_str(
473 "while scanning a YAML directive, did not find expected digit or '.' character",
474 ),
475 Self::MissingDirectiveName => f.write_str(
476 "while scanning a directive, could not find expected directive name",
477 ),
478 Self::InvalidDirectiveName => f.write_str(
479 "while scanning a directive, found unexpected non-alphabetical character",
480 ),
481 Self::DirectiveByteLimitExceeded { limit } => write!(
482 f,
483 "directive exceeds the configured limit of {limit} bytes"
484 ),
485 Self::TooManyReservedDirectiveParams { limit } => write!(
486 f,
487 "reserved directive exceeds the configured limit of {limit} parameters"
488 ),
489 Self::YamlVersionTooLong => {
490 f.write_str("while scanning a YAML directive, found extremely long version number")
491 }
492 Self::MissingYamlVersion => f.write_str(
493 "while scanning a YAML directive, did not find expected version number",
494 ),
495 Self::InvalidTagDirectiveTerminator => {
496 f.write_str("while scanning TAG, did not find expected whitespace or line break")
497 }
498 Self::InvalidTagTerminator => f.write_str(
499 "while scanning a tag, did not find expected whitespace or line break",
500 ),
501 Self::MissingTagUri => {
502 f.write_str("while parsing a tag, did not find expected tag URI")
503 }
504 Self::UnclosedVerbatimTag => {
505 f.write_str("while scanning a verbatim tag, did not find the expected '>'")
506 }
507 Self::InvalidTagEscape => {
508 f.write_str("while parsing a tag, found an invalid escape sequence")
509 }
510 Self::InvalidTagUtf8LeadingByte => {
511 f.write_str("while parsing a tag, found an incorrect leading UTF-8 byte")
512 }
513 Self::InvalidTagUtf8TrailingByte => {
514 f.write_str("while parsing a tag, found an incorrect trailing UTF-8 byte")
515 }
516 Self::InvalidTagUtf8 => {
517 f.write_str("while parsing a tag, found an invalid UTF-8 codepoint")
518 }
519 Self::MissingAnchorOrAliasName => f.write_str(
520 "while scanning an anchor or alias, did not find expected alphabetic or numeric character",
521 ),
522 Self::MisplacedFlowCollectionEnd => f.write_str("misplaced bracket"),
523 Self::MismatchedFlowCollectionEnd { open, close } => {
524 write!(f, "mismatched bracket '{open}' closed by '{close}'")
525 }
526 Self::UnclosedFlowCollection { open } => {
527 write!(f, "unclosed bracket '{open}'")
528 }
529 Self::RecursionLimitExceeded => f.write_str("recursion limit exceeded"),
530 Self::BlockEntryInFlowCollection => {
531 f.write_str(r#""-" is only valid inside a block"#)
532 }
533 Self::BlockSequenceEntryNotAllowed => {
534 f.write_str("block sequence entries are not allowed in this context")
535 }
536 Self::InvalidBlockEntryWhitespace => {
537 f.write_str("'-' must be followed by a valid YAML whitespace")
538 }
539 Self::ZeroBlockScalarIndent => f.write_str(
540 "while scanning a block scalar, found an indentation indicator equal to 0",
541 ),
542 Self::InvalidBlockScalarHeader => f.write_str(
543 "while scanning a block scalar, did not find expected comment or line break",
544 ),
545 Self::TabAtBlockScalarStart => {
546 f.write_str("a block scalar content cannot start with a tab")
547 }
548 Self::InvalidBlockScalarIndent => {
549 f.write_str("wrongly indented line in block scalar")
550 }
551 Self::DocumentIndicatorInQuotedScalar => f.write_str(
552 "while scanning a quoted scalar, found unexpected document indicator",
553 ),
554 Self::UnclosedQuotedScalar => f.write_str("unclosed quote"),
555 Self::TabInIndentation => f.write_str("tab cannot be used as indentation"),
556 Self::InvalidQuotedScalarIndent => {
557 f.write_str("invalid indentation in multiline quoted scalar")
558 }
559 Self::InvalidTrailingSingleQuotedScalar => {
560 f.write_str("invalid trailing content after single-quoted scalar")
561 }
562 Self::InvalidTrailingDoubleQuotedScalar => {
563 f.write_str("invalid trailing content after double-quoted scalar")
564 }
565 Self::UnknownQuotedScalarEscape => {
566 f.write_str("while parsing a quoted scalar, found unknown escape character")
567 }
568 Self::InvalidQuotedScalarHexEscape => f.write_str(
569 "while parsing a quoted scalar, did not find expected hexadecimal number",
570 ),
571 Self::InvalidLowSurrogateHexEscape => f.write_str(
572 "while parsing a quoted scalar, did not find expected hexadecimal number for low surrogate",
573 ),
574 Self::InvalidLowSurrogate => {
575 f.write_str("while parsing a quoted scalar, found invalid low surrogate")
576 }
577 Self::MissingLowSurrogate => f.write_str(
578 "while parsing a quoted scalar, found high surrogate without following low surrogate",
579 ),
580 Self::UnpairedLowSurrogate => {
581 f.write_str("while parsing a quoted scalar, found unpaired low surrogate")
582 }
583 Self::InvalidUnicodeEscape => f.write_str(
584 "while parsing a quoted scalar, found invalid Unicode character escape code",
585 ),
586 Self::InvalidFlowScalarIndent => {
587 f.write_str("invalid indentation in flow construct")
588 }
589 Self::PlainScalarStartsWithDashFlowIndicator => {
590 f.write_str("plain scalar cannot start with '-' followed by ,[]{}")
591 }
592 Self::TabInPlainScalar => {
593 f.write_str("while scanning a plain scalar, found a tab")
594 }
595 Self::UnexpectedEndOfPlainScalar => f.write_str("unexpected end of plain scalar"),
596 Self::MappingKeyNotAllowed => {
597 f.write_str("mapping keys are not allowed in this context")
598 }
599 Self::FlowMappingValueAdjacentCollection => {
600 f.write_str("':' may not precede any of `[{` in flow mapping")
601 }
602 Self::InvalidMappingValueWhitespace => {
603 f.write_str("':' must be followed by a valid YAML whitespace")
604 }
605 Self::InvalidColonPlacement => f.write_str("illegal placement of ':' indicator"),
606 Self::MappingValueNotAllowed => {
607 f.write_str("mapping values are not allowed in this context")
608 }
609 }
610 }
611}
612
613#[cfg(not(feature = "error_messages"))]
614impl fmt::Display for ErrorKind {
615 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
616 f.write_str("")
617 }
618}
619
620#[derive(Clone, PartialEq, Debug, Eq)]
622pub struct ScanError {
623 mark: Marker,
625 kind: ErrorKind,
627 source_stack: Vec<String>,
629}
630
631impl ScanError {
632 #[must_use]
638 #[cold]
639 pub fn new(loc: Marker, message: impl Into<String>) -> ScanError {
640 Self::from_kind(loc, ErrorKind::Custom(message.into()))
641 }
642
643 #[must_use]
644 #[cold]
645 pub(crate) fn from_kind(loc: Marker, kind: ErrorKind) -> ScanError {
646 ScanError {
647 mark: loc,
648 kind,
649 source_stack: Vec::new(),
650 }
651 }
652
653 #[must_use]
654 pub(crate) fn with_source_stack(mut self, source_stack: Vec<String>) -> Self {
655 self.source_stack = source_stack;
656 self
657 }
658
659 #[cold]
660 pub(crate) fn into_result<T>(self) -> Result<T, ScanError> {
661 Err(self)
662 }
663
664 #[must_use]
666 pub fn marker(&self) -> &Marker {
667 &self.mark
668 }
669
670 #[must_use]
672 pub fn kind(&self) -> &ErrorKind {
673 &self.kind
674 }
675
676 pub fn try_into_input_io_error(self) -> Result<InputIoError, Self> {
681 let Self {
682 mark,
683 kind,
684 source_stack,
685 } = self;
686
687 match kind {
688 ErrorKind::InputIo { error } => Ok(error),
689 kind => Err(Self {
690 mark,
691 kind,
692 source_stack,
693 }),
694 }
695 }
696
697 #[must_use]
699 pub fn source_stack(&self) -> &[String] {
700 &self.source_stack
701 }
702
703 #[must_use]
708 pub fn info(&self) -> String {
709 let mut info = self.kind.to_string();
710 if !info.is_empty() && self.source_stack().len() > 1 {
711 info.push_str("\nwhile parsing ");
712 info.push_str(&self.source_stack().join(" -> "));
713 }
714 info
715 }
716}
717
718impl fmt::Display for ScanError {
719 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
720 write!(
721 f,
722 "{} at char {} line {} column {}",
723 self.info(),
724 self.mark.index(),
725 self.mark.line(),
726 self.mark.col() + 1
727 )
728 }
729}
730
731impl core::error::Error for ScanError {
732 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
733 match &self.kind {
734 ErrorKind::InputIo { error } => Some(error),
735 _ => None,
736 }
737 }
738}
739
740#[cfg(test)]
741mod tests {
742 #[cfg(feature = "error_messages")]
743 use alloc::format;
744 #[cfg(feature = "error_messages")]
745 use alloc::string::String;
746 use alloc::string::ToString;
747
748 use super::{ErrorKind, InputIoError, ScanError};
749 use crate::scanner::Marker;
750
751 #[cfg(feature = "error_messages")]
752 #[test]
753 fn constructor_retains_kind_and_derives_info() {
754 let marker = Marker::new(3, 2, 1);
755 let error = ScanError::from_kind(marker, ErrorKind::ExpectedWhitespace);
756
757 assert_eq!(error.kind(), &ErrorKind::ExpectedWhitespace);
758 assert_eq!(error.kind().to_string(), "expected whitespace");
759 assert_eq!(error.info(), "expected whitespace");
760 }
761
762 #[cfg(feature = "error_messages")]
763 #[test]
764 fn parameterized_kind_constructs_info() {
765 let marker = Marker::new(3, 2, 1);
766 let error = ScanError::from_kind(
767 marker,
768 ErrorKind::MismatchedFlowCollectionEnd {
769 open: '[',
770 close: '}',
771 },
772 );
773
774 assert_eq!(error.info(), "mismatched bracket '[' closed by '}'");
775 assert_eq!(
776 format!("{error}"),
777 "mismatched bracket '[' closed by '}' at char 3 line 2 column 2"
778 );
779 }
780
781 #[cfg(feature = "error_messages")]
782 #[test]
783 fn input_error_kinds_construct_info() {
784 assert_eq!(
785 ErrorKind::InputIo {
786 error: InputIoError::from_message("connection reset")
787 }
788 .to_string(),
789 "input I/O error: connection reset"
790 );
791 assert_eq!(
792 ErrorKind::InputDecoding {
793 message: String::from("invalid utf-8")
794 }
795 .to_string(),
796 "input decoding error: invalid utf-8"
797 );
798 assert_eq!(
799 ErrorKind::InputByteLimitExceeded { limit: 4096 }.to_string(),
800 "input exceeds the configured limit of 4096 bytes"
801 );
802 }
803
804 #[test]
805 fn message_only_input_io_error_has_no_source() {
806 use core::error::Error as _;
807
808 let error = InputIoError::from_message("portable failure");
809
810 assert_eq!(error.message(), "portable failure");
811 assert!(error.source().is_none());
812 }
813
814 #[cfg(feature = "std")]
815 #[test]
816 fn std_input_io_error_is_retained_in_scan_error_source_chain() {
817 use core::error::Error as _;
818 use std::io;
819
820 let details = InputIoError::from(io::Error::new(io::ErrorKind::BrokenPipe, "pipe closed"));
821 assert_eq!(details.message(), "pipe closed");
822 assert_eq!(
823 details
824 .io_error()
825 .expect("std construction should retain io::Error")
826 .kind(),
827 io::ErrorKind::BrokenPipe
828 );
829
830 let error = ScanError::from_kind(
831 Marker::new(3, 2, 1),
832 ErrorKind::InputIo {
833 error: details.clone(),
834 },
835 );
836 let input_error = error
837 .source()
838 .and_then(|source| source.downcast_ref::<InputIoError>())
839 .expect("ScanError should expose InputIoError as its source");
840 let io_error = input_error
841 .source()
842 .and_then(|source| source.downcast_ref::<io::Error>())
843 .expect("InputIoError should expose the retained io::Error");
844
845 assert_eq!(io_error.kind(), io::ErrorKind::BrokenPipe);
846 assert_eq!(details, *input_error);
847 }
848
849 #[cfg(feature = "std")]
850 #[test]
851 fn unique_std_input_io_error_can_be_recovered() {
852 use std::io;
853
854 let details = InputIoError::from(io::Error::from_raw_os_error(12_345));
855 let error = details
856 .try_into_io_error()
857 .expect("a uniquely owned io::Error should be recoverable");
858
859 assert_eq!(error.raw_os_error(), Some(12_345));
860 }
861
862 #[cfg(feature = "std")]
863 #[test]
864 fn shared_std_input_io_error_can_be_recovered_after_other_clone_is_dropped() {
865 use std::io;
866
867 let details = InputIoError::from(io::Error::from_raw_os_error(12_345));
868 let other = details.clone();
869 let details = details
870 .try_into_io_error()
871 .expect_err("a shared io::Error cannot be moved out");
872
873 drop(other);
874
875 let error = details
876 .try_into_io_error()
877 .expect("the last owner should recover the io::Error");
878 assert_eq!(error.raw_os_error(), Some(12_345));
879 }
880
881 #[cfg(feature = "std")]
882 #[test]
883 fn scan_error_moves_input_io_error_out_without_cloning() {
884 use std::io;
885
886 let error = ScanError::from_kind(
887 Marker::new(3, 2, 1),
888 ErrorKind::InputIo {
889 error: InputIoError::from(io::Error::from_raw_os_error(12_345)),
890 },
891 );
892 let details = error
893 .try_into_input_io_error()
894 .expect("input I/O details should be extractable");
895 let error = details
896 .try_into_io_error()
897 .expect("extracting the scan error should retain unique ownership");
898
899 assert_eq!(error.raw_os_error(), Some(12_345));
900 }
901
902 #[test]
903 fn extracting_input_io_error_preserves_other_scan_errors() {
904 let error = ScanError::from_kind(Marker::new(3, 2, 1), ErrorKind::ExpectedWhitespace);
905 let error = error
906 .try_into_input_io_error()
907 .expect_err("a non-I/O scan error should be returned unchanged");
908
909 assert_eq!(error.marker(), &Marker::new(3, 2, 1));
910 assert_eq!(error.kind(), &ErrorKind::ExpectedWhitespace);
911 }
912
913 #[cfg(feature = "error_messages")]
914 #[test]
915 fn public_constructor_copies_custom_message() {
916 let marker = Marker::new(3, 2, 1);
917 let mut message = String::from("adapter failed");
918 let error = ScanError::new(marker, &message);
919 message.clear();
920
921 assert_eq!(
922 error.kind(),
923 &ErrorKind::Custom(String::from("adapter failed"))
924 );
925 assert_eq!(error.info(), "adapter failed");
926 }
927
928 #[cfg(not(feature = "error_messages"))]
929 #[test]
930 fn disabled_error_messages_are_empty() {
931 let marker = Marker::new(3, 2, 1);
932 let error = ScanError::from_kind(
933 marker,
934 ErrorKind::MismatchedFlowCollectionEnd {
935 open: '[',
936 close: '}',
937 },
938 );
939
940 assert_eq!(error.kind().to_string(), "");
941 assert_eq!(error.info(), "");
942 }
943}