use std::sync::Arc;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions},
SqlitePool,
};
use uuid::Uuid;
use crate::{
branch::lifecycle::BranchLifecycle,
config::BranchConfig,
error::BranchResult,
types::{Branch, SimulationOutcome},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SimulationScenario {
pub name: String,
pub description: String,
pub max_ops: Option<u32>,
pub timeout_secs: Option<u64>,
pub seed_data: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SandboxStatus {
Running,
Completed {
outcome: SimulationOutcome,
},
Failed(String),
Abandoned,
}
#[derive(Debug, Clone)]
pub struct SimulationEnvironment {
pub id: Uuid,
pub label: String,
pub branch: Branch,
pub parent_branch_id: Uuid,
pub started_at: DateTime<Utc>,
pub scenario: SimulationScenario,
pub status: SandboxStatus,
}
impl SimulationEnvironment {
pub async fn setup(
parent: &Branch,
scenario: SimulationScenario,
_config: Arc<BranchConfig>,
lifecycle: Arc<BranchLifecycle>,
) -> BranchResult<Self> {
let id = Uuid::new_v4();
let id_short = &id.to_string()[..8];
let branch_name = format!("sandbox/{}/{}", scenario.name, id_short);
let label = branch_name.clone();
let branch = lifecycle
.fork(parent.id, &branch_name, Some(&scenario.description))
.await?;
Ok(Self {
id,
label,
branch,
parent_branch_id: parent.id,
started_at: Utc::now(),
scenario,
status: SandboxStatus::Running,
})
}
pub async fn teardown(&mut self, lifecycle: Arc<BranchLifecycle>) -> BranchResult<()> {
lifecycle.discard(self.branch.id).await?;
self.status = SandboxStatus::Abandoned;
Ok(())
}
pub async fn branch_pool(&self) -> BranchResult<SqlitePool> {
SqlitePoolOptions::new()
.max_connections(4)
.connect_with(
SqliteConnectOptions::new()
.filename(&self.branch.db_path)
.create_if_missing(false)
.journal_mode(SqliteJournalMode::Wal),
)
.await
.map_err(crate::error::BranchError::Database)
}
}