use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "UPPERCASE")]
pub enum ReleaseEnvironment {
Dev,
Staging,
Production,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Release {
pub release_id: Uuid,
pub agent_name: String,
pub spec_digest: String,
pub tools_digest: String,
pub graph_digest: String,
pub version: String,
pub environment: ReleaseEnvironment,
pub promoted_at: DateTime<Utc>,
pub promoted_by: String,
pub metadata: serde_json::Value,
}
impl Release {
pub fn new(
agent_name: String,
spec_digest: String,
tools_digest: String,
graph_digest: String,
version: String,
environment: ReleaseEnvironment,
promoted_by: String,
) -> Self {
Self {
release_id: Uuid::new_v4(),
agent_name,
spec_digest,
tools_digest,
graph_digest,
version,
environment,
promoted_at: Utc::now(),
promoted_by,
metadata: serde_json::json!({}),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ReleasePointer {
pub agent_name: String,
pub current_spec_digest: String,
pub previous_spec_digest: Option<String>,
pub last_updated: DateTime<Utc>,
}
impl ReleasePointer {
pub fn new(agent_name: String, current_spec_digest: String) -> Self {
Self {
agent_name,
current_spec_digest,
previous_spec_digest: None,
last_updated: Utc::now(),
}
}
pub fn promote(&mut self, new_spec_digest: String) {
self.previous_spec_digest = Some(self.current_spec_digest.clone());
self.current_spec_digest = new_spec_digest;
self.last_updated = Utc::now();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_release_serde_roundtrip() {
let release = Release::new(
"my_agent".to_string(),
"spec_digest_123".to_string(),
"tools_digest_abc".to_string(),
"graph_digest_xyz".to_string(),
"1.2.3".to_string(),
ReleaseEnvironment::Production,
"github-ci".to_string(),
);
let json = serde_json::to_string(&release).expect("serialize");
let deserialized: Release = serde_json::from_str(&json).expect("deserialize");
assert_eq!(release, deserialized);
}
#[test]
fn test_release_environment_serde() {
let envs = [
ReleaseEnvironment::Dev,
ReleaseEnvironment::Staging,
ReleaseEnvironment::Production,
];
for env in &envs {
let json = serde_json::to_string(env).expect("serialize");
let deserialized: ReleaseEnvironment =
serde_json::from_str(&json).expect("deserialize");
assert_eq!(*env, deserialized);
}
}
#[test]
fn test_release_pointer_previous_is_none() {
let pointer = ReleasePointer::new("my_agent".to_string(), "spec_digest_abc".to_string());
assert_eq!(pointer.agent_name, "my_agent");
assert_eq!(pointer.current_spec_digest, "spec_digest_abc");
assert!(pointer.previous_spec_digest.is_none());
}
#[test]
fn test_release_pointer_promote() {
let mut pointer = ReleasePointer::new("my_agent".to_string(), "spec_digest_v1".to_string());
pointer.promote("spec_digest_v2".to_string());
assert_eq!(pointer.current_spec_digest, "spec_digest_v2");
assert_eq!(
pointer.previous_spec_digest,
Some("spec_digest_v1".to_string())
);
}
#[test]
fn test_release_pointer_serde_roundtrip() {
let pointer =
ReleasePointer::new("my_agent".to_string(), "spec_digest_current".to_string());
let json = serde_json::to_string(&pointer).expect("serialize");
let deserialized: ReleasePointer = serde_json::from_str(&json).expect("deserialize");
assert_eq!(pointer, deserialized);
}
#[test]
fn test_release_environment_all_variants() {
let dev_json = serde_json::to_string(&ReleaseEnvironment::Dev).expect("serialize");
let staging_json = serde_json::to_string(&ReleaseEnvironment::Staging).expect("serialize");
let prod_json = serde_json::to_string(&ReleaseEnvironment::Production).expect("serialize");
assert!(dev_json.contains("DEV"));
assert!(staging_json.contains("STAGING"));
assert!(prod_json.contains("PRODUCTION"));
}
}