reqlang/
diagnostics.rs

1use codespan_reporting::diagnostic::{Diagnostic, Label};
2use serde::{Deserialize, Serialize};
3
4use crate::{
5    errors::{FetchError, 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, FetchError);
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            ReqlangError::FetchError(e) => e.as_diagnostic(span),
110        }
111    }
112}
113
114#[cfg(test)]
115mod tests {
116
117    use crate::{
118        ast::Ast,
119        diagnostics::{
120            Diagnosis, DiagnosisPosition, DiagnosisRange, DiagnosisSeverity, get_diagnostics,
121        },
122        parser::parse,
123    };
124
125    #[test]
126    fn it_works() {
127        let source = String::from("");
128
129        let ast = Ast::from(&source);
130
131        let errs = parse(&ast).unwrap_err();
132
133        assert_eq!(
134            vec![Diagnosis {
135                range: DiagnosisRange {
136                    start: DiagnosisPosition {
137                        line: 0,
138                        character: 0,
139                    },
140                    end: DiagnosisPosition {
141                        line: 0,
142                        character: 0,
143                    },
144                },
145                severity: Some(DiagnosisSeverity::ERROR),
146                message: String::from("ParseError: Request file requires a request be defined")
147            }],
148            get_diagnostics(&errs, &source)
149        );
150    }
151}