1use crate::{Parser, Schema, Span};
7use annotate_snippets::renderer::DecorStyle;
8use annotate_snippets::{AnnotationKind, Group, Level, Snippet};
9use std::borrow::Cow;
10use std::slice;
11
12pub trait Diagnostic {
14 fn kind(&self) -> DiagnosticKind;
16
17 fn schema_name(&self) -> &str;
21
22 fn render(&self, renderer: &Renderer, parser: &Parser) -> String;
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
28pub enum DiagnosticKind {
29 Error,
31
32 Warning,
34}
35
36#[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}