ethers_solc/report/
compiler.rs1use crate::{CompilerInput, CompilerOutput};
9use semver::Version;
10use std::{env, path::PathBuf, str::FromStr};
11
12#[derive(Debug, Clone, Default)]
27pub struct SolcCompilerIoReporter {
28 target: Option<Target>,
30}
31
32impl SolcCompilerIoReporter {
33 pub fn new(value: impl AsRef<str>) -> Self {
36 Self { target: Some(value.as_ref().parse().unwrap_or_default()) }
37 }
38
39 pub const DEFAULT_ENV: &'static str = "ETHERS_SOLC_LOG";
44
45 pub fn from_default_env() -> Self {
48 Self::from_env(Self::DEFAULT_ENV)
49 }
50
51 pub fn from_env<A: AsRef<str>>(env: A) -> Self {
54 env::var(env.as_ref()).map(Self::new).unwrap_or_default()
55 }
56
57 pub fn log_compiler_input(&self, input: &CompilerInput, version: &Version) {
59 if let Some(ref target) = self.target {
60 target.write_input(input, version)
61 }
62 }
63
64 pub fn log_compiler_output(&self, output: &CompilerOutput, version: &Version) {
66 if let Some(ref target) = self.target {
67 target.write_output(output, version)
68 }
69 }
70}
71
72impl<S> From<S> for SolcCompilerIoReporter
73where
74 S: AsRef<str>,
75{
76 fn from(s: S) -> Self {
77 Self::new(s)
78 }
79}
80
81#[derive(Debug, Clone, Eq, PartialEq)]
83struct Target {
84 dest_input: PathBuf,
86 dest_output: PathBuf,
88}
89
90impl Target {
91 fn write_input(&self, input: &CompilerInput, version: &Version) {
92 tracing::trace!("logging compiler input to {}", self.dest_input.display());
93 match serde_json::to_string_pretty(input) {
94 Ok(json) => {
95 if let Err(err) = std::fs::write(get_file_name(&self.dest_input, version), json) {
96 tracing::error!("Failed to write compiler input: {}", err)
97 }
98 }
99 Err(err) => {
100 tracing::error!("Failed to serialize compiler input: {}", err)
101 }
102 }
103 }
104
105 fn write_output(&self, output: &CompilerOutput, version: &Version) {
106 tracing::trace!("logging compiler output to {}", self.dest_output.display());
107 match serde_json::to_string_pretty(output) {
108 Ok(json) => {
109 if let Err(err) = std::fs::write(get_file_name(&self.dest_output, version), json) {
110 tracing::error!("Failed to write compiler output: {}", err)
111 }
112 }
113 Err(err) => {
114 tracing::error!("Failed to serialize compiler output: {}", err)
115 }
116 }
117 }
118}
119
120impl Default for Target {
121 fn default() -> Self {
122 Self {
123 dest_input: "compiler-input.json".into(),
124 dest_output: "compiler-output.json".into(),
125 }
126 }
127}
128
129impl FromStr for Target {
130 type Err = Box<dyn std::error::Error + Send + Sync>;
131 fn from_str(s: &str) -> Result<Self, Self::Err> {
132 let mut dest_input = None;
133 let mut dest_output = None;
134 for part in s.split(',') {
135 let (name, val) =
136 part.split_once('=').ok_or_else(|| BadName { name: part.to_string() })?;
137 match name {
138 "i" | "in" | "input" | "compilerinput" => {
139 dest_input = Some(PathBuf::from(val));
140 }
141 "o" | "out" | "output" | "compileroutput" => {
142 dest_output = Some(PathBuf::from(val));
143 }
144 _ => return Err(BadName { name: part.to_string() }.into()),
145 };
146 }
147
148 Ok(Self {
149 dest_input: dest_input.unwrap_or_else(|| "compiler-input.json".into()),
150 dest_output: dest_output.unwrap_or_else(|| "compiler-output.json".into()),
151 })
152 }
153}
154
155#[derive(Clone, Debug, thiserror::Error)]
157#[error("{}", self.name)]
158pub struct BadName {
159 name: String,
160}
161
162fn get_file_name(path: impl Into<PathBuf>, v: &Version) -> PathBuf {
164 let mut path = path.into();
165 if let Some(stem) = path.file_stem().and_then(|s| s.to_str().map(|s| s.to_string())) {
166 path.set_file_name(format!("{stem}.{}.{}.{}.json", v.major, v.minor, v.patch));
167 }
168 path
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174
175 #[test]
176 fn can_set_file_name() {
177 let s = "/a/b/c/in.json";
178 let p = get_file_name(s, &Version::parse("0.8.10").unwrap());
179 assert_eq!(PathBuf::from("/a/b/c/in.0.8.10.json"), p);
180
181 let s = "abc.json";
182 let p = get_file_name(s, &Version::parse("0.8.10").unwrap());
183 assert_eq!(PathBuf::from("abc.0.8.10.json"), p);
184 }
185
186 #[test]
187 fn can_parse_target() {
188 let target: Target = "in=in.json,out=out.json".parse().unwrap();
189 assert_eq!(target, Target { dest_input: "in.json".into(), dest_output: "out.json".into() });
190
191 let target: Target = "in=in.json".parse().unwrap();
192 assert_eq!(target, Target { dest_input: "in.json".into(), ..Default::default() });
193
194 let target: Target = "out=out.json".parse().unwrap();
195 assert_eq!(target, Target { dest_output: "out.json".into(), ..Default::default() });
196 }
197
198 #[test]
199 fn can_init_reporter_from_env() {
200 let rep = SolcCompilerIoReporter::from_default_env();
201 assert!(rep.target.is_none());
202 std::env::set_var("ETHERS_SOLC_LOG", "in=in.json,out=out.json");
203 let rep = SolcCompilerIoReporter::from_default_env();
204 assert!(rep.target.is_some());
205 assert_eq!(
206 rep.target.unwrap(),
207 Target { dest_input: "in.json".into(), dest_output: "out.json".into() }
208 );
209 std::env::remove_var("ETHERS_SOLC_LOG");
210 }
211}