use crate::chaos_utilities::ChaosConfig;
use crate::contract_drift::DriftBudgetConfig;
use crate::reality::RealityConfig;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Hash)]
#[serde(rename_all = "lowercase")]
pub enum MockEnvironmentName {
Dev,
Test,
Prod,
}
impl MockEnvironmentName {
pub fn as_str(&self) -> &'static str {
match self {
MockEnvironmentName::Dev => "dev",
MockEnvironmentName::Test => "test",
MockEnvironmentName::Prod => "prod",
}
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"dev" => Some(MockEnvironmentName::Dev),
"test" => Some(MockEnvironmentName::Test),
"prod" => Some(MockEnvironmentName::Prod),
_ => None,
}
}
pub fn promotion_order() -> Vec<Self> {
vec![
MockEnvironmentName::Dev,
MockEnvironmentName::Test,
MockEnvironmentName::Prod,
]
}
pub fn next(&self) -> Option<Self> {
match self {
MockEnvironmentName::Dev => Some(MockEnvironmentName::Test),
MockEnvironmentName::Test => Some(MockEnvironmentName::Prod),
MockEnvironmentName::Prod => None,
}
}
pub fn previous(&self) -> Option<Self> {
match self {
MockEnvironmentName::Dev => None,
MockEnvironmentName::Test => Some(MockEnvironmentName::Dev),
MockEnvironmentName::Prod => Some(MockEnvironmentName::Test),
}
}
}
impl std::fmt::Display for MockEnvironmentName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MockEnvironment {
pub id: String,
pub workspace_id: String,
pub name: MockEnvironmentName,
#[serde(skip_serializing_if = "Option::is_none")]
pub reality_config: Option<RealityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaos_config: Option<ChaosConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub drift_budget_config: Option<DriftBudgetConfig>,
}
impl MockEnvironment {
pub fn new(workspace_id: String, name: MockEnvironmentName) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
workspace_id,
name,
reality_config: None,
chaos_config: None,
drift_budget_config: None,
}
}
pub fn with_configs(
workspace_id: String,
name: MockEnvironmentName,
reality_config: Option<RealityConfig>,
chaos_config: Option<ChaosConfig>,
drift_budget_config: Option<DriftBudgetConfig>,
) -> Self {
Self {
id: uuid::Uuid::new_v4().to_string(),
workspace_id,
name,
reality_config,
chaos_config,
drift_budget_config,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MockEnvironmentManager {
pub workspace_id: String,
pub environments: HashMap<MockEnvironmentName, MockEnvironment>,
pub active_environment: Option<MockEnvironmentName>,
}
impl MockEnvironmentManager {
pub fn new(workspace_id: String) -> Self {
Self {
workspace_id,
environments: HashMap::new(),
active_environment: None,
}
}
pub fn add_environment(&mut self, environment: MockEnvironment) {
self.environments.insert(environment.name, environment);
}
pub fn get_environment(&self, name: MockEnvironmentName) -> Option<&MockEnvironment> {
self.environments.get(&name)
}
pub fn get_active_environment(&self) -> Option<&MockEnvironment> {
self.active_environment.and_then(|name| self.environments.get(&name))
}
pub fn set_active_environment(&mut self, name: MockEnvironmentName) -> Result<(), String> {
if self.environments.contains_key(&name) {
self.active_environment = Some(name);
Ok(())
} else {
Err(format!("Environment '{}' not found", name.as_str()))
}
}
pub fn list_environments(&self) -> Vec<&MockEnvironment> {
let order = MockEnvironmentName::promotion_order();
let mut result: Vec<&MockEnvironment> =
order.iter().filter_map(|name| self.environments.get(name)).collect();
for (name, env) in &self.environments {
if !order.contains(name) {
result.push(env);
}
}
result
}
pub fn remove_environment(&mut self, name: MockEnvironmentName) -> Option<MockEnvironment> {
if self.active_environment == Some(name) {
self.active_environment = None;
}
self.environments.remove(&name)
}
}
impl Default for MockEnvironmentManager {
fn default() -> Self {
Self::new(String::new())
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MockEnvironmentConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub reality_config: Option<RealityConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub chaos_config: Option<ChaosConfig>,
#[serde(skip_serializing_if = "Option::is_none")]
pub drift_budget_config: Option<DriftBudgetConfig>,
}
pub struct EnvironmentConfigResolver;
impl EnvironmentConfigResolver {
pub fn resolve_reality_config(
workspace_default: Option<RealityConfig>,
environment_override: Option<RealityConfig>,
) -> Option<RealityConfig> {
environment_override.or(workspace_default)
}
pub fn resolve_chaos_config(
workspace_default: Option<ChaosConfig>,
environment_override: Option<ChaosConfig>,
) -> Option<ChaosConfig> {
environment_override.or(workspace_default)
}
pub fn resolve_drift_budget_config(
workspace_default: Option<DriftBudgetConfig>,
environment_override: Option<DriftBudgetConfig>,
) -> Option<DriftBudgetConfig> {
environment_override.or(workspace_default)
}
pub fn resolve_all_configs(
workspace_defaults: &WorkspaceEnvironmentDefaults,
environment_config: &MockEnvironmentConfig,
) -> ResolvedEnvironmentConfig {
ResolvedEnvironmentConfig {
reality_config: Self::resolve_reality_config(
workspace_defaults.reality_config.clone(),
environment_config.reality_config.clone(),
),
chaos_config: Self::resolve_chaos_config(
workspace_defaults.chaos_config.clone(),
environment_config.chaos_config.clone(),
),
drift_budget_config: Self::resolve_drift_budget_config(
workspace_defaults.drift_budget_config.clone(),
environment_config.drift_budget_config.clone(),
),
}
}
pub fn from_jsonb(value: &Value) -> Result<MockEnvironmentConfig, serde_json::Error> {
serde_json::from_value(value.clone())
}
pub fn to_jsonb(config: &MockEnvironmentConfig) -> Value {
serde_json::to_value(config).unwrap_or_else(|_| serde_json::json!({}))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WorkspaceEnvironmentDefaults {
pub reality_config: Option<RealityConfig>,
pub chaos_config: Option<ChaosConfig>,
pub drift_budget_config: Option<DriftBudgetConfig>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResolvedEnvironmentConfig {
pub reality_config: Option<RealityConfig>,
pub chaos_config: Option<ChaosConfig>,
pub drift_budget_config: Option<DriftBudgetConfig>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_environment_name_parsing() {
assert_eq!(MockEnvironmentName::from_str("dev"), Some(MockEnvironmentName::Dev));
assert_eq!(MockEnvironmentName::from_str("test"), Some(MockEnvironmentName::Test));
assert_eq!(MockEnvironmentName::from_str("prod"), Some(MockEnvironmentName::Prod));
assert_eq!(MockEnvironmentName::from_str("invalid"), None);
}
#[test]
fn test_promotion_order() {
let order = MockEnvironmentName::promotion_order();
assert_eq!(order.len(), 3);
assert_eq!(order[0], MockEnvironmentName::Dev);
assert_eq!(order[1], MockEnvironmentName::Test);
assert_eq!(order[2], MockEnvironmentName::Prod);
}
#[test]
fn test_next_environment() {
assert_eq!(MockEnvironmentName::Dev.next(), Some(MockEnvironmentName::Test));
assert_eq!(MockEnvironmentName::Test.next(), Some(MockEnvironmentName::Prod));
assert_eq!(MockEnvironmentName::Prod.next(), None);
}
#[test]
fn test_previous_environment() {
assert_eq!(MockEnvironmentName::Dev.previous(), None);
assert_eq!(MockEnvironmentName::Test.previous(), Some(MockEnvironmentName::Dev));
assert_eq!(MockEnvironmentName::Prod.previous(), Some(MockEnvironmentName::Test));
}
}