ethers_contract_abigen/source/
mod.rs1#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
6mod online;
7#[cfg(all(feature = "online", not(target_arch = "wasm32")))]
8pub use online::Explorer;
9
10use crate::util;
11use eyre::{Error, Result};
12use std::{env, fs, path::PathBuf, str::FromStr};
13
14#[derive(Clone, Debug, Eq, PartialEq)]
18pub enum Source {
19 String(String),
21
22 Local(PathBuf),
24
25 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
27 Explorer(Explorer, ethers_core::types::Address),
28
29 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
32 Npm(String),
33
34 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
36 Http(url::Url),
37}
38
39impl Default for Source {
40 fn default() -> Self {
41 Self::String("[]".to_string())
42 }
43}
44
45impl FromStr for Source {
46 type Err = Error;
47
48 fn from_str(s: &str) -> Result<Self> {
49 Source::parse(s)
50 }
51}
52
53impl Source {
54 pub fn parse(source: impl AsRef<str>) -> Result<Self> {
82 let source = source.as_ref().trim();
83 match source.chars().next() {
84 Some('[' | '{') => Ok(Self::String(source.to_string())),
85
86 #[cfg(any(not(feature = "online"), target_arch = "wasm32"))]
87 _ => Ok(Self::local(source)?),
88
89 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
90 Some('/') => Self::local(source),
91 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
92 _ => Self::parse_online(source),
93 }
94 }
95
96 pub fn local(path: impl AsRef<str>) -> Result<Self> {
98 let path = path.as_ref().trim_start_matches("file://");
100 let mut resolved = util::resolve_path(path)?;
101
102 if resolved.is_relative() {
103 if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
105 let new = PathBuf::from(manifest_dir).join(&resolved);
106 if new.exists() {
107 resolved = new;
108 }
109 }
110 }
111
112 if let Ok(canonicalized) = dunce::canonicalize(&resolved) {
114 resolved = canonicalized;
115 } else {
116 let path = resolved.display().to_string();
117 let err = if path.contains(':') {
118 eyre::eyre!("File does not exist: {path}\nYou may need to enable the `online` feature to parse this source.")
119 } else {
120 eyre::eyre!("File does not exist: {path}")
121 };
122 return Err(err)
123 }
124
125 Ok(Source::Local(resolved))
126 }
127
128 pub fn is_string(&self) -> bool {
130 matches!(self, Self::String(_))
131 }
132
133 pub fn as_string(&self) -> Option<&String> {
135 match self {
136 Self::String(s) => Some(s),
137 _ => None,
138 }
139 }
140
141 pub fn is_local(&self) -> bool {
143 matches!(self, Self::Local(_))
144 }
145
146 pub fn as_local(&self) -> Option<&PathBuf> {
148 match self {
149 Self::Local(p) => Some(p),
150 _ => None,
151 }
152 }
153
154 pub fn get(&self) -> Result<String> {
157 match self {
158 Self::Local(path) => Ok(fs::read_to_string(path)?),
159 Self::String(abi) => Ok(abi.clone()),
160
161 #[cfg(all(feature = "online", not(target_arch = "wasm32")))]
162 _ => self.get_online(),
163 }
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::path::Path;
171
172 #[test]
173 fn parse_source() {
174 let rel = "../tests/solidity-contracts/console.json";
175 let abs = concat!(env!("CARGO_MANIFEST_DIR"), "/../tests/solidity-contracts/console.json");
176 let abs_url = concat!(
177 "file://",
178 env!("CARGO_MANIFEST_DIR"),
179 "/../tests/solidity-contracts/console.json"
180 );
181 let exp = Source::Local(dunce::canonicalize(Path::new(rel)).unwrap());
182 assert_eq!(Source::parse(rel).unwrap(), exp);
183 assert_eq!(Source::parse(abs).unwrap(), exp);
184 assert_eq!(Source::parse(abs_url).unwrap(), exp);
185
186 let source = r#"[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"name","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"symbol","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"decimals","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"totalSupply","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]"#;
188 let parsed = Source::parse(source).unwrap();
189 assert_eq!(parsed, Source::String(source.to_owned()));
190
191 let source = format!(
193 r#"{{"_format": "hh-sol-artifact-1", "contractName": "Verifier", "sourceName": "contracts/verifier.sol", "abi": {source}, "bytecode": "0x", "deployedBytecode": "0x", "linkReferences": {{}}, "deployedLinkReferences": {{}}}}"#,
194 );
195 let parsed = Source::parse(&source).unwrap();
196 assert_eq!(parsed, Source::String(source));
197 }
198}