reqlang/
diagnostics.rs

1use codespan_reporting::diagnostic::{Diagnostic, Label};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    errors::{ParseError, ReqlangError, ResolverError},
6    span::{Span, Spanned},
7    str_idxpos::index_to_position,
8};
9
10/// Get a list of diagnostics from a list of errors
11pub fn get_diagnostics(errs: &[Spanned<ReqlangError>], source: &str) -> Vec<Diagnosis> {
12    errs.iter()
13        .map(|(err, span)| Diagnosis {
14            range: get_range(source, span),
15            severity: Some(DiagnosisSeverity::ERROR),
16            message: err.to_string(),
17        })
18        .collect()
19}
20
21fn get_range(source: &str, span: &Span) -> DiagnosisRange {
22    DiagnosisRange {
23        start: get_position(source, span.start),
24        end: get_position(source, span.end),
25    }
26}
27
28fn get_position(source: &str, idx: usize) -> DiagnosisPosition {
29    let (line, character) = index_to_position(source, idx);
30
31    DiagnosisPosition {
32        line: line as u32,
33        character: character as u32,
34    }
35}
36
37#[derive(Debug, Eq, PartialEq, Clone, Default, Deserialize, Serialize)]
38#[serde(rename_all = "camelCase")]
39pub struct Diagnosis {
40    pub range: DiagnosisRange,
41
42    #[serde(skip_serializing_if = "Option::is_none")]
43    pub severity: Option<DiagnosisSeverity>,
44
45    pub message: String,
46}
47
48#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Deserialize, Serialize)]
49#[serde(transparent)]
50pub struct DiagnosisSeverity(i32);
51impl DiagnosisSeverity {
52    pub const ERROR: DiagnosisSeverity = DiagnosisSeverity(1);
53    pub const WARNING: DiagnosisSeverity = DiagnosisSeverity(2);
54    pub const INFORMATION: DiagnosisSeverity = DiagnosisSeverity(3);
55    pub const HINT: DiagnosisSeverity = DiagnosisSeverity(4);
56}
57
58#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default, Deserialize, Serialize)]
59pub struct DiagnosisPosition {
60    pub line: u32,
61    pub character: u32,
62}
63
64impl DiagnosisPosition {
65    pub fn new(line: u32, character: u32) -> DiagnosisPosition {
66        DiagnosisPosition { line, character }
67    }
68}
69
70#[derive(Debug, Eq, PartialEq, Copy, Clone, Default, Deserialize, Serialize)]
71pub struct DiagnosisRange {
72    /// The range's start position (inclusive)
73    pub start: DiagnosisPosition,
74    /// The range's end position (exclusive)
75    pub end: DiagnosisPosition,
76}
77
78impl DiagnosisRange {
79    pub fn new(start: DiagnosisPosition, end: DiagnosisPosition) -> DiagnosisRange {
80        DiagnosisRange { start, end }
81    }
82}
83
84#[allow(dead_code)]
85trait AsDiagnostic {
86    fn as_diagnostic(&self, span: &Span) -> Diagnostic<()>;
87}
88
89macro_rules! impl_as_dianostic {
90    ($($error:tt),+) => {$(
91        impl AsDiagnostic for $error {
92            fn as_diagnostic(&self, span: &Span) -> Diagnostic<()> {
93                Diagnostic::error()
94                    .with_code(stringify!($error))
95                    .with_message(self.to_string())
96                    .with_labels(vec![Label::primary((), span.clone())])
97            }
98        }
99    )+};
100}
101
102impl_as_dianostic!(ParseError, ResolverError);
103
104impl AsDiagnostic for ReqlangError {
105    fn as_diagnostic(&self, span: &Span) -> Diagnostic<()> {
106        match self {
107            ReqlangError::ParseError(e) => e.as_diagnostic(span),
108            ReqlangError::ResolverError(e) => e.as_diagnostic(span),
109        }
110    }
111}
112
113#[cfg(test)]
114mod tests {
115
116    use crate::{
117        ast::Ast,
118        diagnostics::{
119            Diagnosis, DiagnosisPosition, DiagnosisRange, DiagnosisSeverity, get_diagnostics,
120        },
121        parser::parse,
122    };
123
124    #[test]
125    fn it_works() {
126        let source = String::from("");
127
128        let ast = Ast::from(&source);
129
130        let errs = parse(&ast).unwrap_err();
131
132        assert_eq!(
133            vec![Diagnosis {
134                range: DiagnosisRange {
135                    start: DiagnosisPosition {
136                        line: 0,
137                        character: 0,
138                    },
139                    end: DiagnosisPosition {
140                        line: 0,
141                        character: 0,
142                    },
143                },
144                severity: Some(DiagnosisSeverity::ERROR),
145                message: String::from("ParseError: Request file requires a request be defined")
146            }],
147            get_diagnostics(&errs, &source)
148        );
149    }
150}