1use super::ValidationError;
16#[cfg(feature = "idl")]
17use miette::{Diagnostic, NamedSource, SourceSpan};
18
19#[derive(Debug)]
25pub struct ValidationDiagnostic {
26 pub error: ValidationError,
28 #[cfg(feature = "idl")]
31 pub src: Option<NamedSource<String>>,
32 #[cfg(feature = "idl")]
35 pub span: Option<SourceSpan>,
36}
37
38impl std::fmt::Display for ValidationDiagnostic {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 std::fmt::Display::fmt(&self.error, f)
41 }
42}
43
44impl std::error::Error for ValidationDiagnostic {
45 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46 self.error.source()
47 }
48}
49
50#[cfg(feature = "idl")]
51impl Diagnostic for ValidationDiagnostic {
52 fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
53 self.error.code()
54 }
55
56 fn severity(&self) -> Option<miette::Severity> {
57 self.error.severity()
58 }
59
60 fn help<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
61 self.error.help()
62 }
63
64 fn url<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
65 self.error.url()
66 }
67
68 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
69 self.src
70 .as_ref()
71 .map(|s| s as &dyn miette::SourceCode)
72 .or_else(|| self.error.source_code())
73 }
74
75 fn labels(&self) -> Option<Box<dyn Iterator<Item = miette::LabeledSpan> + '_>> {
76 if let Some(span) = self.span {
77 Some(Box::new(std::iter::once(
78 miette::LabeledSpan::new_with_span(Some("here".to_string()), span),
79 )))
80 } else {
81 self.error.labels()
82 }
83 }
84}
85
86impl ValidationDiagnostic {
87 #[cfg(feature = "idl")]
94 pub(crate) fn new(
95 error: ValidationError,
96 scope: &[String],
97 source: Option<(&str, &str)>,
98 ) -> Self {
99 let (src, span) = match source {
100 Some((filename, contents)) => {
101 let span = error
102 .needle()
103 .and_then(|needle| locate(contents, scope, needle));
104 (Some(NamedSource::new(filename, contents.to_string())), span)
105 }
106 None => (None, None),
107 };
108 Self { error, src, span }
109 }
110
111 #[cfg(not(feature = "idl"))]
115 pub(crate) fn new(
116 error: ValidationError,
117 _scope: &[String],
118 _source: Option<(&str, &str)>,
119 ) -> Self {
120 Self { error }
121 }
122}
123
124#[cfg(feature = "idl")]
127#[derive(Clone, Copy)]
128enum Needle<'a> {
129 Decl(&'a str),
131 Second(&'a str),
133 Text(&'a str),
135 Scope,
137 SecondScope,
140}
141
142#[cfg(feature = "idl")]
143impl ValidationError {
144 fn needle(&self) -> Option<Needle<'_>> {
147 use Needle::{Decl, Scope, Second, SecondScope, Text};
148 fn duplicate<'a>(name: &'a str, first: &str, second: &str) -> Needle<'a> {
151 if first == second {
152 Second(name)
153 } else {
154 Decl(name)
155 }
156 }
157 Some(match self {
158 Self::UnsupportedSchemaVersion { version, .. } => Text(version),
159 Self::DuplicateModuleName { .. } => SecondScope,
160 Self::InvalidModuleName { name, .. }
161 | Self::ReservedKeyword { name }
162 | Self::InvalidIdentifier { name, .. }
163 | Self::NameCollisionWithErrorDomain { name, .. }
164 | Self::InvalidErrorCode { name, .. }
165 | Self::EmptyStruct { name, .. }
166 | Self::EmptyEnum { name, .. }
167 | Self::EmptyInterface { name, .. }
168 | Self::EmptyCallbackInterface { name, .. } => Decl(name),
169 Self::DuplicateTypeName {
170 name,
171 first,
172 second,
173 }
174 | Self::DuplicateFunctionName {
175 name,
176 first,
177 second,
178 }
179 | Self::DuplicateErrorCodeName {
180 name,
181 first,
182 second,
183 } => duplicate(name, first, second),
184 Self::DuplicateParamName { param: name, .. }
185 | Self::DuplicateStructField { field: name, .. }
186 | Self::DuplicateEnumVariant { variant: name, .. }
187 | Self::DuplicateEnumVariantField { field: name, .. }
188 | Self::DuplicateInterfaceMember { name, .. }
189 | Self::DuplicateCallbackMethod { name, .. } => Second(name),
190 Self::CancellableNotAsync { .. }
191 | Self::ThrowsWithoutErrorDomain { .. }
192 | Self::AsyncIteratorReturn { .. } => Scope,
193 Self::DuplicateEnumValue { enum_name, .. } => Decl(enum_name),
194 Self::ConstructorHasReturn { constructor, .. }
195 | Self::AsyncConstructor { constructor, .. } => Decl(constructor),
196 Self::InvalidCallbackMethod { method, .. } => Decl(method),
197 Self::UnknownTypeRef { name }
198 | Self::QualifiedTypeRef { name }
199 | Self::UnsupportedPrimitive { name }
200 | Self::InterfaceInInvalidPosition { name, .. }
201 | Self::CallbackInterfaceInInvalidPosition { name, .. } => Text(name),
202 Self::InvalidMapKey { key_type } => Text(key_type),
203 Self::IteratorInInvalidPosition { .. } => Text("iter"),
204 Self::NoModuleName
205 | Self::SymbolCollision { .. }
206 | Self::ErrorDomainMissingName { .. }
207 | Self::DuplicateErrorCode { .. } => return None,
208 })
209 }
210}
211
212#[cfg(feature = "idl")]
215fn locate(src: &str, scope: &[String], needle: Needle<'_>) -> Option<SourceSpan> {
216 let (scope, needle) = match (needle, scope.split_last()) {
219 (Needle::Scope, Some((last, outer))) => (outer, Needle::Decl(last)),
220 (Needle::SecondScope, Some((last, outer))) => (outer, Needle::Second(last)),
221 (Needle::Scope | Needle::SecondScope, None) => return None,
222 _ => (scope, needle),
223 };
224 let mut from = 0;
225 for anchor in scope {
226 if let Some(at) = find_decl(src, from, anchor) {
227 from = at + anchor.len();
228 }
229 }
230 let (text, scoped) = match needle {
231 Needle::Decl(name) => (name, find_decl(src, from, name)),
232 Needle::Second(name) => (
233 name,
234 find_decl(src, from, name)
235 .and_then(|first| find_decl(src, first + name.len(), name))
236 .or_else(|| find_decl(src, from, name)),
237 ),
238 Needle::Text(text) => (text, find_token(src, from, text)),
239 Needle::Scope | Needle::SecondScope => unreachable!("resolved above"),
240 };
241 let at = scoped.or_else(|| find_token(src, 0, text))?;
242 Some(span(src, at, text.len()))
243}
244
245#[cfg(feature = "idl")]
246fn is_ident_char(c: char) -> bool {
247 c.is_ascii_alphanumeric() || c == '_'
248}
249
250#[cfg(feature = "idl")]
253fn find_token(src: &str, from: usize, needle: &str) -> Option<usize> {
254 if needle.is_empty() {
255 return None;
256 }
257 src.get(from..)?
258 .match_indices(needle)
259 .map(|(i, _)| from + i)
260 .find(|&at| {
261 let before = src[..at].chars().next_back();
262 let after = src[at + needle.len()..].chars().next();
263 !before.is_some_and(is_ident_char) && !after.is_some_and(is_ident_char)
264 })
265}
266
267#[cfg(feature = "idl")]
271fn find_decl(src: &str, from: usize, name: &str) -> Option<usize> {
272 let mut at = from;
273 loop {
274 let hit = find_token(src, at, name)?;
275 let before = src[..hit].trim_end_matches(['"', '\'']).trim_end();
276 if let Some(key) = before.strip_suffix([':', '=']) {
277 let key = key.trim_end().trim_end_matches(['"', '\'']);
278 if key
279 .strip_suffix("name")
280 .is_some_and(|rest| !rest.chars().next_back().is_some_and(is_ident_char))
281 {
282 return Some(hit);
283 }
284 }
285 at = hit + name.len();
286 }
287}
288
289#[cfg(feature = "idl")]
292fn span(src: &str, at: usize, len: usize) -> SourceSpan {
293 let quote = src[..at]
294 .chars()
295 .next_back()
296 .filter(|c| matches!(c, '"' | '\''));
297 match quote {
298 Some(q) if src[at + len..].starts_with(q) => SourceSpan::new((at - 1).into(), len + 2),
299 _ => SourceSpan::new(at.into(), len),
300 }
301}