jsona_cli/
printing.rs

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}