Skip to main content

lexe_common/
env.rs

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/// Represents a validated `DEPLOY_ENVIRONMENT` configuration.
16#[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"
21    Dev,
22    /// "staging"
23    Staging,
24    /// "prod"
25    Prod,
26}
27
28impl DeployEnv {
29    /// Read a [`DeployEnv`] from env, or err if it was invalid / didn't exist.
30    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    /// Shorthand to check whether this [`DeployEnv`] is dev.
38    #[inline]
39    pub fn is_dev(self) -> bool {
40        matches!(self, Self::Dev)
41    }
42
43    /// Shorthand to check whether this [`DeployEnv`] is staging or prod.
44    #[inline]
45    pub fn is_staging_or_prod(self) -> bool {
46        matches!(self, Self::Staging | Self::Prod)
47    }
48
49    /// Get a [`str`] containing "dev", "staging", or "prod"
50    #[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    /// Validate the [`Network`] parameter for this deploy environment.
60    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    /// Validate the `SGX`/`[use_]sgx` parameter for this deploy environment.
80    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    /// Returns the gateway URL for this deploy environment.
88    ///
89    /// A custom gateway URL (e.g. from env) can be provided for dev.
90    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    /// A strategy for *valid* combinations of [`DeployEnv`] and [`Network`].
103    #[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        // We *could* extract an associated const [(DeployEnv, Network); N]
108        // enumerating all *valid* combos, then iterate over all *possible*
109        // combos to test that `validate_network` is correct, but this
110        // boilerplate adds very little value.
111        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}