soroban_cli/commands/contract/
fetch.rs

1use std::convert::Infallible;
2
3use std::io::Write;
4use std::path::{Path, PathBuf};
5use std::str::FromStr;
6use std::{fmt::Debug, fs, io};
7
8use clap::{arg, command, Parser};
9
10use crate::{
11    commands::{global, NetworkRunnable},
12    config::{
13        self, locator,
14        network::{self, Network},
15    },
16    wasm, Pwd,
17};
18
19#[derive(Parser, Debug, Default, Clone)]
20#[allow(clippy::struct_excessive_bools)]
21#[group(skip)]
22pub struct Cmd {
23    /// Contract ID to fetch
24    #[arg(long = "id", env = "STELLAR_CONTRACT_ID")]
25    pub contract_id: config::UnresolvedContract,
26    /// Where to write output otherwise stdout is used
27    #[arg(long, short = 'o')]
28    pub out_file: Option<std::path::PathBuf>,
29    #[command(flatten)]
30    pub locator: locator::Args,
31    #[command(flatten)]
32    pub network: network::Args,
33}
34
35impl FromStr for Cmd {
36    type Err = clap::error::Error;
37
38    fn from_str(s: &str) -> Result<Self, Self::Err> {
39        use clap::{CommandFactory, FromArgMatches};
40        Self::from_arg_matches_mut(&mut Self::command().get_matches_from(s.split_whitespace()))
41    }
42}
43
44impl Pwd for Cmd {
45    fn set_pwd(&mut self, pwd: &Path) {
46        self.locator.set_pwd(pwd);
47    }
48}
49
50#[derive(thiserror::Error, Debug)]
51pub enum Error {
52    #[error(transparent)]
53    Config(#[from] config::Error),
54    #[error(transparent)]
55    Locator(#[from] locator::Error),
56    #[error(transparent)]
57    Io(#[from] std::io::Error),
58    #[error("reading file {0:?}: {1}")]
59    CannotWriteContractFile(PathBuf, io::Error),
60    #[error(transparent)]
61    Network(#[from] network::Error),
62    #[error("cannot create contract directory for {0:?}")]
63    CannotCreateContractDir(PathBuf),
64    #[error(transparent)]
65    Wasm(#[from] wasm::Error),
66}
67
68impl From<Infallible> for Error {
69    fn from(_: Infallible) -> Self {
70        unreachable!()
71    }
72}
73
74impl Cmd {
75    pub async fn run(&self) -> Result<(), Error> {
76        let bytes = self.get_bytes().await?;
77        if let Some(out_file) = &self.out_file {
78            if let Some(parent) = out_file.parent() {
79                if !parent.exists() {
80                    fs::create_dir_all(parent)
81                        .map_err(|_| Error::CannotCreateContractDir(out_file.clone()))?;
82                }
83            }
84            fs::write(out_file, bytes)
85                .map_err(|io| Error::CannotWriteContractFile(out_file.clone(), io))
86        } else {
87            let stdout = std::io::stdout();
88            let mut handle = stdout.lock();
89            handle.write_all(&bytes)?;
90            handle.flush()?;
91            Ok(())
92        }
93    }
94
95    pub async fn get_bytes(&self) -> Result<Vec<u8>, Error> {
96        self.run_against_rpc_server(None, None).await
97    }
98
99    pub fn network(&self) -> Result<Network, Error> {
100        Ok(self.network.get(&self.locator)?)
101    }
102}
103
104#[async_trait::async_trait]
105impl NetworkRunnable for Cmd {
106    type Error = Error;
107    type Result = Vec<u8>;
108    async fn run_against_rpc_server(
109        &self,
110        _args: Option<&global::Args>,
111        config: Option<&config::Args>,
112    ) -> Result<Vec<u8>, Error> {
113        let network = config.map_or_else(|| self.network(), |c| Ok(c.get_network()?))?;
114        Ok(wasm::fetch_from_contract(
115            &self
116                .contract_id
117                .resolve_contract_id(&self.locator, &network.network_passphrase)?,
118            &network,
119        )
120        .await?)
121    }
122}