taplo_cli/commands/
toml_test.rs

1use crate::Taplo;
2use anyhow::anyhow;
3use serde::{
4    ser::{SerializeMap, SerializeSeq},
5    Serialize,
6};
7use taplo::dom::{
8    node::{DateTimeValue, DomNode},
9    Node,
10};
11use taplo_common::environment::Environment;
12use tokio::io::AsyncReadExt;
13
14impl<E: Environment> Taplo<E> {
15    pub async fn execute_toml_test(&self) -> Result<(), anyhow::Error> {
16        let mut buf = String::new();
17        self.env.stdin().read_to_string(&mut buf).await?;
18
19        let parse = taplo::parser::parse(&buf);
20
21        if !parse.errors.is_empty() {
22            for err in parse.errors {
23                eprintln!("{err}");
24            }
25
26            return Err(anyhow!("invalid toml"));
27        }
28        let dom = parse.into_dom();
29
30        if let Err(err) = dom.validate() {
31            for err in err {
32                eprintln!("{err}");
33            }
34            return Err(anyhow!("invalid toml"));
35        }
36
37        serde_json::to_writer(std::io::stdout(), &TomlTestValue::new(&dom))?;
38
39        Ok(())
40    }
41}
42
43#[derive(Clone, Copy, Serialize)]
44#[serde(rename_all = "lowercase")]
45pub enum TomlTestType {
46    String,
47    Integer,
48    Float,
49    Bool,
50    DateTime,
51    #[serde(rename = "datetime-local")]
52    DateTimeLocal,
53    #[serde(rename = "date-local")]
54    DateLocal,
55    #[serde(rename = "time-local")]
56    TimeLocal,
57}
58
59impl TomlTestType {
60    fn of(node: &Node) -> Option<Self> {
61        match node {
62            Node::Bool(_) => Some(TomlTestType::Bool),
63            Node::Integer(_) => Some(TomlTestType::Integer),
64            Node::Float(_) => Some(TomlTestType::Float),
65            Node::Str(_) => Some(TomlTestType::String),
66            Node::Date(d) => match d.value() {
67                DateTimeValue::OffsetDateTime(_) => Some(TomlTestType::DateTime),
68                DateTimeValue::LocalDateTime(_) => Some(TomlTestType::DateTimeLocal),
69                DateTimeValue::Date(_) => Some(TomlTestType::DateLocal),
70                DateTimeValue::Time(_) => Some(TomlTestType::TimeLocal),
71            },
72            Node::Array(_) => None,
73            Node::Table(_) => None,
74            Node::Invalid(_) => unreachable!(),
75        }
76    }
77}
78
79pub struct TomlTestValue<'a> {
80    r#type: Option<TomlTestType>,
81    node: &'a Node,
82}
83
84impl<'a> TomlTestValue<'a> {
85    pub fn new(node: &'a Node) -> Self {
86        Self {
87            r#type: TomlTestType::of(node),
88            node,
89        }
90    }
91}
92
93impl Serialize for TomlTestValue<'_> {
94    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
95    where
96        S: serde::Serializer,
97    {
98        if let Some(ty) = self.r#type {
99            let mut map = serializer.serialize_map(Some(2))?;
100            map.serialize_entry("type", &ty)?;
101            map.serialize_entry(
102                "value",
103                &match self.node {
104                    Node::Str(d) => d.value().to_string(),
105                    Node::Float(f) if f.value().is_nan() => String::from("nan"),
106                    Node::Float(f) if f.value().is_infinite() => f.syntax().unwrap().to_string(),
107                    _ => serde_json::to_string(&self.node).map_err(serde::ser::Error::custom)?,
108                },
109            )?;
110            map.end()
111        } else {
112            match &self.node {
113                Node::Array(array) => {
114                    let items = array.items().read();
115
116                    let mut seq = serializer.serialize_seq(Some(items.len()))?;
117                    for value in &**items {
118                        seq.serialize_element(&TomlTestValue::new(value))?;
119                    }
120                    seq.end()
121                }
122                Node::Table(table) => {
123                    let entries = table.entries().read();
124
125                    let mut map = serializer.serialize_map(Some(entries.len()))?;
126                    for (key, value) in entries.iter() {
127                        map.serialize_entry(key.value(), &TomlTestValue::new(value))?;
128                    }
129                    map.end()
130                }
131                _ => unreachable!(),
132            }
133        }
134    }
135}