#[cfg(feature = "infra")]
mod inner {
pub use forjar::*;
}
#[cfg(feature = "infra")]
pub use inner::*;
#[derive(Debug, Clone, PartialEq)]
pub struct MachineSpec {
pub name: String,
pub machine_type: String,
pub region: Option<String>,
pub cpus: u32,
pub memory_mb: u64,
pub disk_gb: u64,
}
impl MachineSpec {
pub fn new(name: &str, machine_type: &str) -> Self {
Self {
name: name.to_string(),
machine_type: machine_type.to_string(),
region: None,
cpus: 1,
memory_mb: 512,
disk_gb: 10,
}
}
pub fn with_region(mut self, region: &str) -> Self {
self.region = Some(region.to_string());
self
}
pub fn with_cpus(mut self, cpus: u32) -> Self {
self.cpus = cpus;
self
}
pub fn with_memory_mb(mut self, mb: u64) -> Self {
self.memory_mb = mb;
self
}
pub fn with_disk_gb(mut self, gb: u64) -> Self {
self.disk_gb = gb;
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct NetworkSpec {
pub name: String,
pub cidr: String,
pub public: bool,
}
impl NetworkSpec {
pub fn new(name: &str, cidr: &str) -> Self {
Self {
name: name.to_string(),
cidr: cidr.to_string(),
public: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ResourceChange {
Create(String),
Update { name: String, detail: String },
Destroy(String),
NoOp(String),
}
#[derive(Debug, Clone)]
pub struct InfraPlan {
pub changes: Vec<ResourceChange>,
}
impl InfraPlan {
pub fn empty() -> Self {
Self {
changes: Vec::new(),
}
}
pub fn creates(&self) -> usize {
self.changes
.iter()
.filter(|c| matches!(c, ResourceChange::Create(_)))
.count()
}
pub fn updates(&self) -> usize {
self.changes
.iter()
.filter(|c| matches!(c, ResourceChange::Update { .. }))
.count()
}
pub fn destroys(&self) -> usize {
self.changes
.iter()
.filter(|c| matches!(c, ResourceChange::Destroy(_)))
.count()
}
pub fn has_changes(&self) -> bool {
self.changes
.iter()
.any(|c| !matches!(c, ResourceChange::NoOp(_)))
}
pub fn summary(&self) -> String {
format!(
"Plan: {} to create, {} to update, {} to destroy",
self.creates(),
self.updates(),
self.destroys()
)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct InfraState {
pub resources: Vec<(String, String)>,
}
impl InfraState {
pub fn empty() -> Self {
Self {
resources: Vec::new(),
}
}
pub fn resource_count(&self) -> usize {
self.resources.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_machine_spec_new() {
let spec = MachineSpec::new("web-1", "web");
assert_eq!(spec.name, "web-1");
assert_eq!(spec.machine_type, "web");
assert!(spec.region.is_none());
assert_eq!(spec.cpus, 1);
assert_eq!(spec.memory_mb, 512);
}
#[test]
fn test_machine_spec_builder() {
let spec = MachineSpec::new("db-1", "db")
.with_region("us-east-1")
.with_cpus(4)
.with_memory_mb(8192)
.with_disk_gb(500);
assert_eq!(spec.region, Some("us-east-1".to_string()));
assert_eq!(spec.cpus, 4);
assert_eq!(spec.memory_mb, 8192);
assert_eq!(spec.disk_gb, 500);
}
#[test]
fn test_network_spec_new() {
let net = NetworkSpec::new("vpc-main", "10.0.0.0/16");
assert_eq!(net.name, "vpc-main");
assert_eq!(net.cidr, "10.0.0.0/16");
assert!(!net.public);
}
#[test]
fn test_infra_plan_empty() {
let plan = InfraPlan::empty();
assert_eq!(plan.creates(), 0);
assert_eq!(plan.updates(), 0);
assert_eq!(plan.destroys(), 0);
assert!(!plan.has_changes());
}
#[test]
fn test_infra_plan_with_changes() {
let plan = InfraPlan {
changes: vec![
ResourceChange::Create("web-1".to_string()),
ResourceChange::Create("web-2".to_string()),
ResourceChange::Update {
name: "db-1".to_string(),
detail: "resize 4→8 CPU".to_string(),
},
ResourceChange::Destroy("old-cache".to_string()),
ResourceChange::NoOp("vpc-main".to_string()),
],
};
assert_eq!(plan.creates(), 2);
assert_eq!(plan.updates(), 1);
assert_eq!(plan.destroys(), 1);
assert!(plan.has_changes());
assert_eq!(
plan.summary(),
"Plan: 2 to create, 1 to update, 1 to destroy"
);
}
#[test]
fn test_infra_state_empty() {
let state = InfraState::empty();
assert_eq!(state.resource_count(), 0);
}
#[test]
fn test_infra_state_with_resources() {
let state = InfraState {
resources: vec![
("web-1".to_string(), "abc123".to_string()),
("db-1".to_string(), "def456".to_string()),
],
};
assert_eq!(state.resource_count(), 2);
}
}