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