lintel_check/
diagnostics.rs1use miette::{Diagnostic, LabeledSpan, NamedSource, SourceSpan};
2use thiserror::Error;
3
4#[derive(Debug, Error, Diagnostic)]
6#[error("{message}")]
7pub struct ParseDiagnostic {
8 #[source_code]
9 pub src: NamedSource<String>,
10
11 #[label("here")]
12 pub span: SourceSpan,
13
14 pub message: String,
15}
16
17#[derive(Debug, Error)]
19#[error("{message}")]
20pub struct ValidationDiagnostic {
21 pub src: NamedSource<String>,
22
23 pub span: SourceSpan,
24
25 pub path: String,
26
27 pub instance_path: String,
28
29 pub message: String,
30}
31
32impl Diagnostic for ValidationDiagnostic {
33 fn source_code(&self) -> Option<&dyn miette::SourceCode> {
34 Some(&self.src)
35 }
36
37 fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
38 let label = if self.instance_path.is_empty() {
39 "here".to_string()
40 } else {
41 self.instance_path.clone()
42 };
43 Some(Box::new(std::iter::once(LabeledSpan::new(
44 Some(label),
45 self.span.offset(),
46 self.span.len(),
47 ))))
48 }
49}
50
51#[derive(Debug, Error, Diagnostic)]
53#[error("{path}: {message}")]
54pub struct FileDiagnostic {
55 pub path: String,
56 pub message: String,
57}
58
59pub fn find_instance_path_offset(content: &str, instance_path: &str) -> usize {
64 if instance_path.is_empty() || instance_path == "/" {
65 return 0;
66 }
67
68 let segment = instance_path.rsplit('/').next().unwrap_or("");
70 if segment.is_empty() {
71 return 0;
72 }
73
74 let json_key = format!("\"{segment}\"");
76 if let Some(pos) = content.find(&json_key) {
77 return pos;
78 }
79
80 let yaml_key = format!("{segment}:");
82 let quoted_yaml_key = format!("\"{segment}\":");
83 let mut offset = 0;
84 for line in content.lines() {
85 let trimmed = line.trim_start();
86 if trimmed.starts_with(&yaml_key) || trimmed.starts_with("ed_yaml_key) {
87 let key_start = line.len() - trimmed.len();
88 return offset + key_start;
89 }
90 offset += line.len() + 1; }
92
93 0
94}