use std::{borrow::Cow, env, fmt, fmt::Display, str::FromStr};
use anyhow::{Context, anyhow, ensure};
use lexe_std::Apply;
#[cfg(any(test, feature = "test-utils"))]
use proptest::strategy::Strategy;
#[cfg(any(test, feature = "test-utils"))]
use proptest_derive::Arbitrary;
use serde::Serialize;
use serde_with::DeserializeFromStr;
use strum::VariantArray;
use crate::ln::network::Network;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[derive(DeserializeFromStr, VariantArray)]
#[cfg_attr(any(test, feature = "test-utils"), derive(Arbitrary))]
pub enum DeployEnv {
Dev,
Staging,
Prod,
}
impl DeployEnv {
pub fn from_env() -> anyhow::Result<Self> {
env::var("DEPLOY_ENVIRONMENT")
.context("DEPLOY_ENVIRONMENT was not set")?
.as_str()
.apply(Self::from_str)
}
#[inline]
pub fn is_dev(self) -> bool {
matches!(self, Self::Dev)
}
#[inline]
pub fn is_staging_or_prod(self) -> bool {
matches!(self, Self::Staging | Self::Prod)
}
#[inline]
pub fn as_str(&self) -> &'static str {
match self {
Self::Dev => "dev",
Self::Staging => "staging",
Self::Prod => "prod",
}
}
pub fn validate_network(&self, network: Network) -> anyhow::Result<()> {
use Network::*;
match self {
Self::Dev => ensure!(
matches!(network, Regtest | Testnet3 | Testnet4),
"Dev environment can only be regtest or testnet!"
),
Self::Staging => ensure!(
matches!(network, Testnet3 | Testnet4),
"Staging environment can only be testnet!"
),
Self::Prod => ensure!(
matches!(network, Mainnet),
"Prod environment can only be mainnet!"
),
}
Ok(())
}
pub fn validate_sgx(&self, use_sgx: bool) -> anyhow::Result<()> {
if matches!(self, Self::Staging | Self::Prod) {
ensure!(use_sgx, "Staging and prod can only run in SGX!");
}
Ok(())
}
pub fn gateway_url(
&self,
dev_gateway_url: Option<Cow<'static, str>>,
) -> Cow<'static, str> {
match self {
Self::Dev => dev_gateway_url
.unwrap_or(Cow::Borrowed("https://localhost:4040")),
Self::Staging => Cow::Borrowed("https://gateway.staging.lexe.app"),
Self::Prod => Cow::Borrowed("https://gateway.lexe.app"),
}
}
#[cfg(any(test, feature = "test-utils"))]
pub fn any_valid_network_combo()
-> impl Strategy<Value = (DeployEnv, Network)> {
use proptest::strategy::Just;
proptest::prop_oneof![
Just((Self::Dev, Network::Regtest)),
Just((Self::Dev, Network::Testnet3)),
Just((Self::Dev, Network::Testnet4)),
Just((Self::Staging, Network::Testnet3)),
Just((Self::Staging, Network::Testnet4)),
Just((Self::Prod, Network::Mainnet)),
]
}
}
impl FromStr for DeployEnv {
type Err = anyhow::Error;
fn from_str(s: &str) -> anyhow::Result<Self> {
match s {
"dev" => Ok(Self::Dev),
"staging" => Ok(Self::Staging),
"prod" => Ok(Self::Prod),
_ => Err(anyhow!(
"Unrecognized DEPLOY_ENVIRONMENT '{s}': \
must be 'dev', 'staging', or 'prod'"
)),
}
}
}
impl Display for DeployEnv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl Serialize for DeployEnv {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
self.as_str().serialize(serializer)
}
}
#[cfg(test)]
mod test {
use proptest::{prop_assert, proptest};
use super::*;
use crate::test_utils::roundtrip;
#[test]
fn deploy_env_roundtrip() {
let expected_ser = r#"["dev","staging","prod"]"#;
roundtrip::json_unit_enum_backwards_compat::<DeployEnv>(expected_ser);
roundtrip::fromstr_display_roundtrip_proptest::<DeployEnv>();
}
#[test]
fn test_any_valid_network_combo() {
proptest!(|(
(deploy_env, network) in DeployEnv::any_valid_network_combo(),
)| {
prop_assert!(deploy_env.validate_network(network).is_ok());
})
}
}