1use std::{borrow::Cow, env, fmt, fmt::Display, str::FromStr};
2
3use anyhow::{Context, anyhow, ensure};
4use lexe_std::Apply;
5#[cfg(any(test, feature = "test-utils"))]
6use proptest::strategy::Strategy;
7#[cfg(any(test, feature = "test-utils"))]
8use proptest_derive::Arbitrary;
9use serde::Serialize;
10use serde_with::DeserializeFromStr;
11use strum::VariantArray;
12
13use crate::ln::network::Network;
14
15#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
17#[derive(DeserializeFromStr, VariantArray)]
18#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
19pub enum DeployEnv {
20 Dev,
22 Staging,
24 Prod,
26}
27
28impl DeployEnv {
29 pub fn from_env() -> anyhow::Result<Self> {
31 env::var("DEPLOY_ENVIRONMENT")
32 .context("DEPLOY_ENVIRONMENT was not set")?
33 .as_str()
34 .apply(Self::from_str)
35 }
36
37 #[inline]
39 pub fn is_dev(self) -> bool {
40 matches!(self, Self::Dev)
41 }
42
43 #[inline]
45 pub fn is_staging_or_prod(self) -> bool {
46 matches!(self, Self::Staging | Self::Prod)
47 }
48
49 #[inline]
51 pub fn as_str(&self) -> &'static str {
52 match self {
53 Self::Dev => "dev",
54 Self::Staging => "staging",
55 Self::Prod => "prod",
56 }
57 }
58
59 pub fn validate_network(&self, network: Network) -> anyhow::Result<()> {
61 use Network::*;
62 match self {
63 Self::Dev => ensure!(
64 matches!(network, Regtest | Testnet3 | Testnet4),
65 "Dev environment can only be regtest or testnet!"
66 ),
67 Self::Staging => ensure!(
68 matches!(network, Testnet3 | Testnet4),
69 "Staging environment can only be testnet!"
70 ),
71 Self::Prod => ensure!(
72 matches!(network, Mainnet),
73 "Prod environment can only be mainnet!"
74 ),
75 }
76 Ok(())
77 }
78
79 pub fn validate_sgx(&self, use_sgx: bool) -> anyhow::Result<()> {
81 if matches!(self, Self::Staging | Self::Prod) {
82 ensure!(use_sgx, "Staging and prod can only run in SGX!");
83 }
84 Ok(())
85 }
86
87 pub fn gateway_url(
91 &self,
92 dev_gateway_url: Option<Cow<'static, str>>,
93 ) -> Cow<'static, str> {
94 match self {
95 Self::Dev => dev_gateway_url
96 .unwrap_or(Cow::Borrowed("https://localhost:4040")),
97 Self::Staging => Cow::Borrowed("https://gateway.staging.lexe.app"),
98 Self::Prod => Cow::Borrowed("https://gateway.lexe.app"),
99 }
100 }
101
102 #[cfg(any(test, feature = "test-utils"))]
104 pub fn any_valid_network_combo()
105 -> impl Strategy<Value = (DeployEnv, Network)> {
106 use proptest::strategy::Just;
107 proptest::prop_oneof![
112 Just((Self::Dev, Network::Regtest)),
113 Just((Self::Dev, Network::Testnet3)),
114 Just((Self::Dev, Network::Testnet4)),
115 Just((Self::Staging, Network::Testnet3)),
116 Just((Self::Staging, Network::Testnet4)),
117 Just((Self::Prod, Network::Mainnet)),
118 ]
119 }
120}
121
122impl FromStr for DeployEnv {
123 type Err = anyhow::Error;
124
125 fn from_str(s: &str) -> anyhow::Result<Self> {
126 match s {
127 "dev" => Ok(Self::Dev),
128 "staging" => Ok(Self::Staging),
129 "prod" => Ok(Self::Prod),
130 _ => Err(anyhow!(
131 "Unrecognized DEPLOY_ENVIRONMENT '{s}': \
132 must be 'dev', 'staging', or 'prod'"
133 )),
134 }
135 }
136}
137
138impl Display for DeployEnv {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 f.write_str(self.as_str())
141 }
142}
143
144impl Serialize for DeployEnv {
145 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
146 where
147 S: serde::Serializer,
148 {
149 self.as_str().serialize(serializer)
150 }
151}
152
153#[cfg(test)]
154mod test {
155 use proptest::{prop_assert, proptest};
156
157 use super::*;
158 use crate::test_utils::roundtrip;
159
160 #[test]
161 fn deploy_env_roundtrip() {
162 let expected_ser = r#"["dev","staging","prod"]"#;
163 roundtrip::json_unit_enum_backwards_compat::<DeployEnv>(expected_ser);
164 roundtrip::fromstr_display_roundtrip_proptest::<DeployEnv>();
165 }
166
167 #[test]
168 fn test_any_valid_network_combo() {
169 proptest!(|(
170 (deploy_env, network) in DeployEnv::any_valid_network_combo(),
171 )| {
172 prop_assert!(deploy_env.validate_network(network).is_ok());
173 })
174 }
175}