1use crate::App;
2use codespan_reporting::{
3 diagnostic::{Diagnostic, Label},
4 files::SimpleFile,
5 term::{
6 self,
7 termcolor::{Ansi, NoColor},
8 },
9};
10use itertools::Itertools;
11use jsona::{
12 dom::{self, Node},
13 parser,
14 rowan::TextRange,
15};
16use jsona_util::{environment::Environment, schema::JSONASchemaValidationError};
17use std::ops::Range;
18use tokio::io::AsyncWriteExt;
19
20impl<E: Environment> App<E> {
21 pub(crate) async fn print_parse_errors(
22 &self,
23 file: &SimpleFile<&str, &str>,
24 errors: &[parser::Error],
25 ) -> Result<(), anyhow::Error> {
26 let mut out_diag = Vec::<u8>::new();
27
28 let config = codespan_reporting::term::Config::default();
29
30 for error in errors.iter().unique_by(|e| e.range) {
31 let diag = Diagnostic::error()
32 .with_message("invalid JSONA")
33 .with_labels(Vec::from([
34 Label::primary((), std_range(error.range)).with_message(&error.message)
35 ]));
36
37 if self.colors {
38 term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
39 } else {
40 term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
41 }
42 }
43
44 let mut stderr = self.env.stderr();
45
46 stderr.write_all(&out_diag).await?;
47 stderr.flush().await?;
48
49 Ok(())
50 }
51
52 pub(crate) async fn print_semantic_errors(
53 &self,
54 file: &SimpleFile<&str, &str>,
55 errors: impl Iterator<Item = dom::DomError>,
56 ) -> Result<(), anyhow::Error> {
57 let mut out_diag = Vec::<u8>::new();
58
59 let config = codespan_reporting::term::Config::default();
60
61 for error in errors {
62 let diag = match &error {
63 dom::DomError::ConflictingKeys { key, other_key } => Diagnostic::error()
64 .with_message(error.to_string())
65 .with_labels(Vec::from([
66 Label::primary((), std_range(key.text_range().unwrap()))
67 .with_message("duplicate key"),
68 Label::secondary((), std_range(other_key.text_range().unwrap()))
69 .with_message("duplicate found here"),
70 ])),
71 dom::DomError::InvalidNode { syntax }
72 | dom::DomError::InvalidString { syntax }
73 | dom::DomError::InvalidNumber { syntax } => Diagnostic::error()
74 .with_message(error.to_string())
75 .with_labels(Vec::from([Label::primary(
76 (),
77 std_range(syntax.text_range()),
78 )
79 .with_message(error.to_string())])),
80 };
81
82 if self.colors {
83 term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
84 } else {
85 term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
86 }
87 }
88 let mut stderr = self.env.stderr();
89 stderr.write_all(&out_diag).await?;
90 stderr.flush().await?;
91 Ok(())
92 }
93
94 pub(crate) async fn print_schema_errors(
95 &self,
96 file: &SimpleFile<&str, &str>,
97 node: &Node,
98 errors: &[JSONASchemaValidationError],
99 ) -> Result<(), anyhow::Error> {
100 let config = codespan_reporting::term::Config::default();
101
102 let mut out_diag = Vec::<u8>::new();
103 for err in errors {
104 let text_range = node
105 .path(&err.keys)
106 .and_then(|v| v.text_range())
107 .or_else(|| err.keys.last_text_range())
108 .unwrap_or_default();
109 let diag = Diagnostic::error()
110 .with_message(&err.kind.to_string())
111 .with_labels(Vec::from([
112 Label::primary((), std_range(text_range)).with_message(&err.kind.to_string())
113 ]));
114
115 if self.colors {
116 term::emit(&mut Ansi::new(&mut out_diag), &config, file, &diag)?;
117 } else {
118 term::emit(&mut NoColor::new(&mut out_diag), &config, file, &diag)?;
119 };
120 }
121 let mut stderr = self.env.stderr();
122 stderr.write_all(&out_diag).await?;
123 stderr.flush().await?;
124
125 Ok(())
126 }
127}
128
129fn std_range(range: TextRange) -> Range<usize> {
130 let start: usize = u32::from(range.start()) as _;
131 let end: usize = u32::from(range.end()) as _;
132 start..end
133}