Skip to main content

qusql_parse/
issue.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5// http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use core::fmt::Display;
14
15use alloc::{borrow::Cow, vec::Vec};
16
17use crate::{Span, Spanned};
18
19/// Level of an issues
20#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
21pub enum Level {
22    Warning,
23    Error,
24}
25
26#[derive(Clone, PartialEq, Eq, Debug)]
27pub struct Fragment<'a> {
28    /// The primary message of the fragmenst
29    pub message: Cow<'static, str>,
30    /// The span to attach the primary message to
31    pub span: Span,
32    /// The sql segment of the issue
33    pub sql_segment: &'a str,
34}
35
36/// An issue encountered during parsing, or later stages
37#[derive(Clone, PartialEq, Eq, Debug)]
38pub struct Issue<'a> {
39    /// The level of the issue
40    pub level: Level,
41    /// The primary message of the issue
42    pub message: Cow<'static, str>,
43    /// The span to attach the primary message to
44    pub span: Span,
45    /// The sql segment of the issue
46    pub sql_segment: &'a str,
47    /// List of secondary messages , spans and sql segments
48    pub fragments: Vec<Fragment<'a>>,
49    /// Optional help/suggestion line shown below the diagnostic
50    pub help: Option<Cow<'static, str>>,
51}
52pub struct IssueHandle<'a, 'b> {
53    src: &'a str,
54    issue: &'b mut Issue<'a>,
55}
56
57impl<'a, 'b> IssueHandle<'a, 'b> {
58    pub fn frag(
59        &mut self,
60        message: impl Into<Cow<'static, str>>,
61        span: &impl Spanned,
62    ) -> &mut Self {
63        let span: core::ops::Range<usize> = span.span();
64        self.issue.fragments.push(Fragment {
65            message: message.into(),
66            span: span.clone(),
67            sql_segment: &self.src[span.start..span.end],
68        });
69        self
70    }
71
72    pub fn help(&mut self, message: impl Into<Cow<'static, str>>) -> &mut Self {
73        self.issue.help = Some(message.into());
74        self
75    }
76}
77
78#[derive(Debug)]
79pub struct Issues<'a> {
80    pub src: &'a str,
81    pub issues: Vec<Issue<'a>>,
82}
83
84impl<'a> Issues<'a> {
85    pub fn err<'b>(
86        &'b mut self,
87        message: impl Into<Cow<'static, str>>,
88        span: &impl Spanned,
89    ) -> IssueHandle<'a, 'b> {
90        let span: core::ops::Range<usize> = span.span();
91        self.issues.push(Issue {
92            level: Level::Error,
93            message: message.into(),
94            span: span.clone(),
95            sql_segment: self.segment(span),
96            fragments: Default::default(),
97            help: None,
98        });
99        IssueHandle {
100            src: self.src,
101            issue: self.issues.last_mut().unwrap(),
102        }
103    }
104
105    pub fn warn<'b>(
106        &'b mut self,
107        message: impl Into<Cow<'static, str>>,
108        span: &impl Spanned,
109    ) -> IssueHandle<'a, 'b> {
110        let span = span.span();
111        self.issues.push(Issue {
112            level: Level::Warning,
113            message: message.into(),
114            span: span.clone(),
115            sql_segment: self.segment(span),
116            fragments: Default::default(),
117            help: None,
118        });
119        IssueHandle {
120            src: self.src,
121            issue: self.issues.last_mut().unwrap(),
122        }
123    }
124
125    pub fn segment(&self, span: Span) -> &'a str {
126        &self.src[span.start..span.end]
127    }
128
129    pub fn new(src: &'a str) -> Self {
130        Self {
131            src,
132            issues: Default::default(),
133        }
134    }
135
136    pub fn get(&self) -> &'_ [Issue<'a>] {
137        &self.issues
138    }
139
140    pub fn into_vec(self) -> Vec<Issue<'a>> {
141        self.issues
142    }
143
144    pub fn is_ok(&self) -> bool {
145        self.issues.is_empty()
146    }
147}
148
149impl<'a> Display for Issues<'a> {
150    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
151        if self.issues.is_empty() {
152            return writeln!(f, "No issues");
153        }
154        writeln!(f, "Issues:")?;
155        for issue in self.get() {
156            match issue.level {
157                Level::Warning => write!(f, "  Warning ")?,
158                Level::Error => write!(f, "  Error ")?,
159            }
160            writeln!(f, "{}", issue.message)?;
161            let line = self.src[..issue.span.start]
162                .chars()
163                .filter(|v| *v == '\n')
164                .count()
165                + 1;
166            writeln!(f, "    At line {}, code {}", line, issue.sql_segment)?;
167            for fragment in &issue.fragments {
168                writeln!(f, "  {}", fragment.message)?;
169                let line = self.src[..fragment.span.start]
170                    .chars()
171                    .filter(|v| *v == '\n')
172                    .count()
173                    + 1;
174                writeln!(f, "    At line {}, code {}", line, fragment.sql_segment)?;
175            }
176        }
177        Ok(())
178    }
179}