aldrin_parser/
diag.rs

1//! Diagnostic information and formatting.
2//!
3//! This module primarily provides the [`Diagnostic`] trait, which is implemented by
4//! [`Error`](crate::Error) and [`Warning`](crate::Warning).
5
6use crate::{Parser, Schema, Span};
7use annotate_snippets::renderer::DecorStyle;
8use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
9use std::borrow::Cow;
10use std::slice;
11
12/// Diagnostic information about an error or a warning.
13pub trait Diagnostic {
14    /// Kind of the diagnostic; either an error or a warning.
15    fn kind(&self) -> DiagnosticKind;
16
17    /// Name of the schema this diagnostic originated from.
18    ///
19    /// The schema name can be used to look up the [`Schema`] with [`Parser::get_schema`].
20    fn schema_name(&self) -> &str;
21
22    /// Renders the diagnostic for printing.
23    fn render(&self, renderer: &Renderer, parser: &Parser) -> String;
24}
25
26/// Error or warning.
27#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub enum DiagnosticKind {
29    /// Indicates an issue which prevents further processing.
30    Error,
31
32    /// Indicates an issue which doesn't prevent further processing.
33    Warning,
34}
35
36/// Renderer used to format [`Diagnostic`s](Diagnostic).
37#[derive(Debug, Clone)]
38pub struct Renderer {
39    inner: annotate_snippets::Renderer,
40}
41
42impl Renderer {
43    pub fn new(color: bool, unicode: bool, term_width: usize) -> Self {
44        let mut inner = if color {
45            annotate_snippets::Renderer::styled()
46        } else {
47            annotate_snippets::Renderer::plain()
48        };
49
50        if unicode {
51            inner = inner.decor_style(DecorStyle::Unicode);
52        };
53
54        inner = inner.term_width(term_width);
55
56        Self { inner }
57    }
58
59    pub fn render(&self, diagnostic: &(impl Diagnostic + ?Sized), parser: &Parser) -> String {
60        diagnostic.render(self, parser)
61    }
62
63    pub(crate) fn error<'a>(&'a self, title: impl Into<Cow<'a, str>>) -> Report<'a> {
64        Report::new(
65            Group::with_title(Level::ERROR.primary_title(title)),
66            &self.inner,
67        )
68    }
69
70    pub(crate) fn warning<'a>(&'a self, title: impl Into<Cow<'a, str>>) -> Report<'a> {
71        Report::new(
72            Group::with_title(Level::WARNING.primary_title(title)),
73            &self.inner,
74        )
75    }
76}
77
78pub(crate) struct Report<'a> {
79    group: Group<'a>,
80    renderer: &'a annotate_snippets::Renderer,
81}
82
83impl<'a> Report<'a> {
84    fn new(group: Group<'a>, renderer: &'a annotate_snippets::Renderer) -> Self {
85        Self { group, renderer }
86    }
87
88    pub(crate) fn render(&self) -> String {
89        self.renderer.render(slice::from_ref(&self.group))
90    }
91
92    pub(crate) fn snippet(
93        mut self,
94        schema: &'a Schema,
95        span: Span,
96        label: impl Into<Cow<'a, str>>,
97    ) -> Self {
98        self.group = self.group.element(
99            Snippet::source(schema.source().unwrap())
100                .path(schema.path())
101                .annotation(
102                    AnnotationKind::Primary
103                        .span(span.start..span.end)
104                        .label(Some(label)),
105                ),
106        );
107
108        self
109    }
110
111    pub(crate) fn snippet_with_context(
112        mut self,
113        schema: &'a Schema,
114        main_span: Span,
115        main_label: impl Into<Cow<'a, str>>,
116        context_span: Span,
117        context_label: impl Into<Cow<'a, str>>,
118    ) -> Self {
119        self.group = self.group.element(
120            Snippet::source(schema.source().unwrap())
121                .path(schema.path())
122                .annotation(
123                    AnnotationKind::Primary
124                        .span(main_span.start..main_span.end)
125                        .label(Some(main_label)),
126                )
127                .annotation(
128                    AnnotationKind::Context
129                        .span(context_span.start..context_span.end)
130                        .label(Some(context_label)),
131                ),
132        );
133
134        self
135    }
136
137    pub(crate) fn context(
138        mut self,
139        schema: &'a Schema,
140        span: Span,
141        label: impl Into<Cow<'a, str>>,
142    ) -> Self {
143        self.group = self.group.element(
144            Snippet::source(schema.source().unwrap())
145                .path(schema.path())
146                .annotation(
147                    AnnotationKind::Context
148                        .span(span.start..span.end)
149                        .label(Some(label)),
150                ),
151        );
152
153        self
154    }
155
156    pub(crate) fn help(mut self, text: impl Into<Cow<'a, str>>) -> Self {
157        self.group = self.group.element(Level::HELP.message(text));
158        self
159    }
160
161    pub(crate) fn note(mut self, text: impl Into<Cow<'a, str>>) -> Self {
162        self.group = self.group.element(Level::NOTE.message(text));
163        self
164    }
165}