1use std::fmt;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, DxError>;
11
12pub const MAX_SNIPPET_LENGTH: usize = 50;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct SourceLocation {
18 pub line: usize,
20 pub column: usize,
22 pub offset: usize,
24}
25
26impl SourceLocation {
27 pub fn new(line: usize, column: usize, offset: usize) -> Self {
29 Self {
30 line,
31 column,
32 offset,
33 }
34 }
35
36 pub fn from_offset(input: &[u8], offset: usize) -> Self {
38 let mut line = 1;
39 let mut column = 1;
40
41 for (i, &byte) in input.iter().enumerate() {
42 if i >= offset {
43 break;
44 }
45 if byte == b'\n' {
46 line += 1;
47 column = 1;
48 } else {
49 column += 1;
50 }
51 }
52
53 Self {
54 line,
55 column,
56 offset,
57 }
58 }
59}
60
61impl fmt::Display for SourceLocation {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "line {}, column {}", self.line, self.column)
64 }
65}
66
67pub fn extract_snippet(input: &[u8], offset: usize) -> String {
72 if input.is_empty() {
73 return String::new();
74 }
75
76 let offset = offset.min(input.len().saturating_sub(1));
78
79 let half_len = MAX_SNIPPET_LENGTH / 2;
81 let start = offset.saturating_sub(half_len);
82 let end = (offset + half_len).min(input.len());
83
84 let slice = &input[start..end];
86
87 let snippet: String = String::from_utf8_lossy(slice)
89 .chars()
90 .filter(|c| !c.is_control() || *c == ' ' || *c == '\t')
91 .collect();
92
93 if snippet.trim().is_empty() && !slice.is_empty() {
95 return format!("<{} bytes>", slice.len());
96 }
97
98 snippet
99}
100
101pub const DX_MAGIC: [u8; 2] = [0x5A, 0x44]; pub const DX_VERSION: u8 = 1;
106
107pub const MAX_INPUT_SIZE: usize = 100 * 1024 * 1024;
109
110pub const MAX_RECURSION_DEPTH: usize = 1000;
112
113pub const MAX_TABLE_ROWS: usize = 10_000_000;
115
116#[derive(Error, Debug, Clone, PartialEq)]
118pub enum DxError {
119 #[error("Unexpected end of input at position {0}")]
122 UnexpectedEof(usize),
123
124 #[error("Parse error at {location}: {message}\n --> {snippet}")]
126 ParseError {
127 location: SourceLocation,
129 message: String,
131 snippet: String,
133 },
134
135 #[error("Invalid syntax at position {pos}: {msg}")]
137 InvalidSyntax {
138 pos: usize,
140 msg: String,
142 },
143
144 #[error("Schema error: {0}")]
147 SchemaError(String),
148
149 #[error("Type mismatch: expected {expected}, found {actual}")]
151 TypeMismatch {
152 expected: String,
154 actual: String,
156 },
157
158 #[error("Unknown alias: {0}")]
161 UnknownAlias(String),
162
163 #[error("Unknown anchor: {0}")]
165 UnknownAnchor(String),
166
167 #[error("Invalid type hint: {0}")]
170 InvalidTypeHint(String),
171
172 #[error("Invalid number format: {0}")]
174 InvalidNumber(String),
175
176 #[error("Invalid UTF-8 at byte offset {offset}")]
179 Utf8Error {
180 offset: usize,
182 },
183
184 #[error("Invalid Base62 character '{char}' at position {position}: {message}")]
186 Base62Error {
187 char: char,
189 position: usize,
191 message: String,
193 },
194
195 #[error("Integer overflow")]
197 IntegerOverflow,
198
199 #[error("Invalid magic bytes: expected [0x5A, 0x44], got [{0:#04X}, {1:#04X}]")]
202 InvalidMagic(u8, u8),
203
204 #[error("Unsupported version {found}, expected {expected}")]
206 UnsupportedVersion {
207 found: u8,
209 expected: u8,
211 },
212
213 #[error("Buffer too small: need {required} bytes, have {available}")]
215 BufferTooSmall {
216 required: usize,
218 available: usize,
220 },
221
222 #[error("Compression error: {0}")]
225 CompressionError(String),
226
227 #[error("Decompression error: {0}")]
229 DecompressionError(String),
230
231 #[error("IO error: {0}")]
234 Io(String),
235
236 #[error("Unsupported platform: {0}")]
238 UnsupportedPlatform(String),
239
240 #[error("Conversion error: {0}")]
243 ConversionError(String),
244
245 #[error("Ditto without previous value at position {0}")]
247 DittoNoPrevious(usize),
248
249 #[error("Prefix inheritance failed: {0}")]
251 PrefixError(String),
252
253 #[error("Input too large: {size} bytes exceeds maximum of {max} bytes")]
256 InputTooLarge {
257 size: usize,
259 max: usize,
261 },
262
263 #[error("Recursion limit exceeded: depth {depth} exceeds maximum of {max}")]
265 RecursionLimitExceeded {
266 depth: usize,
268 max: usize,
270 },
271
272 #[error("Table too large: {rows} rows exceeds maximum of {max} rows")]
274 TableTooLarge {
275 rows: usize,
277 max: usize,
279 },
280}
281
282impl DxError {
283 pub fn parse_error(input: &[u8], offset: usize, message: impl Into<String>) -> Self {
285 DxError::ParseError {
286 location: SourceLocation::from_offset(input, offset),
287 message: message.into(),
288 snippet: extract_snippet(input, offset),
289 }
290 }
291
292 pub fn parse_error_with_location(
294 location: SourceLocation,
295 message: impl Into<String>,
296 snippet: impl Into<String>,
297 ) -> Self {
298 DxError::ParseError {
299 location,
300 message: message.into(),
301 snippet: snippet.into(),
302 }
303 }
304
305 pub fn type_mismatch(expected: impl Into<String>, actual: impl Into<String>) -> Self {
307 DxError::TypeMismatch {
308 expected: expected.into(),
309 actual: actual.into(),
310 }
311 }
312
313 pub fn utf8_error(offset: usize) -> Self {
315 DxError::Utf8Error { offset }
316 }
317
318 pub fn base62_error(char: char, position: usize, message: impl Into<String>) -> Self {
320 DxError::Base62Error {
321 char,
322 position,
323 message: message.into(),
324 }
325 }
326
327 pub fn invalid_magic(byte0: u8, byte1: u8) -> Self {
329 DxError::InvalidMagic(byte0, byte1)
330 }
331
332 pub fn unsupported_version(found: u8) -> Self {
334 DxError::UnsupportedVersion {
335 found,
336 expected: DX_VERSION,
337 }
338 }
339
340 pub fn buffer_too_small(required: usize, available: usize) -> Self {
342 DxError::BufferTooSmall {
343 required,
344 available,
345 }
346 }
347
348 pub fn input_too_large(size: usize) -> Self {
350 DxError::InputTooLarge {
351 size,
352 max: MAX_INPUT_SIZE,
353 }
354 }
355
356 pub fn recursion_limit_exceeded(depth: usize) -> Self {
358 DxError::RecursionLimitExceeded {
359 depth,
360 max: MAX_RECURSION_DEPTH,
361 }
362 }
363
364 pub fn table_too_large(rows: usize) -> Self {
366 DxError::TableTooLarge {
367 rows,
368 max: MAX_TABLE_ROWS,
369 }
370 }
371
372 pub fn offset(&self) -> Option<usize> {
374 match self {
375 DxError::UnexpectedEof(offset) => Some(*offset),
376 DxError::ParseError { location, .. } => Some(location.offset),
377 DxError::InvalidSyntax { pos, .. } => Some(*pos),
378 DxError::Utf8Error { offset } => Some(*offset),
379 DxError::Base62Error { position, .. } => Some(*position),
380 DxError::DittoNoPrevious(pos) => Some(*pos),
381 _ => None,
382 }
383 }
384
385 pub fn location(&self) -> Option<&SourceLocation> {
387 match self {
388 DxError::ParseError { location, .. } => Some(location),
389 _ => None,
390 }
391 }
392
393 pub fn snippet(&self) -> Option<&str> {
395 match self {
396 DxError::ParseError { snippet, .. } => Some(snippet),
397 _ => None,
398 }
399 }
400
401 pub fn line(&self) -> Option<usize> {
403 self.location().map(|loc| loc.line)
404 }
405
406 pub fn column(&self) -> Option<usize> {
408 self.location().map(|loc| loc.column)
409 }
410
411 pub fn is_recoverable(&self) -> bool {
413 matches!(
414 self,
415 DxError::UnknownAlias(_) | DxError::UnknownAnchor(_) | DxError::TypeMismatch { .. }
416 )
417 }
418}
419
420impl From<std::io::Error> for DxError {
421 fn from(err: std::io::Error) -> Self {
422 DxError::Io(err.to_string())
423 }
424}
425
426impl From<std::str::Utf8Error> for DxError {
427 fn from(err: std::str::Utf8Error) -> Self {
428 DxError::Utf8Error {
429 offset: err.valid_up_to(),
430 }
431 }
432}
433
434impl From<std::string::FromUtf8Error> for DxError {
435 fn from(err: std::string::FromUtf8Error) -> Self {
436 DxError::Utf8Error {
437 offset: err.utf8_error().valid_up_to(),
438 }
439 }
440}
441
442impl From<crate::llm::parser::ParseError> for DxError {
443 fn from(err: crate::llm::parser::ParseError) -> Self {
448 use crate::llm::parser::ParseError;
449
450 match err {
451 ParseError::UnexpectedChar { ch, pos } => DxError::InvalidSyntax {
452 pos,
453 msg: format!("Unexpected character '{}'", ch),
454 },
455 ParseError::UnexpectedEof => DxError::UnexpectedEof(0),
456 ParseError::InvalidValue { value } => DxError::InvalidSyntax {
457 pos: 0,
458 msg: format!("Invalid value format: {}", value),
459 },
460 ParseError::SchemaMismatch { expected, got } => DxError::SchemaError(format!(
461 "Schema mismatch: expected {} columns, got {}",
462 expected, got
463 )),
464 ParseError::Utf8Error { offset } => DxError::Utf8Error { offset },
465 ParseError::InputTooLarge { size, max } => DxError::InputTooLarge { size, max },
466 ParseError::UnclosedBracket { pos } => DxError::InvalidSyntax {
467 pos,
468 msg: "Unclosed bracket".to_string(),
469 },
470 ParseError::UnclosedParen { pos } => DxError::InvalidSyntax {
471 pos,
472 msg: "Unclosed parenthesis".to_string(),
473 },
474 ParseError::MissingValue { pos } => DxError::InvalidSyntax {
475 pos,
476 msg: "Missing value after '='".to_string(),
477 },
478 ParseError::InvalidTable { msg } => {
479 DxError::SchemaError(format!("Invalid table format: {}", msg))
480 }
481 }
482 }
483}
484
485impl From<crate::llm::convert::ConvertError> for DxError {
486 fn from(err: crate::llm::convert::ConvertError) -> Self {
493 use crate::llm::convert::ConvertError;
494
495 match err {
496 ConvertError::LlmParse(parse_err) => parse_err.into(),
497 ConvertError::HumanParse(human_err) => DxError::ConversionError(human_err.to_string()),
498 ConvertError::MachineFormat { msg } => {
499 DxError::ConversionError(format!("Machine format error: {}", msg))
500 }
501 }
502 }
503}
504
505#[cfg(test)]
506mod tests {
507 use super::*;
508
509 #[test]
510 fn test_source_location_from_offset() {
511 let input = b"line1\nline2\nline3";
512
513 let loc = SourceLocation::from_offset(input, 0);
515 assert_eq!(loc.line, 1);
516 assert_eq!(loc.column, 1);
517
518 let loc = SourceLocation::from_offset(input, 3);
520 assert_eq!(loc.line, 1);
521 assert_eq!(loc.column, 4);
522
523 let loc = SourceLocation::from_offset(input, 6);
525 assert_eq!(loc.line, 2);
526 assert_eq!(loc.column, 1);
527
528 let loc = SourceLocation::from_offset(input, 14);
530 assert_eq!(loc.line, 3);
531 assert_eq!(loc.column, 3);
532 }
533
534 #[test]
535 fn test_parse_error_with_location() {
536 let input = b"key: value\nbad line here";
537 let err = DxError::parse_error(input, 15, "unexpected token");
538
539 if let DxError::ParseError {
540 location,
541 message,
542 snippet,
543 } = &err
544 {
545 assert_eq!(location.line, 2);
546 assert_eq!(location.column, 5);
547 assert_eq!(message, "unexpected token");
548 assert!(!snippet.is_empty());
549 } else {
550 panic!("Expected ParseError");
551 }
552 }
553
554 #[test]
555 fn test_parse_error_snippet() {
556 let input = b"key: value\nbad line here with more content";
557 let err = DxError::parse_error(input, 15, "unexpected token");
558
559 let snippet = err.snippet().unwrap();
560 assert!(!snippet.is_empty());
561 assert!(snippet.len() <= MAX_SNIPPET_LENGTH);
562 }
563
564 #[test]
565 fn test_extract_snippet() {
566 let input = b"hello world this is a test";
567 let snippet = extract_snippet(input, 6);
568 assert!(!snippet.is_empty());
569 assert!(snippet.contains("world"));
570 }
571
572 #[test]
573 fn test_extract_snippet_empty_input() {
574 let input = b"";
575 let snippet = extract_snippet(input, 0);
576 assert!(snippet.is_empty());
577 }
578
579 #[test]
580 fn test_type_mismatch_error() {
581 let err = DxError::type_mismatch("int", "string");
582 let msg = err.to_string();
583 assert!(msg.contains("expected int"));
584 assert!(msg.contains("found string"));
585 }
586
587 #[test]
588 fn test_invalid_magic() {
589 let err = DxError::invalid_magic(0x00, 0x01);
590 assert!(err.to_string().contains("0x00"));
591 assert!(err.to_string().contains("0x01"));
592 }
593
594 #[test]
595 fn test_buffer_too_small() {
596 let err = DxError::buffer_too_small(100, 50);
597 assert!(err.to_string().contains("100"));
598 assert!(err.to_string().contains("50"));
599 }
600
601 #[test]
602 fn test_error_offset() {
603 assert_eq!(DxError::UnexpectedEof(42).offset(), Some(42));
604 assert_eq!(DxError::utf8_error(10).offset(), Some(10));
605 assert_eq!(DxError::SchemaError("test".into()).offset(), None);
606 }
607
608 #[test]
609 fn test_error_line_column() {
610 let input = b"line1\nline2\nline3";
611 let err = DxError::parse_error(input, 8, "test");
612
613 assert_eq!(err.line(), Some(2));
614 assert_eq!(err.column(), Some(3));
615 }
616
617 #[test]
618 fn test_error_line_column_none() {
619 let err = DxError::SchemaError("test".into());
620 assert_eq!(err.line(), None);
621 assert_eq!(err.column(), None);
622 }
623
624 #[test]
625 fn test_std_error_implementation() {
626 fn assert_error<E: std::error::Error>(_: &E) {}
628
629 let err = DxError::parse_error(b"test input", 5, "test error");
630 assert_error(&err);
631
632 let err = DxError::type_mismatch("int", "string");
633 assert_error(&err);
634
635 let err = DxError::utf8_error(10);
636 assert_error(&err);
637
638 let err = DxError::Io("file not found".to_string());
639 assert_error(&err);
640 }
641
642 #[test]
643 fn test_error_display_is_actionable() {
644 let input = b"key: value\nbad line here";
646 let err = DxError::parse_error(input, 15, "unexpected token");
647 let msg = err.to_string();
648 assert!(msg.contains("line"));
649 assert!(msg.contains("column"));
650 assert!(msg.contains("unexpected token"));
651
652 let err = DxError::type_mismatch("integer", "string");
654 let msg = err.to_string();
655 assert!(msg.contains("integer"));
656 assert!(msg.contains("string"));
657
658 let err = DxError::buffer_too_small(100, 50);
660 let msg = err.to_string();
661 assert!(msg.contains("100"));
662 assert!(msg.contains("50"));
663
664 let err = DxError::input_too_large(200_000_000);
666 let msg = err.to_string();
667 assert!(msg.contains("200000000"));
668 assert!(msg.contains(&MAX_INPUT_SIZE.to_string()));
669 }
670
671 #[test]
672 fn test_error_from_io() {
673 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
674 let dx_err: DxError = io_err.into();
675 match dx_err {
676 DxError::Io(msg) => assert!(msg.contains("not found")),
677 _ => panic!("Expected Io error"),
678 }
679 }
680
681 #[test]
682 fn test_error_from_utf8() {
683 let invalid = vec![0xFF, 0xFE];
685 let result = std::str::from_utf8(&invalid);
686 if let Err(utf8_err) = result {
687 let dx_err: DxError = utf8_err.into();
688 match dx_err {
689 DxError::Utf8Error { offset } => assert_eq!(offset, 0),
690 _ => panic!("Expected Utf8Error"),
691 }
692 }
693 }
694
695 #[test]
696 fn test_error_from_parse_error_unexpected_char() {
697 use crate::llm::parser::ParseError;
698
699 let parse_err = ParseError::UnexpectedChar { ch: '@', pos: 42 };
700 let dx_err: DxError = parse_err.into();
701
702 match dx_err {
703 DxError::InvalidSyntax { pos, msg } => {
704 assert_eq!(pos, 42);
705 assert!(msg.contains('@'));
706 }
707 _ => panic!("Expected InvalidSyntax error"),
708 }
709 }
710
711 #[test]
712 fn test_error_from_parse_error_unexpected_eof() {
713 use crate::llm::parser::ParseError;
714
715 let parse_err = ParseError::UnexpectedEof;
716 let dx_err: DxError = parse_err.into();
717
718 match dx_err {
719 DxError::UnexpectedEof(pos) => assert_eq!(pos, 0),
720 _ => panic!("Expected UnexpectedEof error"),
721 }
722 }
723
724 #[test]
725 fn test_error_from_parse_error_utf8() {
726 use crate::llm::parser::ParseError;
727
728 let parse_err = ParseError::Utf8Error { offset: 123 };
729 let dx_err: DxError = parse_err.into();
730
731 match dx_err {
732 DxError::Utf8Error { offset } => assert_eq!(offset, 123),
733 _ => panic!("Expected Utf8Error"),
734 }
735 }
736
737 #[test]
738 fn test_error_from_parse_error_input_too_large() {
739 use crate::llm::parser::ParseError;
740
741 let parse_err = ParseError::InputTooLarge {
742 size: 200_000_000,
743 max: 100_000_000,
744 };
745 let dx_err: DxError = parse_err.into();
746
747 match dx_err {
748 DxError::InputTooLarge { size, max } => {
749 assert_eq!(size, 200_000_000);
750 assert_eq!(max, 100_000_000);
751 }
752 _ => panic!("Expected InputTooLarge error"),
753 }
754 }
755
756 #[test]
757 fn test_error_from_parse_error_schema_mismatch() {
758 use crate::llm::parser::ParseError;
759
760 let parse_err = ParseError::SchemaMismatch {
761 expected: 5,
762 got: 3,
763 };
764 let dx_err: DxError = parse_err.into();
765
766 match dx_err {
767 DxError::SchemaError(msg) => {
768 assert!(msg.contains("5"));
769 assert!(msg.contains("3"));
770 }
771 _ => panic!("Expected SchemaError"),
772 }
773 }
774
775 #[test]
776 fn test_error_from_parse_error_unclosed_bracket() {
777 use crate::llm::parser::ParseError;
778
779 let parse_err = ParseError::UnclosedBracket { pos: 10 };
780 let dx_err: DxError = parse_err.into();
781
782 match dx_err {
783 DxError::InvalidSyntax { pos, msg } => {
784 assert_eq!(pos, 10);
785 assert!(msg.contains("bracket"));
786 }
787 _ => panic!("Expected InvalidSyntax error"),
788 }
789 }
790
791 #[test]
792 fn test_error_from_parse_error_unclosed_paren() {
793 use crate::llm::parser::ParseError;
794
795 let parse_err = ParseError::UnclosedParen { pos: 20 };
796 let dx_err: DxError = parse_err.into();
797
798 match dx_err {
799 DxError::InvalidSyntax { pos, msg } => {
800 assert_eq!(pos, 20);
801 assert!(msg.contains("parenthesis"));
802 }
803 _ => panic!("Expected InvalidSyntax error"),
804 }
805 }
806
807 #[test]
808 fn test_error_from_parse_error_missing_value() {
809 use crate::llm::parser::ParseError;
810
811 let parse_err = ParseError::MissingValue { pos: 15 };
812 let dx_err: DxError = parse_err.into();
813
814 match dx_err {
815 DxError::InvalidSyntax { pos, msg } => {
816 assert_eq!(pos, 15);
817 assert!(msg.contains("Missing value"));
818 }
819 _ => panic!("Expected InvalidSyntax error"),
820 }
821 }
822
823 #[test]
824 fn test_error_from_parse_error_invalid_table() {
825 use crate::llm::parser::ParseError;
826
827 let parse_err = ParseError::InvalidTable {
828 msg: "Empty schema".to_string(),
829 };
830 let dx_err: DxError = parse_err.into();
831
832 match dx_err {
833 DxError::SchemaError(msg) => {
834 assert!(msg.contains("Empty schema"));
835 }
836 _ => panic!("Expected SchemaError"),
837 }
838 }
839
840 #[test]
841 fn test_error_from_parse_error_invalid_value() {
842 use crate::llm::parser::ParseError;
843
844 let parse_err = ParseError::InvalidValue {
845 value: "bad_value".to_string(),
846 };
847 let dx_err: DxError = parse_err.into();
848
849 match dx_err {
850 DxError::InvalidSyntax { pos, msg } => {
851 assert_eq!(pos, 0);
852 assert!(msg.contains("bad_value"));
853 }
854 _ => panic!("Expected InvalidSyntax error"),
855 }
856 }
857
858 #[test]
859 fn test_error_from_parse_error_preserves_position() {
860 use crate::llm::parser::ParseError;
861
862 let test_cases: Vec<(ParseError, Option<usize>)> = vec![
864 (ParseError::UnexpectedChar { ch: 'x', pos: 100 }, Some(100)),
865 (ParseError::UnclosedBracket { pos: 200 }, Some(200)),
866 (ParseError::UnclosedParen { pos: 300 }, Some(300)),
867 (ParseError::MissingValue { pos: 400 }, Some(400)),
868 (ParseError::Utf8Error { offset: 500 }, Some(500)),
869 ];
870
871 for (parse_err, expected_pos) in test_cases {
872 let dx_err: DxError = parse_err.into();
873 assert_eq!(
874 dx_err.offset(),
875 expected_pos,
876 "Position not preserved for error: {:?}",
877 dx_err
878 );
879 }
880 }
881
882 #[test]
885 fn test_error_from_convert_error_llm_parse() {
886 use crate::llm::convert::ConvertError;
887 use crate::llm::parser::ParseError;
888
889 let parse_err = ParseError::UnexpectedChar { ch: '#', pos: 50 };
891 let convert_err = ConvertError::LlmParse(parse_err);
892 let dx_err: DxError = convert_err.into();
893
894 match dx_err {
895 DxError::InvalidSyntax { pos, msg } => {
896 assert_eq!(pos, 50);
897 assert!(msg.contains('#'));
898 }
899 _ => panic!("Expected InvalidSyntax error, got {:?}", dx_err),
900 }
901 }
902
903 #[test]
904 fn test_error_from_convert_error_llm_parse_eof() {
905 use crate::llm::convert::ConvertError;
906 use crate::llm::parser::ParseError;
907
908 let parse_err = ParseError::UnexpectedEof;
909 let convert_err = ConvertError::LlmParse(parse_err);
910 let dx_err: DxError = convert_err.into();
911
912 match dx_err {
913 DxError::UnexpectedEof(pos) => assert_eq!(pos, 0),
914 _ => panic!("Expected UnexpectedEof error, got {:?}", dx_err),
915 }
916 }
917
918 #[test]
919 fn test_error_from_convert_error_human_parse() {
920 use crate::llm::convert::ConvertError;
921 use crate::llm::human_parser::HumanParseError;
922
923 let human_err = HumanParseError::InvalidSectionHeader {
924 msg: "Missing closing bracket".to_string(),
925 };
926 let convert_err = ConvertError::HumanParse(human_err);
927 let dx_err: DxError = convert_err.into();
928
929 match dx_err {
930 DxError::ConversionError(msg) => {
931 assert!(msg.contains("Invalid section header"));
932 assert!(msg.contains("Missing closing bracket"));
933 }
934 _ => panic!("Expected ConversionError, got {:?}", dx_err),
935 }
936 }
937
938 #[test]
939 fn test_error_from_convert_error_human_parse_invalid_key_value() {
940 use crate::llm::convert::ConvertError;
941 use crate::llm::human_parser::HumanParseError;
942
943 let human_err = HumanParseError::InvalidKeyValue {
944 msg: "No equals sign found".to_string(),
945 };
946 let convert_err = ConvertError::HumanParse(human_err);
947 let dx_err: DxError = convert_err.into();
948
949 match dx_err {
950 DxError::ConversionError(msg) => {
951 assert!(msg.contains("Invalid key-value pair"));
952 assert!(msg.contains("No equals sign found"));
953 }
954 _ => panic!("Expected ConversionError, got {:?}", dx_err),
955 }
956 }
957
958 #[test]
959 fn test_error_from_convert_error_human_parse_invalid_table() {
960 use crate::llm::convert::ConvertError;
961 use crate::llm::human_parser::HumanParseError;
962
963 let human_err = HumanParseError::InvalidTable {
964 line: 42,
965 msg: "Mismatched columns".to_string(),
966 };
967 let convert_err = ConvertError::HumanParse(human_err);
968 let dx_err: DxError = convert_err.into();
969
970 match dx_err {
971 DxError::ConversionError(msg) => {
972 assert!(msg.contains("Invalid table format"));
973 assert!(msg.contains("42"));
974 assert!(msg.contains("Mismatched columns"));
975 }
976 _ => panic!("Expected ConversionError, got {:?}", dx_err),
977 }
978 }
979
980 #[test]
981 fn test_error_from_convert_error_machine_format() {
982 use crate::llm::convert::ConvertError;
983
984 let convert_err = ConvertError::MachineFormat {
985 msg: "Invalid magic number".to_string(),
986 };
987 let dx_err: DxError = convert_err.into();
988
989 match dx_err {
990 DxError::ConversionError(msg) => {
991 assert!(msg.contains("Machine format error"));
992 assert!(msg.contains("Invalid magic number"));
993 }
994 _ => panic!("Expected ConversionError, got {:?}", dx_err),
995 }
996 }
997
998 #[test]
999 fn test_error_from_convert_error_machine_format_eof() {
1000 use crate::llm::convert::ConvertError;
1001
1002 let convert_err = ConvertError::MachineFormat {
1003 msg: "Unexpected end of data".to_string(),
1004 };
1005 let dx_err: DxError = convert_err.into();
1006
1007 match dx_err {
1008 DxError::ConversionError(msg) => {
1009 assert!(msg.contains("Machine format error"));
1010 assert!(msg.contains("Unexpected end of data"));
1011 }
1012 _ => panic!("Expected ConversionError, got {:?}", dx_err),
1013 }
1014 }
1015
1016 #[test]
1017 fn test_error_from_convert_error_all_variants_convertible() {
1018 use crate::llm::convert::ConvertError;
1019 use crate::llm::human_parser::HumanParseError;
1020 use crate::llm::parser::ParseError;
1021
1022 let test_cases: Vec<ConvertError> = vec![
1024 ConvertError::LlmParse(ParseError::UnexpectedEof),
1025 ConvertError::LlmParse(ParseError::UnexpectedChar { ch: 'x', pos: 0 }),
1026 ConvertError::LlmParse(ParseError::InvalidValue {
1027 value: "test".to_string(),
1028 }),
1029 ConvertError::LlmParse(ParseError::SchemaMismatch {
1030 expected: 3,
1031 got: 2,
1032 }),
1033 ConvertError::LlmParse(ParseError::Utf8Error { offset: 10 }),
1034 ConvertError::LlmParse(ParseError::InputTooLarge {
1035 size: 200,
1036 max: 100,
1037 }),
1038 ConvertError::LlmParse(ParseError::UnclosedBracket { pos: 5 }),
1039 ConvertError::LlmParse(ParseError::UnclosedParen { pos: 6 }),
1040 ConvertError::LlmParse(ParseError::MissingValue { pos: 7 }),
1041 ConvertError::LlmParse(ParseError::InvalidTable {
1042 msg: "test".to_string(),
1043 }),
1044 ConvertError::HumanParse(HumanParseError::InvalidSectionHeader {
1045 msg: "test".to_string(),
1046 }),
1047 ConvertError::HumanParse(HumanParseError::InvalidKeyValue {
1048 msg: "test".to_string(),
1049 }),
1050 ConvertError::HumanParse(HumanParseError::InvalidTable {
1051 line: 1,
1052 msg: "test".to_string(),
1053 }),
1054 ConvertError::HumanParse(HumanParseError::UnexpectedContent {
1055 msg: "test".to_string(),
1056 }),
1057 ConvertError::HumanParse(HumanParseError::InputTooLarge {
1058 size: 200,
1059 max: 100,
1060 }),
1061 ConvertError::HumanParse(HumanParseError::TableTooLarge {
1062 rows: 200,
1063 max: 100,
1064 }),
1065 ConvertError::MachineFormat {
1066 msg: "test".to_string(),
1067 },
1068 ];
1069
1070 for convert_err in test_cases {
1071 let dx_err: DxError = convert_err.into();
1072 let msg = dx_err.to_string();
1074 assert!(!msg.is_empty(), "Error message should not be empty");
1075 }
1076 }
1077}