schemaui_cli/
io.rs

1use std::fs;
2use std::io::{self, Read};
3use std::path::PathBuf;
4
5use color_eyre::eyre::{Report, Result, WrapErr};
6use schemaui::{DocumentFormat, parse_document_str};
7use serde_json::Value;
8
9#[derive(Debug)]
10pub enum InputSource {
11    File(PathBuf),
12    Stdin,
13}
14
15pub fn load_value(spec: &str, format: DocumentFormat, label: &str) -> Result<Value> {
16    if spec == "-" {
17        let contents = read_from_source(&InputSource::Stdin)?;
18        return parse_contents(&contents, format, label);
19    }
20
21    let path = PathBuf::from(spec);
22    match read_from_source(&InputSource::File(path.clone())) {
23        Ok(contents) => parse_contents(&contents, format, label),
24        Err(err) => {
25            if is_not_found(&err) {
26                let inline_label = format!("inline {label}");
27                return parse_contents(spec, format, &inline_label);
28            }
29            Err(err.wrap_err(format!("failed to load {label} from {}", path.display())))
30        }
31    }
32}
33
34fn read_from_source(source: &InputSource) -> Result<String> {
35    match source {
36        InputSource::Stdin => {
37            let mut buffer = String::new();
38            io::stdin()
39                .read_to_string(&mut buffer)
40                .wrap_err("failed to read from stdin")?;
41            Ok(buffer)
42        }
43        InputSource::File(path) => fs::read_to_string(path)
44            .wrap_err_with(|| format!("failed to read file {}", path.display())),
45    }
46}
47
48fn parse_contents(contents: &str, format: DocumentFormat, label: &str) -> Result<Value> {
49    match parse_document_str(contents, format) {
50        Ok(value) => Ok(value),
51        Err(primary) => {
52            for candidate in DocumentFormat::available_formats() {
53                if candidate == format {
54                    continue;
55                }
56                if let Ok(value) = parse_document_str(contents, candidate) {
57                    return Ok(value);
58                }
59            }
60            Err(Report::msg(format!(
61                "failed to parse {label}: tried {} (first error: {primary})",
62                format_list()
63            )))
64        }
65    }
66}
67
68pub fn is_not_found(err: &Report) -> bool {
69    err.downcast_ref::<io::Error>()
70        .map_or(false, |io_err| io_err.kind() == io::ErrorKind::NotFound)
71}
72
73fn format_list() -> &'static str {
74    #[cfg(all(feature = "yaml", feature = "toml"))]
75    {
76        "JSON/YAML/TOML"
77    }
78    #[cfg(all(feature = "yaml", not(feature = "toml")))]
79    {
80        "JSON/YAML"
81    }
82    #[cfg(all(not(feature = "yaml"), feature = "toml"))]
83    {
84        "JSON/TOML"
85    }
86    #[cfg(all(not(feature = "yaml"), not(feature = "toml")))]
87    {
88        "JSON"
89    }
90}