use crate::bundle_deployment::BundleDeployment;
use crate::capability_slot::{CapabilitySlot, PackDescriptor};
use crate::error::SpecError;
use crate::ids::PackId;
use crate::refs::SecretRef;
use crate::retention::{HealthStatus, RetentionPolicy, RevocationConfig};
use crate::revision::Revision;
use crate::traffic_split::TrafficSplit;
use crate::version::SchemaVersion;
use greentic_types::EnvId;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnvironmentHostConfig {
pub env_id: EnvId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub region: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tenant_org_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EnvPackBinding {
pub slot: CapabilitySlot,
pub kind: PackDescriptor,
pub pack_ref: PackId,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub answers_ref: Option<PathBuf>,
#[serde(default)]
pub generation: u64,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub previous_binding_ref: Option<PathBuf>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Environment {
pub schema: SchemaVersion,
pub environment_id: EnvId,
pub name: String,
pub host_config: EnvironmentHostConfig,
pub packs: Vec<EnvPackBinding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub credentials_ref: Option<SecretRef>,
#[serde(default)]
pub bundles: Vec<BundleDeployment>,
#[serde(default)]
pub revisions: Vec<Revision>,
#[serde(default)]
pub traffic_splits: Vec<TrafficSplit>,
#[serde(default)]
pub revocation: RevocationConfig,
#[serde(default)]
pub retention: RetentionPolicy,
#[serde(default)]
pub health: HealthStatus,
}
impl Environment {
pub fn schema_str() -> &'static str {
SchemaVersion::ENVIRONMENT_V1
}
pub fn pack_for_slot(&self, slot: CapabilitySlot) -> Option<&EnvPackBinding> {
self.packs.iter().find(|b| b.slot == slot)
}
pub fn validate(&self) -> Result<(), SpecError> {
if self.schema.as_str() != SchemaVersion::ENVIRONMENT_V1 {
return Err(SpecError::SchemaMismatch {
expected: SchemaVersion::ENVIRONMENT_V1,
actual: self.schema.as_str().to_string(),
});
}
if self.host_config.env_id != self.environment_id {
return Err(SpecError::EnvIdMismatch {
context: "host_config",
expected: self.environment_id.clone(),
actual: self.host_config.env_id.clone(),
});
}
let mut seen = [false; 6];
for binding in &self.packs {
let idx = binding.slot as usize;
if seen[idx] {
return Err(SpecError::DuplicateCapabilitySlot(binding.slot));
}
seen[idx] = true;
}
for revision in &self.revisions {
if revision.env_id != self.environment_id {
return Err(SpecError::EnvIdMismatch {
context: "revision",
expected: self.environment_id.clone(),
actual: revision.env_id.clone(),
});
}
}
for bundle in &self.bundles {
if bundle.env_id != self.environment_id {
return Err(SpecError::EnvIdMismatch {
context: "bundle_deployment",
expected: self.environment_id.clone(),
actual: bundle.env_id.clone(),
});
}
bundle.validate()?;
for rev_id in &bundle.current_revisions {
let referenced = self
.revisions
.iter()
.find(|r| r.revision_id == *rev_id)
.ok_or(SpecError::UnknownRevision(*rev_id))?;
if referenced.deployment_id != bundle.deployment_id {
return Err(SpecError::BundleRevisionWrongDeployment {
deployment: bundle.deployment_id,
revision: *rev_id,
actual_deployment: referenced.deployment_id,
});
}
}
}
for split in &self.traffic_splits {
if split.env_id != self.environment_id {
return Err(SpecError::EnvIdMismatch {
context: "traffic_split",
expected: self.environment_id.clone(),
actual: split.env_id.clone(),
});
}
split.validate()?;
if !self
.bundles
.iter()
.any(|b| b.deployment_id == split.deployment_id)
{
return Err(SpecError::UnknownDeployment(split.deployment_id));
}
for entry in &split.entries {
let referenced = self
.revisions
.iter()
.find(|r| r.revision_id == entry.revision_id)
.ok_or(SpecError::UnknownRevision(entry.revision_id))?;
if referenced.deployment_id != split.deployment_id {
return Err(SpecError::SplitRevisionWrongDeployment {
revision: entry.revision_id,
expected_deployment: split.deployment_id,
actual_deployment: referenced.deployment_id,
});
}
if referenced.bundle_id != split.bundle_id {
return Err(SpecError::SplitRevisionWrongBundle {
revision: entry.revision_id,
expected_bundle: split.bundle_id.clone(),
actual_bundle: referenced.bundle_id.clone(),
});
}
}
}
Ok(())
}
}