Skip to main content

wpl/parser/
error.rs

1use crate::winnow::error::{ContextError, ParseError, StrContext};
2use orion_error::{OrionError, StructError, UvsReason};
3use serde::Serialize;
4use winnow::error::{ErrMode, Needed};
5use wp_error::util::split_string;
6use wp_model_core::model::MetaErr;
7// use wp_error::DataErrKind; // kept for potential future conversions
8
9fn translate_position(input: &[u8], index: usize) -> (usize, usize) {
10    if input.is_empty() {
11        return (0, index);
12    }
13
14    let safe_index = index.min(input.len() - 1);
15    let column_offset = index - safe_index;
16    let index = safe_index;
17
18    let nl = input[0..index]
19        .iter()
20        .rev()
21        .enumerate()
22        .find(|(_, b)| **b == b'\n')
23        .map(|(nl, _)| index - nl - 1);
24    let line_start = match nl {
25        Some(nl) => nl + 1,
26        None => 0,
27    };
28    let line = input[0..line_start].iter().filter(|b| **b == b'\n').count();
29
30    let column = std::str::from_utf8(&input[line_start..=index])
31        .map(|s| s.chars().count() - 1)
32        .unwrap_or_else(|_| index - line_start);
33    let column = column + column_offset;
34
35    (line, column)
36}
37
38pub fn error_detail(error: ParseError<&str, ContextError<StrContext>>) -> String {
39    let offset = error.offset();
40    let original = *error.input();
41    let span = if offset == original.len() {
42        offset..offset
43    } else {
44        offset..(offset + 1)
45    };
46
47    let mut msg = String::new();
48    let (line, column) = translate_position(original.as_bytes(), span.start);
49    let line_num = line + 1;
50    let col_num = column + 1;
51    let gutter = line_num.to_string().len();
52    let content = original.split('\n').nth(line).expect("valid line number");
53
54    msg.push_str(&format!(
55        "parse error at line {}, column {}\n",
56        line_num, col_num
57    ));
58    //   |
59    for _ in 0..=gutter {
60        msg.push(' ');
61    }
62    msg.push_str("|\n");
63
64    // 1 | 00:32:00.a999999
65    msg.push_str(&format!("{} | ", line_num));
66    msg.push_str(&format!("{}\n", content));
67    for _ in 0..=gutter {
68        msg.push(' ');
69    }
70    msg.push('|');
71    for _ in 0..=column {
72        msg.push(' ');
73    }
74    // The span will be empty at eof, so we need to make sure we always print at least
75    // one `^`
76    msg.push('^');
77    for _ in (span.start + 1)..(span.end.min(span.start + content.len())) {
78        msg.push('^');
79    }
80    msg.push('\n');
81    msg.push('\n');
82    msg.push_str(&error.inner().to_string());
83    msg
84}
85
86#[derive(Debug, Clone, PartialEq, Serialize, OrionError)]
87pub enum WplCodeReason {
88    #[orion_error(identity = "biz.plugin")]
89    Plugin,
90    #[orion_error(identity = "biz.syntax")]
91    Syntax,
92    #[orion_error(identity = "biz.wpl_empty")]
93    Empty,
94    #[orion_error(identity = "biz.unsupport")]
95    UnSupport,
96    #[orion_error(transparent)]
97    Uvs(UvsReason),
98}
99
100impl From<UvsReason> for WplCodeReason {
101    fn from(r: UvsReason) -> Self {
102        WplCodeReason::Uvs(r)
103    }
104}
105
106pub type WplCodeError = StructError<WplCodeReason>;
107
108pub type WplCodeResult<T> = Result<T, WplCodeError>;
109
110/// Trait for converting various error types into WplCodeError.
111/// Uses a local trait to avoid orphan rule restrictions.
112pub(crate) trait IntoWplCodeError {
113    fn into_wpl_err(self) -> WplCodeError;
114}
115
116impl IntoWplCodeError for WplCodeError {
117    fn into_wpl_err(self) -> WplCodeError {
118        self
119    }
120}
121
122impl IntoWplCodeError for MetaErr {
123    fn into_wpl_err(self) -> WplCodeError {
124        let msg = self.to_string();
125        StructError::builder(WplCodeReason::UnSupport)
126            .detail(msg)
127            .finish()
128    }
129}
130
131impl IntoWplCodeError for wp_parse_api::WparseError {
132    fn into_wpl_err(self) -> WplCodeError {
133        let msg = self.to_string();
134        StructError::builder(WplCodeReason::Plugin)
135            .detail(msg)
136            .finish()
137    }
138}
139
140impl IntoWplCodeError for crate::idcard::Error {
141    fn into_wpl_err(self) -> WplCodeError {
142        let msg = self.to_string();
143        StructError::builder(WplCodeReason::Plugin)
144            .detail(msg)
145            .finish()
146    }
147}
148
149impl IntoWplCodeError for std::string::FromUtf8Error {
150    fn into_wpl_err(self) -> WplCodeError {
151        let msg = self.to_string();
152        StructError::builder(WplCodeReason::Plugin)
153            .detail(msg)
154            .finish()
155    }
156}
157
158impl IntoWplCodeError for std::net::AddrParseError {
159    fn into_wpl_err(self) -> WplCodeError {
160        let msg = self.to_string();
161        StructError::builder(WplCodeReason::Plugin)
162            .detail(msg)
163            .finish()
164    }
165}
166
167pub trait WPLCodeErrorTrait {
168    fn from_syntax(e: ErrMode<ContextError>, code: &str, path: &str) -> Self;
169    fn from_parse_err(e: ParseError<&str, ContextError>, code: &str, path: &str) -> Self;
170}
171impl WPLCodeErrorTrait for StructError<WplCodeReason> {
172    fn from_syntax(e: ErrMode<ContextError>, code: &str, path: &str) -> Self {
173        match e {
174            ErrMode::Incomplete(Needed::Size(u)) => {
175                let msg = format!("parsing require {u}");
176                StructError::builder(WplCodeReason::Syntax)
177                    .detail(msg)
178                    .finish()
179            }
180            ErrMode::Incomplete(Needed::Unknown) => {
181                let msg = "parsing require more data".to_string();
182                StructError::builder(WplCodeReason::Syntax)
183                    .detail(msg)
184                    .finish()
185            }
186            ErrMode::Backtrack(e) => {
187                let where_in = split_string(code);
188                let msg = format!(
189                    ":wpl code parse fail!\n[path ]: '{}'\n[where]: '{}'\n[error]: {}",
190                    path, where_in, e
191                );
192                StructError::builder(WplCodeReason::Syntax)
193                    .detail(msg)
194                    .finish()
195            }
196            ErrMode::Cut(e) => {
197                let where_in = split_string(code);
198                let msg = format!(
199                    ":code parse fail\n[path ]: '{}'\n[where]: '{}'\n[error]: {}",
200                    path, where_in, e
201                );
202                StructError::builder(WplCodeReason::Syntax)
203                    .detail(msg)
204                    .finish()
205            }
206        }
207    }
208
209    fn from_parse_err(e: ParseError<&str, ContextError>, code: &str, path: &str) -> Self {
210        let where_in = split_string(code);
211        let msg = format!(
212            "parse error\n[path ]: '{}'\n[where]: '{}'\n[error]: {}",
213            path, where_in, e
214        );
215        StructError::builder(WplCodeReason::Syntax)
216            .detail(msg)
217            .finish()
218    }
219}
220
221/*
222impl From<DataErrKind> for StructError<WplCodeReason> {
223    fn from(value: DataErrKind) -> Self {
224        WplCodeReason::from_data().to_err()
225    }
226}
227*/