1use std::io::Write;
2
3pub 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
31pub 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#[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}