stellar_scaffold_cli/commands/build/
env_toml.rs1use 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 STELLAR_SCAFFOLD_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)]
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 after_deploy: Option<String>,
117
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub id: Option<String>,
120
121 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub constructor_args: Option<String>,
123}
124
125impl Default for Contract {
126 fn default() -> Self {
127 Self {
128 client: default_client(),
129 after_deploy: None,
130 id: None,
131 constructor_args: None,
132 }
133 }
134}
135
136fn default_client() -> bool {
137 true
138}
139
140impl Environment {
141 pub fn get(workspace_root: &Path, scaffold_env: &str) -> Result<Option<Environment>, Error> {
142 let env_toml = workspace_root.join(ENV_FILE);
143
144 if !env_toml.exists() {
145 return Ok(None);
146 }
147
148 let toml_str = std::fs::read_to_string(env_toml)?;
149 let mut parsed_toml: Environments = toml::from_str(&toml_str)?;
150 let current_env = parsed_toml.remove(scaffold_env);
151 if current_env.is_none() {
152 return Err(Error::NoSettingsForCurrentEnv(scaffold_env.to_string()));
153 }
154 Ok(current_env)
155 }
156}
157
158impl From<&Network> for stellar_cli::config::network::Args {
159 fn from(network: &Network) -> Self {
160 stellar_cli::config::network::Args {
161 network: network.name.clone(),
162 rpc_url: network.rpc_url.clone(),
163 network_passphrase: network.network_passphrase.clone(),
164 rpc_headers: network.rpc_headers.clone().unwrap_or_default(),
165 }
166 }
167}