greentic_deploy_spec/
traffic_split.rs1use crate::error::SpecError;
6use crate::ids::{BundleId, DeploymentId, RevisionId};
7use crate::version::SchemaVersion;
8use chrono::{DateTime, Utc};
9use greentic_types::EnvId;
10use serde::{Deserialize, Serialize};
11use std::path::PathBuf;
12
13const BASIS_POINTS_TOTAL: u32 = 10_000;
14
15#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
16pub struct TrafficSplitEntry {
17 pub revision_id: RevisionId,
18 pub weight_bps: u32,
19}
20
21#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
22pub struct TrafficSplit {
23 pub schema: SchemaVersion,
24 pub env_id: EnvId,
25 pub deployment_id: DeploymentId,
26 pub bundle_id: BundleId,
27 pub generation: u64,
28 pub entries: Vec<TrafficSplitEntry>,
29 pub updated_at: DateTime<Utc>,
30 pub updated_by: String,
31 pub idempotency_key: String,
32 pub authorization_ref: PathBuf,
33 #[serde(default, skip_serializing_if = "Option::is_none")]
34 pub previous_split_ref: Option<PathBuf>,
35}
36
37impl TrafficSplit {
38 pub fn schema_str() -> &'static str {
39 SchemaVersion::TRAFFIC_SPLIT_V1
40 }
41
42 pub fn validate(&self) -> Result<(), SpecError> {
49 if self.schema.as_str() != SchemaVersion::TRAFFIC_SPLIT_V1 {
50 return Err(SpecError::SchemaMismatch {
51 expected: SchemaVersion::TRAFFIC_SPLIT_V1,
52 actual: self.schema.as_str().to_string(),
53 });
54 }
55 let mut sum: u64 = 0;
56 for entry in &self.entries {
57 if entry.weight_bps > BASIS_POINTS_TOTAL {
58 return Err(SpecError::BasisPointsEntryTooLarge {
59 value: entry.weight_bps,
60 });
61 }
62 sum += u64::from(entry.weight_bps);
63 }
64 if sum != u64::from(BASIS_POINTS_TOTAL) {
65 return Err(SpecError::BasisPointsSum { sum });
66 }
67 Ok(())
68 }
69}