Skip to main content

wpl/parser/
error.rs

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