loam_cli/commands/build/
env_toml.rs

1use indexmap::IndexMap;
2use serde::Deserialize;
3use std::collections::BTreeMap as Map;
4use std::path::Path;
5use toml::value::Table;
6
7pub const ENV_FILE: &str = "environments.toml";
8
9#[derive(thiserror::Error, Debug)]
10pub enum Error {
11    #[error("⛔ ️parsing environments.toml: {0}")]
12    ParsingToml(#[from] toml::de::Error),
13    #[error("⛔ ️no settings for current LOAM_ENV ({0:?}) found in environments.toml")]
14    NoSettingsForCurrentEnv(String),
15    #[error("⛔ ️reading environments.toml as a string: {0}")]
16    ParsingString(#[from] std::io::Error),
17}
18
19type Environments = Map<Box<str>, Environment>;
20
21#[derive(Debug, Clone)]
22pub struct Environment {
23    pub accounts: Option<Vec<Account>>,
24    pub network: Network,
25    pub contracts: Option<IndexMap<Box<str>, Contract>>,
26}
27
28fn deserialize_accounts<'de, D>(deserializer: D) -> Result<Option<Vec<Account>>, D::Error>
29where
30    D: serde::Deserializer<'de>,
31{
32    let opt: Option<Vec<AccountRepresentation>> = Option::deserialize(deserializer)?;
33    Ok(opt.map(|vec| vec.into_iter().map(Account::from).collect()))
34}
35
36impl<'de> Deserialize<'de> for Environment {
37    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38    where
39        D: serde::Deserializer<'de>,
40    {
41        #[derive(Deserialize)]
42        struct EnvironmentHelper {
43            #[serde(default, deserialize_with = "deserialize_accounts")]
44            accounts: Option<Vec<Account>>,
45            network: Network,
46            contracts: Option<Table>,
47        }
48
49        let helper = EnvironmentHelper::deserialize(deserializer)?;
50
51        let contracts = helper
52            .contracts
53            .map(|contracts_table| {
54                contracts_table
55                    .into_iter()
56                    .map(|(key, value)| {
57                        let contract: Contract =
58                            Contract::deserialize(value).map_err(serde::de::Error::custom)?;
59                        Ok((key.into_boxed_str(), contract))
60                    })
61                    .collect::<Result<IndexMap<_, _>, D::Error>>()
62            })
63            .transpose()?;
64
65        Ok(Environment {
66            accounts: helper.accounts,
67            network: helper.network,
68            contracts,
69        })
70    }
71}
72
73#[derive(Debug, serde::Deserialize, Clone)]
74#[serde(rename_all = "kebab-case")]
75pub struct Network {
76    pub name: Option<String>,
77    pub rpc_url: Option<String>,
78    pub network_passphrase: Option<String>,
79    pub rpc_headers: Option<Vec<(String, String)>>,
80    #[serde(skip_serializing_if = "is_false", default)]
81    pub run_locally: bool,
82}
83
84#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
85#[serde(untagged)]
86pub enum AccountRepresentation {
87    Simple(String),
88    Detailed(Account),
89}
90
91#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
92pub struct Account {
93    pub name: String,
94    #[serde(default)]
95    pub default: bool,
96}
97
98impl From<AccountRepresentation> for Account {
99    fn from(rep: AccountRepresentation) -> Self {
100        match rep {
101            AccountRepresentation::Simple(name) => Account {
102                name,
103                default: false,
104            },
105            AccountRepresentation::Detailed(account) => account,
106        }
107    }
108}
109
110#[derive(Debug, Deserialize, Clone, Default)]
111pub struct Contract {
112    #[serde(default = "default_client", skip_serializing_if = "std::ops::Not::not")]
113    pub client: bool,
114
115    #[serde(default, skip_serializing_if = "Option::is_none")]
116    pub init: Option<String>,
117
118    #[serde(default, skip_serializing_if = "Option::is_none")]
119    pub id: Option<String>,
120}
121
122fn default_client() -> bool {
123    true
124}
125
126impl Environment {
127    pub fn get(workspace_root: &Path, loam_env: &str) -> Result<Option<Environment>, Error> {
128        let env_toml = workspace_root.join(ENV_FILE);
129
130        if !env_toml.exists() {
131            return Ok(None);
132        }
133
134        let toml_str = std::fs::read_to_string(env_toml)?;
135        let mut parsed_toml: Environments = toml::from_str(&toml_str)?;
136        let current_env = parsed_toml.remove(loam_env);
137        if current_env.is_none() {
138            return Err(Error::NoSettingsForCurrentEnv(loam_env.to_string()));
139        };
140        Ok(current_env)
141    }
142}