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.
44    #[inline]
45    pub fn is_staging(self) -> bool {
46        matches!(self, Self::Staging)
47    }
48
49    /// Shorthand to check whether this [`DeployEnv`] is prod.
50    #[inline]
51    pub fn is_prod(self) -> bool {
52        matches!(self, Self::Prod)
53    }
54
55    /// Shorthand to check whether this [`DeployEnv`] is staging or prod.
56    #[inline]
57    pub fn is_staging_or_prod(self) -> bool {
58        matches!(self, Self::Staging | Self::Prod)
59    }
60
61    /// Get a [`str`] containing "dev", "staging", or "prod"
62    #[inline]
63    pub fn as_str(&self) -> &'static str {
64        match self {
65            Self::Dev => "dev",
66            Self::Staging => "staging",
67            Self::Prod => "prod",
68        }
69    }
70
71    /// Validate the [`Network`] parameter for this deploy environment.
72    pub fn validate_network(&self, network: Network) -> anyhow::Result<()> {
73        use Network::*;
74        match self {
75            Self::Dev => ensure!(
76                matches!(network, Regtest | Testnet3 | Testnet4),
77                "Dev environment can only be regtest or testnet!"
78            ),
79            Self::Staging => ensure!(
80                matches!(network, Testnet3 | Testnet4),
81                "Staging environment can only be testnet!"
82            ),
83            Self::Prod => ensure!(
84                matches!(network, Mainnet),
85                "Prod environment can only be mainnet!"
86            ),
87        }
88        Ok(())
89    }
90
91    /// Validate the `SGX`/`[use_]sgx` parameter for this deploy environment.
92    pub fn validate_sgx(&self, use_sgx: bool) -> anyhow::Result<()> {
93        if matches!(self, Self::Staging | Self::Prod) {
94            ensure!(use_sgx, "Staging and prod can only run in SGX!");
95        }
96        Ok(())
97    }
98
99    /// Returns the gateway URL for this deploy environment.
100    ///
101    /// A custom gateway URL (e.g. from env) can be provided for dev.
102    pub fn gateway_url(
103        &self,
104        dev_gateway_url: Option<Cow<'static, str>>,
105    ) -> Cow<'static, str> {
106        match self {
107            Self::Dev => dev_gateway_url
108                .unwrap_or(Cow::Borrowed("https://localhost:4040")),
109            Self::Staging => Cow::Borrowed("https://gateway.staging.lexe.app"),
110            Self::Prod => Cow::Borrowed("https://gateway.lexe.app"),
111        }
112    }
113
114    /// A strategy for *valid* combinations of [`DeployEnv`] and [`Network`].
115    #[cfg(any(test, feature = "test-utils"))]
116    pub fn any_valid_network_combo()
117    -> impl Strategy<Value = (DeployEnv, Network)> {
118        use proptest::strategy::Just;
119        // We *could* extract an associated const [(DeployEnv, Network); N]
120        // enumerating all *valid* combos, then iterate over all *possible*
121        // combos to test that `validate_network` is correct, but this
122        // boilerplate adds very little value.
123        proptest::prop_oneof![
124            Just((Self::Dev, Network::Regtest)),
125            Just((Self::Dev, Network::Testnet3)),
126            Just((Self::Dev, Network::Testnet4)),
127            Just((Self::Staging, Network::Testnet3)),
128            Just((Self::Staging, Network::Testnet4)),
129            Just((Self::Prod, Network::Mainnet)),
130        ]
131    }
132}
133
134impl FromStr for DeployEnv {
135    type Err = anyhow::Error;
136
137    fn from_str(s: &str) -> anyhow::Result<Self> {
138        match s {
139            "dev" => Ok(Self::Dev),
140            "staging" => Ok(Self::Staging),
141            "prod" => Ok(Self::Prod),
142            _ => Err(anyhow!(
143                "Unrecognized DEPLOY_ENVIRONMENT '{s}': \
144                must be 'dev', 'staging', or 'prod'"
145            )),
146        }
147    }
148}
149
150impl Display for DeployEnv {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.write_str(self.as_str())
153    }
154}
155
156impl Serialize for DeployEnv {
157    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
158    where
159        S: serde::Serializer,
160    {
161        self.as_str().serialize(serializer)
162    }
163}
164
165#[cfg(test)]
166mod test {
167    use proptest::{prop_assert, proptest};
168
169    use super::*;
170    use crate::test_utils::roundtrip;
171
172    #[test]
173    fn deploy_env_roundtrip() {
174        let expected_ser = r#"["dev","staging","prod"]"#;
175        roundtrip::json_unit_enum_backwards_compat::<DeployEnv>(expected_ser);
176        roundtrip::fromstr_display_roundtrip_proptest::<DeployEnv>();
177    }
178
179    #[test]
180    fn test_any_valid_network_combo() {
181        proptest!(|(
182            (deploy_env, network) in DeployEnv::any_valid_network_combo(),
183        )| {
184            prop_assert!(deploy_env.validate_network(network).is_ok());
185        })
186    }
187}