use std::collections::{BTreeMap, BTreeSet};
use serde::{Deserialize, Serialize};
pub const JOURNAL_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Phase {
DryRun,
Build,
Publish,
Tag,
}
impl Phase {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::DryRun => "dry_run",
Self::Build => "build",
Self::Publish => "publish",
Self::Tag => "tag",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PhaseOutcome {
Ok,
Failed,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RunStatus {
InProgress,
Completed,
Abandoned,
}
impl RunStatus {
#[must_use]
pub fn as_str(self) -> &'static str {
match self {
Self::InProgress => "in_progress",
Self::Completed => "completed",
Self::Abandoned => "abandoned",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PublishReceipt {
pub ecosystem: String,
pub package: Option<String>,
pub version: String,
pub registry_url: Option<String>,
pub digest: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct TagState {
pub created_local: bool,
pub pushed_remote: bool,
pub github_release: bool,
pub github_release_url: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PhaseRecord {
pub phase: Phase,
pub outcome: PhaseOutcome,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum EventKind {
RunCreated {
run_id: String,
plan_id: String,
version: String,
targets: Vec<String>,
},
PhaseEntered {
phase: Phase,
},
PhaseCompleted {
phase: Phase,
outcome: PhaseOutcome,
},
TargetDryRun {
target: String,
},
TargetBuilt {
target: String,
},
TargetPublished {
target: String,
receipt: PublishReceipt,
},
TargetCancelled {
target: String,
reason: String,
},
TagCreatedLocal {
tag: String,
},
TagPushedRemote {
tag: String,
},
GithubReleaseCreated {
tag: String,
url: Option<String>,
},
RunAbandoned {
reason: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JournalEvent {
pub schema_version: u32,
pub seq: u64,
pub ts: u64,
pub idempotency_key: String,
#[serde(flatten)]
pub kind: EventKind,
}
impl EventKind {
#[must_use]
pub fn idempotency_key(&self) -> String {
match self {
Self::RunCreated { .. } => "run_created".to_string(),
Self::PhaseEntered { phase } => format!("phase_entered:{}", phase.as_str()),
Self::PhaseCompleted { phase, .. } => {
format!("phase_completed:{}", phase.as_str())
}
Self::TargetDryRun { target } => format!("dry_run:{target}"),
Self::TargetBuilt { target } => format!("built:{target}"),
Self::TargetPublished { target, .. } => format!("published:{target}"),
Self::TargetCancelled { target, .. } => format!("cancelled:{target}"),
Self::TagCreatedLocal { tag } => format!("tag_created_local:{tag}"),
Self::TagPushedRemote { tag } => format!("tag_pushed_remote:{tag}"),
Self::GithubReleaseCreated { tag, .. } => format!("github_release_created:{tag}"),
Self::RunAbandoned { .. } => "run_abandoned".to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RunState {
pub schema_version: u32,
pub run_id: String,
pub plan_id: String,
pub version: String,
pub targets: Vec<String>,
pub applied_seq: u64,
pub status: RunStatus,
pub current_phase: Option<Phase>,
pub phases: Vec<PhaseRecord>,
pub dry_run: BTreeSet<String>,
pub built: BTreeSet<String>,
pub published: BTreeMap<String, PublishReceipt>,
pub cancelled: BTreeMap<String, String>,
pub tags: BTreeMap<String, TagState>,
pub abandon_reason: Option<String>,
pub created_ts: u64,
pub updated_ts: u64,
}
impl RunState {
#[must_use]
pub fn empty() -> Self {
Self {
schema_version: JOURNAL_SCHEMA_VERSION,
run_id: String::new(),
plan_id: String::new(),
version: String::new(),
targets: Vec::new(),
applied_seq: 0,
status: RunStatus::InProgress,
current_phase: None,
phases: Vec::new(),
dry_run: BTreeSet::new(),
built: BTreeSet::new(),
published: BTreeMap::new(),
cancelled: BTreeMap::new(),
tags: BTreeMap::new(),
abandon_reason: None,
created_ts: 0,
updated_ts: 0,
}
}
}
impl Default for RunState {
fn default() -> Self {
Self::empty()
}
}