fuels_rs/
source.rs

1use std::{
2    borrow::Cow,
3    env, fs,
4    path::{Path, PathBuf},
5    str::FromStr,
6};
7use url::Url;
8
9use anyhow::{anyhow, Context, Error, Result};
10
11/// A source of a Truffle artifact JSON.
12#[derive(Clone, Debug, Eq, PartialEq)]
13pub enum Source {
14    /// A raw ABI string
15    String(String),
16
17    /// An ABI located on the local file system.
18    Local(PathBuf),
19    // In the future we can have an ABI to be retrieved over HTTP(S) or block explorer
20    // Http(Url),
21}
22
23impl Source {
24    /// Parses an ABI from a source
25    ///
26    /// Contract ABIs can be retrieved from the local filesystem or it can
27    /// be provided in-line. It accepts:
28    ///
29    /// - raw ABI JSON
30    ///
31    /// - `relative/path/to/Contract.json`: a relative path to an ABI JSON file.
32    /// This relative path is rooted in the current working directory.
33    /// To specify the root for relative paths, use `Source::with_root`.
34    ///
35    /// - `/absolute/path/to/Contract.json` or
36    ///   `file:///absolute/path/to/Contract.json`: an absolute path or file URL
37    ///   to an ABI JSON file.
38    pub fn parse<S>(source: S) -> Result<Self, Error>
39    where
40        S: AsRef<str>,
41    {
42        let source = source.as_ref().trim();
43
44        if source.starts_with('[') || source.starts_with("\n") {
45            return Ok(Source::String(source.to_owned()));
46        }
47        let root = env::current_dir()?.canonicalize()?;
48        Source::with_root(root, source)
49    }
50
51    /// Parses an artifact source from a string and a specified root directory
52    /// for resolving relative paths. See `Source::with_root` for more details
53    /// on supported source strings.
54    fn with_root<P, S>(root: P, source: S) -> Result<Self, Error>
55    where
56        P: AsRef<Path>,
57        S: AsRef<str>,
58    {
59        let base = Url::from_directory_path(&root).map_err(|_| {
60            anyhow!(
61                "root path '{:?}' is not absolute",
62                root.as_ref().as_os_str()
63            )
64        })?;
65        let url = base.join(source.as_ref())?;
66
67        match url.scheme() {
68            "file" => Ok(Source::local(url.path())),
69            // TODO: support other URL schemes (http, etc)
70            _ => Err(anyhow!("unsupported URL '{}'", url)),
71        }
72    }
73
74    /// Creates a local filesystem source from a path string.
75    fn local<P>(path: P) -> Self
76    where
77        P: AsRef<Path>,
78    {
79        Source::Local(path.as_ref().into())
80    }
81
82    /// Retrieves the source JSON of the artifact this will either read the JSON
83    /// from the file system or retrieve a contract ABI from the network
84    /// dependending on the source type.
85    pub fn get(&self) -> Result<String> {
86        match self {
87            Source::Local(path) => get_local_contract(path),
88            Source::String(abi) => Ok(abi.clone()),
89        }
90    }
91}
92
93fn get_local_contract(path: &Path) -> Result<String> {
94    let path = if path.is_relative() {
95        let absolute_path = path.canonicalize().with_context(|| {
96            format!(
97                "unable to canonicalize file from working dir {} with path {}",
98                env::current_dir()
99                    .map(|cwd| cwd.display().to_string())
100                    .unwrap_or_else(|err| format!("??? ({})", err)),
101                path.display(),
102            )
103        })?;
104        Cow::Owned(absolute_path)
105    } else {
106        Cow::Borrowed(path)
107    };
108
109    let json = fs::read_to_string(&path).context(format!(
110        "failed to read artifact JSON file with path {}",
111        &path.display()
112    ))?;
113    Ok(json)
114}
115
116impl FromStr for Source {
117    type Err = Error;
118
119    fn from_str(s: &str) -> Result<Self> {
120        Source::parse(s)
121    }
122}