toml_test/
verify.rs

1use std::io::Write;
2
3/// Abstract over your TOML serialization
4pub trait Encoder {
5    fn encode(&self, data: crate::decoded::DecodedValue) -> Result<String, crate::Error>;
6
7    fn verify_valid_case(&self, decoded: &[u8], fixture: &dyn Decoder) -> Result<(), crate::Error> {
8        let decoded_expected = crate::decoded::DecodedValue::from_slice(decoded)?;
9        let actual = self.encode(decoded_expected.clone())?;
10        let decoded_actual = fixture.decode(actual.as_bytes()).map_err(|err| {
11            crate::Error::new(format!(
12                "Could not parse encoded TOML: {err}\n```\n{actual}\n```"
13            ))
14        })?;
15
16        if decoded_actual == decoded_expected {
17            Ok(())
18        } else {
19            Err(crate::Error::new(format!(
20                "Unexpected decoding\n```toml\n{}\n```\nExpected\n{}\nActual\n{}",
21                actual,
22                decoded_expected.to_string_pretty().unwrap(),
23                decoded_actual.to_string_pretty().unwrap()
24            )))
25        }
26    }
27
28    fn name(&self) -> &str;
29}
30
31/// Abstract over your TOML deserialization
32pub trait Decoder {
33    fn decode(&self, data: &[u8]) -> Result<crate::decoded::DecodedValue, crate::Error>;
34
35    fn verify_valid_case(&self, fixture: &[u8], expected: &[u8]) -> Result<(), crate::Error> {
36        let actual = self.decode(fixture)?;
37        let expected = crate::decoded::DecodedValue::from_slice(expected)?;
38        if actual == expected {
39            Ok(())
40        } else {
41            Err(crate::Error::new(format!(
42                "Unexpected decoding\n```toml\n{}\n```\nExpected\n{}\nActual\n{}",
43                std::str::from_utf8(fixture).unwrap(),
44                expected.to_string_pretty().unwrap(),
45                actual.to_string_pretty().unwrap()
46            )))
47        }
48    }
49
50    fn verify_invalid_case(&self, fixture: &[u8]) -> Result<crate::Error, crate::Error> {
51        match self.decode(fixture) {
52            Ok(value) => Err(crate::Error::new(format!(
53                "Should have failed but got:\n{}\n```toml\n{}\n```",
54                value.to_string_pretty().unwrap(),
55                std::str::from_utf8(fixture).unwrap(),
56            ))),
57            Err(err) => Ok(err),
58        }
59    }
60
61    fn name(&self) -> &str;
62}
63
64/// TOML parser-as-a-binary
65#[derive(Clone, Debug, PartialEq, Eq)]
66pub struct Command {
67    bin: std::path::PathBuf,
68}
69
70impl Command {
71    pub fn new(path: impl AsRef<std::path::Path>) -> Self {
72        Self {
73            bin: path.as_ref().to_owned(),
74        }
75    }
76}
77
78impl Encoder for Command {
79    fn encode(&self, data: crate::decoded::DecodedValue) -> Result<String, crate::Error> {
80        let data = data.to_string_pretty()?;
81
82        let mut cmd = std::process::Command::new(&self.bin);
83        cmd.stdin(std::process::Stdio::piped())
84            .stdout(std::process::Stdio::piped())
85            .stderr(std::process::Stdio::piped());
86        let child = cmd.spawn().map_err(crate::Error::new)?;
87        child
88            .stdin
89            .as_ref()
90            .unwrap()
91            .write_all(data.as_bytes())
92            .map_err(crate::Error::new)?;
93
94        let output = child.wait_with_output().map_err(crate::Error::new)?;
95        if output.status.success() {
96            let output = String::from_utf8(output.stdout).map_err(crate::Error::new)?;
97            Ok(output)
98        } else {
99            let message = String::from_utf8_lossy(&output.stderr);
100            Err(crate::Error::new(format!(
101                "{} failed with {:?}: {}",
102                self.bin.display(),
103                output.status.code(),
104                message
105            )))
106        }
107    }
108
109    fn name(&self) -> &str {
110        self.bin.to_str().expect("we'll always get valid UTF-8")
111    }
112}
113
114impl Decoder for Command {
115    fn decode(&self, data: &[u8]) -> Result<crate::decoded::DecodedValue, crate::Error> {
116        let mut cmd = std::process::Command::new(&self.bin);
117        cmd.stdin(std::process::Stdio::piped())
118            .stdout(std::process::Stdio::piped())
119            .stderr(std::process::Stdio::piped());
120        let child = cmd.spawn().map_err(crate::Error::new)?;
121        child
122            .stdin
123            .as_ref()
124            .unwrap()
125            .write_all(data)
126            .map_err(crate::Error::new)?;
127
128        let output = child.wait_with_output().map_err(crate::Error::new)?;
129        if output.status.success() {
130            let output = crate::decoded::DecodedValue::from_slice(&output.stdout)
131                .map_err(crate::Error::new)?;
132            Ok(output)
133        } else {
134            let message = String::from_utf8_lossy(&output.stderr);
135            Err(crate::Error::new(format!(
136                "{} failed with {:?}: {}",
137                self.bin.display(),
138                output.status.code(),
139                message
140            )))
141        }
142    }
143
144    fn name(&self) -> &str {
145        self.bin.to_str().expect("we'll always get valid UTF-8")
146    }
147}