Skip to main content

omnigraph_compiler/
error.rs

1use thiserror::Error;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4pub struct SourceSpan {
5    pub start: usize,
6    pub end: usize,
7}
8
9impl SourceSpan {
10    pub fn new(start: usize, end: usize) -> Self {
11        Self { start, end }
12    }
13}
14
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct ParseDiagnostic {
17    pub message: String,
18    pub span: Option<SourceSpan>,
19}
20
21impl ParseDiagnostic {
22    pub fn new(message: String, span: Option<SourceSpan>) -> Self {
23        Self { message, span }
24    }
25}
26
27impl std::fmt::Display for ParseDiagnostic {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        write!(f, "{}", self.message)
30    }
31}
32
33impl std::error::Error for ParseDiagnostic {}
34
35pub fn render_span(span: SourceSpan) -> SourceSpan {
36    SourceSpan {
37        start: span.start,
38        end: span.end.max(span.start.saturating_add(1)),
39    }
40}
41
42pub fn decode_string_literal(raw: &str) -> Result<String> {
43    let inner = raw
44        .strip_prefix('"')
45        .and_then(|inner| inner.strip_suffix('"'))
46        .unwrap_or(raw);
47
48    let mut decoded = String::with_capacity(inner.len());
49    let mut chars = inner.chars();
50    while let Some(ch) = chars.next() {
51        if ch != '\\' {
52            decoded.push(ch);
53            continue;
54        }
55
56        let escaped = chars
57            .next()
58            .ok_or_else(|| NanoError::Parse("unterminated escape sequence".to_string()))?;
59        match escaped {
60            '"' => decoded.push('"'),
61            '\\' => decoded.push('\\'),
62            'n' => decoded.push('\n'),
63            'r' => decoded.push('\r'),
64            't' => decoded.push('\t'),
65            other => {
66                return Err(NanoError::Parse(format!(
67                    "unsupported escape sequence: \\{}",
68                    other
69                )));
70            }
71        }
72    }
73
74    Ok(decoded)
75}
76
77#[derive(Debug, Error)]
78pub enum NanoError {
79    #[error("parse error: {0}")]
80    Parse(String),
81
82    #[error("catalog error: {0}")]
83    Catalog(String),
84
85    #[error("type error: {0}")]
86    Type(String),
87
88    #[error("storage error: {0}")]
89    Storage(String),
90
91    #[error(
92        "@unique constraint violation on {type_name}.{property}: duplicate value '{value}' at rows {first_row} and {second_row}"
93    )]
94    UniqueConstraint {
95        type_name: String,
96        property: String,
97        value: String,
98        first_row: usize,
99        second_row: usize,
100    },
101
102    #[error("plan error: {0}")]
103    Plan(String),
104
105    #[error("execution error: {0}")]
106    Execution(String),
107
108    #[error(transparent)]
109    Arrow(#[from] arrow_schema::ArrowError),
110
111    #[error("io error: {0}")]
112    Io(#[from] std::io::Error),
113
114    #[error("lance error: {0}")]
115    Lance(String),
116
117    #[error("manifest error: {0}")]
118    Manifest(String),
119}
120
121pub type Result<T> = std::result::Result<T, NanoError>;
122
123#[cfg(test)]
124mod tests {
125    use super::{SourceSpan, decode_string_literal, render_span};
126
127    #[test]
128    fn source_span_preserves_zero_width() {
129        let span = SourceSpan::new(7, 7);
130        assert_eq!(span.start, 7);
131        assert_eq!(span.end, 7);
132    }
133
134    #[test]
135    fn render_span_widens_zero_width_for_diagnostics() {
136        let rendered = render_span(SourceSpan::new(7, 7));
137        assert_eq!(rendered.start, 7);
138        assert_eq!(rendered.end, 8);
139    }
140
141    #[test]
142    fn decode_string_literal_supports_common_escapes() {
143        let decoded = decode_string_literal("\"a\\n\\r\\t\\\\\\\"b\"").unwrap();
144        assert_eq!(decoded, "a\n\r\t\\\"b");
145    }
146}