openstep_plist/
error.rs

1use serde::{de, ser};
2use std::fmt::Display;
3
4pub type Result<T> = std::result::Result<T, Error>;
5
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct LineColumn {
8    pub line: usize,
9    pub column: usize,
10}
11
12impl LineColumn {
13    pub fn from_pos(s: &str, pos: usize) -> Self {
14        let mut line = 1usize;
15        let mut col = 1usize;
16        let bytes = s.as_bytes();
17        let mut i = 0usize;
18        while i < pos && i < bytes.len() {
19            if bytes[i] == b'\n' {
20                line += 1;
21                col = 1;
22            } else {
23                col += 1;
24            }
25            i += 1;
26        }
27        LineColumn { line, column: col }
28    }
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
32pub enum Error {
33    #[error("Unexpected character '{ch}' at line {}, column {}", .lc.line, .lc.column)]
34    UnexpectedChar { ch: char, lc: LineColumn },
35    #[error("Unterminated string at line {}, column {}", .lc.line, .lc.column)]
36    UnclosedString { lc: LineColumn },
37    #[error("Unterminated data block at line {}, column {}", .lc.line, .lc.column)]
38    UnclosedData { lc: LineColumn },
39    // Internal-use variants (no position); convert with Error::at()
40    #[error("Data block did not contain valid paired hex digits")]
41    BadDataInternal,
42    #[error("Unknown escape code")]
43    UnknownEscapeInternal,
44    #[error("Invalid unicode escape sequence: '{seq}'")]
45    InvalidUnicodeEscapeInternal { seq: String },
46    #[error("Expected string, found '{token_name}'")]
47    NotAStringInternal { token_name: &'static str },
48    #[error("Data block did not contain valid paired hex digits at line {}, column {}", .lc.line, .lc.column)]
49    BadData { lc: LineColumn },
50    #[error("Unknown escape code at line {}, column {}", .lc.line, .lc.column)]
51    UnknownEscape { lc: LineColumn },
52    #[error("Invalid unicode escape sequence: '{seq}' at line {}, column {}", .lc.line, .lc.column)]
53    InvalidUnicodeEscape { seq: String, lc: LineColumn },
54    #[error("Expected string, found '{token_name}' at line {}, column {}", .lc.line, .lc.column)]
55    NotAString {
56        token_name: &'static str,
57        lc: LineColumn,
58    },
59    #[error("Missing '=' at line {}, column {}", .lc.line, .lc.column)]
60    ExpectedEquals { lc: LineColumn },
61    #[error("Missing ',' at line {}, column {}", .lc.line, .lc.column)]
62    ExpectedComma { lc: LineColumn },
63    #[error("Missing ';' at line {}, column {}", .lc.line, .lc.column)]
64    ExpectedSemicolon { lc: LineColumn },
65    #[error("Missing '{{' at line {}, column {}", .lc.line, .lc.column)]
66    ExpectedOpenBrace { lc: LineColumn },
67    #[error("Missing '}}' at line {}, column {}", .lc.line, .lc.column)]
68    ExpectedCloseBrace { lc: LineColumn },
69    #[error("Missing '(' at line {}, column {}", .lc.line, .lc.column)]
70    ExpectedOpenParen { lc: LineColumn },
71    #[error("Missing ')' at line {}, column {}", .lc.line, .lc.column)]
72    ExpectedCloseParen { lc: LineColumn },
73    #[error("Expected character '{ch}' at line {}, column {}", .lc.line, .lc.column)]
74    ExpectedChar { ch: char, lc: LineColumn },
75    #[error("Expected numeric value at line {}, column {}", .lc.line, .lc.column)]
76    ExpectedNumber { lc: LineColumn },
77    #[error("Expected string value at line {}, column {}", .lc.line, .lc.column)]
78    ExpectedString { lc: LineColumn },
79    #[error("Expected '{expected}', found '{found}'")]
80    UnexpectedDataType {
81        expected: &'static str,
82        found: &'static str,
83    },
84    #[error("Unexpected token '{name}' at line {}, column {}", .lc.line, .lc.column)]
85    UnexpectedToken { name: &'static str, lc: LineColumn },
86    #[error("parsing failed: '{0}'")]
87    Parse(String),
88    #[error("serializing failed: '{0}'")]
89    Serialize(String),
90}
91
92impl ser::Error for Error {
93    fn custom<T: Display>(msg: T) -> Self {
94        Error::Serialize(msg.to_string())
95    }
96}
97
98impl de::Error for Error {
99    fn custom<T: Display>(msg: T) -> Self {
100        Error::Parse(msg.to_string())
101    }
102}
103
104impl Error {
105    pub fn at(self, s: &str, pos: usize) -> Self {
106        let lc = LineColumn::from_pos(s, pos);
107        match self {
108            Error::BadDataInternal => Error::BadData { lc },
109            Error::UnknownEscapeInternal => Error::UnknownEscape { lc },
110            Error::InvalidUnicodeEscapeInternal { seq } => Error::InvalidUnicodeEscape { seq, lc },
111            Error::NotAStringInternal { token_name } => Error::NotAString { token_name, lc },
112            other => other,
113        }
114    }
115}