1use crate::types::EpochError;
6use std::borrow::Cow;
7use thiserror::Error;
8use winnow::error::{AddContext, ParserError, StrContext};
9use winnow::stream::Stream;
10
11#[derive(Debug, Clone, PartialEq)]
12pub struct ParseDiagnostic {
13 pub line: usize,
14 pub column: usize,
15 pub message: String,
16 pub contexts: Vec<&'static str>,
17}
18
19#[derive(Debug, Clone, PartialEq, Default)]
20pub struct RawParsePosition {
21 pub offset: usize,
22 pub message: Cow<'static, str>,
23 pub contexts: ContextStack,
24}
25
26impl RawParsePosition {
27 pub fn into_parse_error(self, input: &str) -> KvnParseError {
28 let diag = ParseDiagnostic::new(input, self.offset, &*self.message);
33 KvnParseError {
34 line: diag.line,
35 column: diag.column,
36 message: self.message.into_owned(),
37 contexts: self.contexts.to_vec(),
38 offset: self.offset,
39 }
40 }
41}
42
43impl ParseDiagnostic {
44 pub fn new(input: &str, offset: usize, message: impl Into<String>) -> Self {
46 let offset = offset.min(input.len());
47 let prefix = &input[..offset];
48 let line = prefix.as_bytes().iter().filter(|&&b| b == b'\n').count() + 1;
49 let line_start = prefix.rfind('\n').map(|i| i + 1).unwrap_or(0);
50 let column = prefix[line_start..].chars().count();
51
52 Self {
53 line,
54 column: column + 1,
55 message: message.into(),
56 contexts: Vec::new(),
57 }
58 }
59
60 pub fn with_contexts(mut self, contexts: Vec<&'static str>) -> Self {
62 self.contexts = contexts;
63 self
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Error)]
69pub struct KvnParseError {
70 pub line: usize,
71 pub column: usize,
72 pub message: String,
73 pub contexts: Vec<&'static str>,
74 pub offset: usize, }
76
77impl std::fmt::Display for KvnParseError {
78 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79 write!(
80 f,
81 "KVN parsing error at line {}, column {}: {}",
82 self.line, self.column, self.message
83 )?;
84 if !self.contexts.is_empty() {
85 write!(f, "\nContext: {}", self.contexts.join(" > "))?;
86 }
87 Ok(())
88 }
89}
90
91#[derive(Debug, Clone, PartialEq, Error)]
93#[error("Invalid value '{value}' for field '{field}'; expected one of: {expected}")]
94pub struct EnumParseError {
95 pub field: &'static str,
96 pub value: String,
97 pub expected: &'static str,
98}
99
100#[derive(Debug, Error)]
102#[non_exhaustive]
103pub enum FormatError {
104 #[error(transparent)]
106 Kvn(#[from] Box<KvnParseError>),
107
108 #[error("XML error: {0}")]
110 Xml(
111 #[source]
112 #[from]
113 quick_xml::Error,
114 ),
115
116 #[error("XML deserialization error: {0}")]
118 XmlDe(
119 #[source]
120 #[from]
121 quick_xml::DeError,
122 ),
123
124 #[error("XML serialization error: {0}")]
126 XmlSer(
127 #[source]
128 #[from]
129 quick_xml::se::SeError,
130 ),
131
132 #[error("Parse float error: {0}")]
134 ParseFloat(
135 #[source]
136 #[from]
137 std::num::ParseFloatError,
138 ),
139
140 #[error("Parse int error: {0}")]
142 ParseInt(
143 #[source]
144 #[from]
145 std::num::ParseIntError,
146 ),
147
148 #[error(transparent)]
150 Enum(#[from] EnumParseError),
151
152 #[error("Invalid format: {0}")]
154 InvalidFormat(String),
155
156 #[error("{context}: {source}")]
158 XmlWithContext {
159 context: String,
160 #[source]
161 source: quick_xml::DeError,
162 },
163}
164
165#[derive(Debug, Clone, PartialEq, Error)]
167#[non_exhaustive]
168pub enum ValidationError {
169 #[error("Missing required field: {field} in block {block}")]
171 MissingRequiredField {
172 block: Cow<'static, str>,
173 field: Cow<'static, str>,
174 line: Option<usize>,
175 },
176
177 #[error("Conflicting fields: {fields:?}")]
179 Conflict {
180 fields: Vec<Cow<'static, str>>,
181 line: Option<usize>,
182 },
183
184 #[error("Invalid value for '{field}': '{value}' (expected {expected})")]
186 InvalidValue {
187 field: Cow<'static, str>,
188 value: String,
189 expected: Cow<'static, str>,
190 line: Option<usize>,
191 },
192
193 #[error("Value for '{name}' is out of range: {value} (expected {expected})")]
195 OutOfRange {
196 name: Cow<'static, str>,
197 value: String,
198 expected: Cow<'static, str>,
199 line: Option<usize>,
200 },
201
202 #[error("Validation error: {message}")]
204 Generic {
205 message: Cow<'static, str>,
206 line: Option<usize>,
207 },
208}
209
210impl ValidationError {
211 pub fn missing_required(
213 block: impl Into<Cow<'static, str>>,
214 field: impl Into<Cow<'static, str>>,
215 ) -> Self {
216 Self::MissingRequiredField {
217 block: block.into(),
218 field: field.into(),
219 line: None,
220 }
221 }
222
223 pub fn invalid_value(
225 field: impl Into<Cow<'static, str>>,
226 value: impl Into<String>,
227 expected: impl Into<Cow<'static, str>>,
228 ) -> Self {
229 Self::InvalidValue {
230 field: field.into(),
231 value: value.into(),
232 expected: expected.into(),
233 line: None,
234 }
235 }
236
237 pub fn generic(message: impl Into<Cow<'static, str>>) -> Self {
239 Self::Generic {
240 message: message.into(),
241 line: None,
242 }
243 }
244
245 pub fn conflict<I>(fields: I) -> Self
247 where
248 I: IntoIterator<Item = Cow<'static, str>>,
249 {
250 Self::Conflict {
251 fields: fields.into_iter().collect(),
252 line: None,
253 }
254 }
255}
256
257pub trait WithLocation: Sized {
259 fn with_line(self, line: usize) -> Self;
261}
262
263impl WithLocation for ValidationError {
264 fn with_line(mut self, line: usize) -> Self {
265 match &mut self {
266 ValidationError::OutOfRange {
267 line: ref mut l, ..
268 }
269 | ValidationError::InvalidValue {
270 line: ref mut l, ..
271 }
272 | ValidationError::MissingRequiredField {
273 line: ref mut l, ..
274 }
275 | ValidationError::Conflict {
276 line: ref mut l, ..
277 }
278 | ValidationError::Generic {
279 line: ref mut l, ..
280 } => {
281 if l.is_none() {
282 *l = Some(line);
283 }
284 }
285 }
286 self
287 }
288}
289
290#[derive(Error, Debug)]
315#[non_exhaustive]
316pub enum CcsdsNdmError {
317 #[error("I/O error: {0}")]
319 Io(
320 #[source]
321 #[from]
322 std::io::Error,
323 ),
324
325 #[error(transparent)]
327 Format(#[from] Box<FormatError>),
328
329 #[error(transparent)]
331 Validation(#[from] Box<ValidationError>),
332
333 #[error("Epoch error: {0}")]
335 Epoch(
336 #[source]
337 #[from]
338 EpochError,
339 ),
340
341 #[error("Unsupported message type: {0}")]
343 UnsupportedMessage(String),
344
345 #[error("Unexpected end of input: {context}")]
347 UnexpectedEof { context: String },
348}
349
350#[derive(Debug, Clone, PartialEq, Default)]
355pub struct ContextStack {
356 contexts: [&'static str; 3],
357 len: usize,
358}
359
360impl ContextStack {
361 pub fn new() -> Self {
362 Self::default()
363 }
364
365 pub fn push(&mut self, context: &'static str) {
366 if self.len < self.contexts.len() {
367 self.contexts[self.len] = context;
368 self.len += 1;
369 }
370 }
371
372 pub fn last(&self) -> Option<&&'static str> {
373 if self.len > 0 {
374 Some(&self.contexts[self.len - 1])
375 } else {
376 None
377 }
378 }
379
380 pub fn to_vec(&self) -> Vec<&'static str> {
381 self.contexts[..self.len].to_vec()
382 }
383}
384
385#[derive(Debug, Clone, PartialEq)]
387pub struct InternalParserError {
388 pub message: Cow<'static, str>,
389 pub contexts: ContextStack,
390 pub kind: Box<ParserErrorKind>,
391}
392
393#[derive(Debug, Clone, PartialEq, Default)]
394#[non_exhaustive]
395pub enum ParserErrorKind {
396 #[default]
397 Kvn,
398 MissingRequiredField {
399 block: &'static str,
400 field: &'static str,
401 },
402 Validation(ValidationError),
403 Epoch(EpochError),
404 Enum(EnumParseError),
405 ParseInt(std::num::ParseIntError),
406 ParseFloat(std::num::ParseFloatError),
407}
408
409impl ParserError<&str> for InternalParserError {
410 type Inner = ();
411 fn from_input(_input: &&str) -> Self {
412 Self {
413 message: Cow::Borrowed(""),
414 contexts: ContextStack::new(),
415 kind: Box::new(ParserErrorKind::default()),
416 }
417 }
418
419 fn into_inner(self) -> std::result::Result<Self::Inner, Self> {
420 Ok(())
421 }
422}
423
424impl winnow::error::FromExternalError<&str, EpochError> for InternalParserError {
425 fn from_external_error(_input: &&str, e: EpochError) -> Self {
426 Self {
427 message: Cow::Borrowed(""),
428 contexts: ContextStack::new(),
429 kind: Box::new(ParserErrorKind::Epoch(e)),
430 }
431 }
432}
433
434impl winnow::error::FromExternalError<&str, std::num::ParseFloatError> for InternalParserError {
435 fn from_external_error(_input: &&str, e: std::num::ParseFloatError) -> Self {
436 Self {
437 message: Cow::Borrowed(""),
438 contexts: ContextStack::new(),
439 kind: Box::new(ParserErrorKind::ParseFloat(e)),
440 }
441 }
442}
443
444impl winnow::error::FromExternalError<&str, std::num::ParseIntError> for InternalParserError {
445 fn from_external_error(_input: &&str, e: std::num::ParseIntError) -> Self {
446 Self {
447 message: Cow::Borrowed(""),
448 contexts: ContextStack::new(),
449 kind: Box::new(ParserErrorKind::ParseInt(e)),
450 }
451 }
452}
453
454impl winnow::error::FromExternalError<&str, EnumParseError> for InternalParserError {
455 fn from_external_error(_input: &&str, e: EnumParseError) -> Self {
456 Self {
457 message: Cow::Borrowed(""),
458 contexts: ContextStack::new(),
459 kind: Box::new(ParserErrorKind::Enum(e)),
460 }
461 }
462}
463
464impl winnow::error::FromExternalError<&str, ValidationError> for InternalParserError {
465 fn from_external_error(_input: &&str, e: ValidationError) -> Self {
466 Self {
467 message: Cow::Borrowed(""),
468 contexts: ContextStack::new(),
469 kind: Box::new(ParserErrorKind::Validation(e)),
470 }
471 }
472}
473
474impl winnow::error::FromExternalError<&str, CcsdsNdmError> for InternalParserError {
475 fn from_external_error(input: &&str, e: CcsdsNdmError) -> Self {
476 match e {
477 CcsdsNdmError::Validation(ve) => Self::from_external_error(input, *ve),
478 CcsdsNdmError::Epoch(ee) => Self::from_external_error(input, ee),
479 CcsdsNdmError::Format(fe) => match *fe {
480 FormatError::Enum(ee) => Self::from_external_error(input, ee),
481 FormatError::ParseFloat(pfe) => Self::from_external_error(input, pfe),
482 FormatError::ParseInt(pie) => Self::from_external_error(input, pie),
483 _ => Self {
484 message: Cow::Owned(fe.to_string()),
485 contexts: ContextStack::new(),
486 kind: Box::new(ParserErrorKind::default()),
487 },
488 },
489 _ => Self {
490 message: Cow::Owned(e.to_string()),
491 contexts: ContextStack::new(),
492 kind: Box::new(ParserErrorKind::default()),
493 },
494 }
495 }
496}
497
498impl AddContext<&str, StrContext> for InternalParserError {
499 fn add_context(
500 mut self,
501 _input: &&str,
502 _token: &<&str as Stream>::Checkpoint,
503 context: StrContext,
504 ) -> Self {
505 match context {
506 StrContext::Label(l) => {
507 if self.contexts.last() != Some(&l) {
508 self.contexts.push(l);
509 }
510 }
511 StrContext::Expected(e) => {
512 self.message = Cow::Owned(format!("Expected {}", e));
513 }
514 _ => {} }
516 self
517 }
518}
519
520impl From<ValidationError> for CcsdsNdmError {
521 fn from(e: ValidationError) -> Self {
522 CcsdsNdmError::Validation(Box::new(e))
523 }
524}
525
526impl From<FormatError> for CcsdsNdmError {
527 fn from(e: FormatError) -> Self {
528 CcsdsNdmError::Format(Box::new(e))
529 }
530}
531
532impl From<EnumParseError> for CcsdsNdmError {
533 fn from(e: EnumParseError) -> Self {
534 CcsdsNdmError::Format(Box::new(FormatError::Enum(e)))
535 }
536}
537
538impl From<std::num::ParseFloatError> for CcsdsNdmError {
539 fn from(e: std::num::ParseFloatError) -> Self {
540 CcsdsNdmError::Format(Box::new(FormatError::ParseFloat(e)))
541 }
542}
543
544impl From<std::num::ParseIntError> for CcsdsNdmError {
545 fn from(e: std::num::ParseIntError) -> Self {
546 CcsdsNdmError::Format(Box::new(FormatError::ParseInt(e)))
547 }
548}
549
550impl From<quick_xml::DeError> for CcsdsNdmError {
551 fn from(e: quick_xml::DeError) -> Self {
552 CcsdsNdmError::Format(Box::new(FormatError::XmlDe(e)))
553 }
554}
555
556impl From<quick_xml::se::SeError> for CcsdsNdmError {
557 fn from(e: quick_xml::se::SeError) -> Self {
558 CcsdsNdmError::Format(Box::new(FormatError::XmlSer(e)))
559 }
560}
561
562impl From<quick_xml::Error> for CcsdsNdmError {
563 fn from(e: quick_xml::Error) -> Self {
564 CcsdsNdmError::Format(Box::new(FormatError::Xml(e)))
565 }
566}
567
568impl winnow::error::FromExternalError<&str, EpochError> for CcsdsNdmError {
569 fn from_external_error(_input: &&str, e: EpochError) -> Self {
570 CcsdsNdmError::Epoch(e)
571 }
572}
573
574impl winnow::error::FromExternalError<&str, std::num::ParseFloatError> for CcsdsNdmError {
575 fn from_external_error(_input: &&str, e: std::num::ParseFloatError) -> Self {
576 CcsdsNdmError::Format(Box::new(FormatError::ParseFloat(e)))
577 }
578}
579
580impl winnow::error::FromExternalError<&str, std::num::ParseIntError> for CcsdsNdmError {
581 fn from_external_error(_input: &&str, e: std::num::ParseIntError) -> Self {
582 CcsdsNdmError::Format(Box::new(FormatError::ParseInt(e)))
583 }
584}
585
586impl AddContext<&str, StrContext> for CcsdsNdmError {
587 fn add_context(
588 mut self,
589 _input: &&str,
590 _token: &<&str as Stream>::Checkpoint,
591 context: StrContext,
592 ) -> Self {
593 if let CcsdsNdmError::Format(ref mut format_err) = self {
594 if let FormatError::Kvn(ref mut inner) = **format_err {
595 match context {
596 StrContext::Label(l) => {
597 if inner.contexts.last() != Some(&l) {
598 inner.contexts.push(l);
599 }
600 }
601 StrContext::Expected(e) => inner.message = format!("Expected {}", e),
602 _ => {} }
604 }
605 }
606 self
607 }
608}
609
610impl CcsdsNdmError {
611 pub fn as_kvn_parse_error(&self) -> Option<&KvnParseError> {
613 match self {
614 CcsdsNdmError::Format(e) => match **e {
615 FormatError::Kvn(ref err) => Some(err),
616 _ => None,
617 },
618 _ => None,
619 }
620 }
621
622 pub fn as_validation_error(&self) -> Option<&ValidationError> {
624 match self {
625 CcsdsNdmError::Validation(e) => Some(e),
626 _ => None,
627 }
628 }
629
630 pub fn as_format_error(&self) -> Option<&FormatError> {
632 match self {
633 CcsdsNdmError::Format(e) => Some(e),
634 _ => None,
635 }
636 }
637
638 pub fn as_epoch_error(&self) -> Option<&EpochError> {
640 match self {
641 CcsdsNdmError::Epoch(e) => Some(e),
642 _ => None,
643 }
644 }
645
646 pub fn as_io_error(&self) -> Option<&std::io::Error> {
648 match self {
649 CcsdsNdmError::Io(e) => Some(e),
650 _ => None,
651 }
652 }
653
654 pub fn as_xml_error(&self) -> Option<&quick_xml::Error> {
656 match self {
657 CcsdsNdmError::Format(e) => match **e {
658 FormatError::Xml(ref xe) => Some(xe),
659 _ => None,
660 },
661 _ => None,
662 }
663 }
664
665 pub fn is_format_error(&self) -> bool {
667 matches!(self, CcsdsNdmError::Format(_))
668 }
669
670 pub fn is_kvn_error(&self) -> bool {
672 self.as_kvn_parse_error().is_some()
673 }
674
675 pub fn is_validation_error(&self) -> bool {
677 self.as_validation_error().is_some()
678 }
679
680 pub fn is_io_error(&self) -> bool {
682 matches!(self, CcsdsNdmError::Io(_))
683 }
684
685 pub fn is_epoch_error(&self) -> bool {
687 matches!(self, CcsdsNdmError::Epoch(_))
688 }
689
690 pub fn as_enum_error(&self) -> Option<&EnumParseError> {
692 match self {
693 CcsdsNdmError::Format(e) => match **e {
694 FormatError::Enum(ref ee) => Some(ee),
695 _ => None,
696 },
697 _ => None,
698 }
699 }
700
701 pub fn as_parse_int_error(&self) -> Option<&std::num::ParseIntError> {
703 match self {
704 CcsdsNdmError::Format(e) => match **e {
705 FormatError::ParseInt(ref pie) => Some(pie),
706 _ => None,
707 },
708 _ => None,
709 }
710 }
711
712 pub fn as_parse_float_error(&self) -> Option<&std::num::ParseFloatError> {
714 match self {
715 CcsdsNdmError::Format(e) => match **e {
716 FormatError::ParseFloat(ref pfe) => Some(pfe),
717 _ => None,
718 },
719 _ => None,
720 }
721 }
722
723 pub fn with_location(mut self, input: &str, offset: usize) -> Self {
725 match self {
726 CcsdsNdmError::Format(ref mut format_err) => {
727 if let FormatError::Kvn(ref mut inner) = **format_err {
728 if inner.line > 0 {
730 return self;
731 }
732
733 let target_offset = if offset > 0 {
734 offset
735 } else if inner.offset > 0 {
736 inner.offset
737 } else {
738 0
739 };
740
741 let diag = ParseDiagnostic::new(input, target_offset, "");
742 inner.line = diag.line;
743 inner.column = diag.column;
744 inner.offset = target_offset;
745 }
746 }
747 CcsdsNdmError::Validation(ref mut val_err) => match **val_err {
748 ValidationError::InvalidValue { ref mut line, .. }
749 | ValidationError::MissingRequiredField { ref mut line, .. }
750 | ValidationError::Conflict { ref mut line, .. }
751 | ValidationError::Generic { ref mut line, .. }
752 | ValidationError::OutOfRange { ref mut line, .. } => {
753 if line.is_none() {
754 let diag = ParseDiagnostic::new(input, offset, "");
755 *line = Some(diag.line);
756 }
757 }
758 },
759 _ => {} }
761 self
762 }
763}
764
765pub type Result<T> = std::result::Result<T, CcsdsNdmError>;
766
767#[cfg(test)]
768mod tests {
769 use super::*;
770
771 #[test]
772 fn test_kvn_parse_error_display() {
773 let err = KvnParseError {
774 line: 10,
775 column: 5,
776 message: "Test error".into(),
777 contexts: vec!["Header", "Version"],
778 offset: 100,
779 };
780 let s = format!("{}", err);
781 assert!(s.contains("line 10, column 5"));
782 assert!(s.contains("Test error"));
783 assert!(s.contains("Header > Version"));
784 }
785
786 #[test]
787 fn test_enum_parse_error_display() {
788 let err = EnumParseError {
789 field: "FIELD",
790 value: "VAL".into(),
791 expected: "A or B",
792 };
793 let s = format!("{}", err); assert!(s.contains("Invalid value 'VAL' for field 'FIELD'"));
796 assert!(s.contains("expected one of: A or B"));
797 }
798
799 #[test]
800 fn test_validation_error_display() {
801 let err = ValidationError::MissingRequiredField {
802 block: "BLOCK".into(),
803 field: "FIELD".into(),
804 line: Some(42),
805 };
806 let s = format!("{}", err);
807 assert!(s.contains("Missing required field: FIELD in block BLOCK"));
808 }
809
810 #[test]
811 fn test_validation_error_with_location() {
812 let mut err = ValidationError::OutOfRange {
813 name: "N".into(),
814 value: "V".into(),
815 expected: "E".into(),
816 line: None,
817 };
818 err = err.with_line(123);
820 if let ValidationError::OutOfRange { line, .. } = err {
821 assert_eq!(line, Some(123));
822 } else {
823 panic!("Wrong variant");
824 }
825
826 err = err.with_line(456);
828 if let ValidationError::OutOfRange { line, .. } = err {
829 assert_eq!(line, Some(123));
830 } else {
831 panic!("Wrong variant");
832 }
833 }
834
835 #[test]
836 fn test_ccsds_ndm_error_helpers() {
837 let io_err = std::io::Error::new(std::io::ErrorKind::Other, "io");
838 let err: CcsdsNdmError = io_err.into();
839 assert!(err.as_io_error().is_some());
840 assert!(err.is_io_error());
841 assert_eq!(format!("{}", err), "I/O error: io");
842
843 let val_err = ValidationError::Generic {
844 message: "g".into(),
845 line: None,
846 };
847 let err: CcsdsNdmError = val_err.into();
848 assert!(err.as_validation_error().is_some());
849 assert!(err.is_validation_error());
850
851 let fmt_err = FormatError::InvalidFormat("f".into());
852 let err: CcsdsNdmError = fmt_err.into();
853 assert!(err.as_format_error().is_some());
854 assert!(err.is_format_error());
855 assert!(!err.is_kvn_error());
856
857 let enum_err = EnumParseError {
858 field: "F",
859 value: "V".into(),
860 expected: "E",
861 };
862 let err: CcsdsNdmError = enum_err.into();
863 assert!(err.as_enum_error().is_some());
864
865 let pfe_err = "abc".parse::<f64>().unwrap_err();
866 let err: CcsdsNdmError = pfe_err.into();
867 assert!(err.as_parse_float_error().is_some());
868
869 let pie_err = "abc".parse::<i32>().unwrap_err();
870 let err: CcsdsNdmError = pie_err.into();
871 assert!(err.as_parse_int_error().is_some());
872
873 let epoch_err = EpochError::InvalidFormat("2023".into());
874 let err: CcsdsNdmError = CcsdsNdmError::Epoch(epoch_err);
875 assert!(err.as_epoch_error().is_some());
876 assert!(err.is_epoch_error());
877
878 let eof_err = CcsdsNdmError::UnexpectedEof {
879 context: "ctx".into(),
880 };
881 assert_eq!(format!("{}", eof_err), "Unexpected end of input: ctx");
882
883 let unsupported = CcsdsNdmError::UnsupportedMessage("type".into());
884 assert_eq!(format!("{}", unsupported), "Unsupported message type: type");
885 }
886
887 #[test]
888 fn test_format_error_variants() {
889 let xml_err = quick_xml::Error::Io(std::sync::Arc::new(std::io::Error::new(
890 std::io::ErrorKind::Other,
891 "io",
892 )));
893 let err: CcsdsNdmError = FormatError::Xml(xml_err).into();
894 assert!(err.as_xml_error().is_some());
895
896 let fmt_err = FormatError::XmlWithContext {
897 context: "ctx".into(),
898 source: quick_xml::DeError::Custom("msg".into()),
899 };
900 assert!(format!("{}", fmt_err).contains("ctx"));
901 }
902
903 #[test]
904 fn test_with_location() {
905 let input = "LINE1\nLINE2\nLINE3";
906 let mut err = CcsdsNdmError::Validation(Box::new(ValidationError::Generic {
907 message: "msg".into(),
908 line: None,
909 }));
910 err = err.with_location(input, 6);
912 if let CcsdsNdmError::Validation(ve) = err {
913 if let ValidationError::Generic { line, .. } = *ve {
914 assert_eq!(line, Some(2));
915 }
916 }
917
918 let kvn_err = KvnParseError {
919 line: 0,
920 column: 0,
921 message: "msg".into(),
922 contexts: vec![],
923 offset: 0,
924 };
925 let mut err = CcsdsNdmError::Format(Box::new(FormatError::Kvn(Box::new(kvn_err))));
926 err = err.with_location(input, 12); if let Some(ke) = err.as_kvn_parse_error() {
928 assert_eq!(ke.line, 3);
929 }
930 }
931
932 #[test]
933 fn test_context_stack() {
934 let mut stack = ContextStack::new();
935 assert_eq!(stack.last(), None);
936 stack.push("A");
937 stack.push("B");
938 stack.push("C");
939 assert_eq!(stack.last(), Some(&"C"));
940 stack.push("D"); assert_eq!(stack.last(), Some(&"C"));
942 assert_eq!(stack.to_vec(), vec!["A", "B", "C"]);
943 }
944
945 #[test]
946 fn test_internal_parser_error() {
947 use winnow::error::ParserError;
948 let input = "abc";
949 let mut err = InternalParserError::from_input(&input);
950 assert_eq!(err.message, "");
951 err.contexts.push("ctx");
952 assert_eq!(err.contexts.to_vec(), vec!["ctx"]);
953
954 use winnow::error::FromExternalError;
956 let enum_err = EnumParseError {
957 field: "F",
958 value: "V".into(),
959 expected: "E",
960 };
961 let err = InternalParserError::from_external_error(&input, enum_err);
962 assert!(matches!(*err.kind, ParserErrorKind::Enum(_)));
963
964 let ccsds_err = CcsdsNdmError::Validation(Box::new(ValidationError::Generic {
965 message: "m".into(),
966 line: None,
967 }));
968 let err = InternalParserError::from_external_error(&input, ccsds_err);
969 assert!(matches!(*err.kind, ParserErrorKind::Validation(_)));
970 }
971}