solstice-ci/crates/common/src/messages.rs

160 lines
5.1 KiB
Rust

use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;
/// Versioned internal job request schema published to the message bus.
/// Keep additions backward compatible; never reuse or repurpose existing fields.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobRequest {
/// Schema identifier for routing and evolution.
#[serde(default = "default_jobrequest_schema")]
pub schema_version: String, // e.g., "jobrequest.v1"
/// Unique request identifier for idempotency and tracing correlation.
pub request_id: Uuid,
/// Source system of this request (forge or manual trigger).
pub source: SourceSystem,
/// Repository clone URL (SSH or HTTPS).
pub repo_url: String,
/// Repository owner (parsed from webhook or URL); optional for backward-compat.
#[serde(default)]
pub repo_owner: Option<String>,
/// Repository name (parsed from webhook or URL); optional for backward-compat.
#[serde(default)]
pub repo_name: Option<String>,
/// Commit SHA to check out.
pub commit_sha: String,
/// Optional path to the workflow file within the repo (KDL).
pub workflow_path: Option<String>,
/// Optional specific job id from the workflow to run.
pub workflow_job_id: Option<String>,
/// Optional scheduling hint selecting a base image or host group.
pub runs_on: Option<String>,
/// Submission timestamp (UTC).
pub submitted_at: OffsetDateTime,
}
fn default_jobrequest_schema() -> String {
"jobrequest.v1".to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceSystem {
Github,
Forgejo,
Manual,
}
impl JobRequest {
pub fn new(
source: SourceSystem,
repo_url: impl Into<String>,
commit_sha: impl Into<String>,
) -> Self {
Self {
schema_version: default_jobrequest_schema(),
request_id: Uuid::new_v4(),
source,
repo_url: repo_url.into(),
repo_owner: None,
repo_name: None,
commit_sha: commit_sha.into(),
workflow_path: None,
workflow_job_id: None,
runs_on: None,
submitted_at: OffsetDateTime::now_utc(),
}
}
}
/// Final job result reported by the orchestrator back to the Integration layer over MQ.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JobResult {
/// Schema identifier used as routing key/type. e.g., "jobresult.v1"
#[serde(default = "default_jobresult_schema")]
pub schema_version: String,
/// Correlates to the original JobRequest.request_id
pub request_id: Uuid,
/// Repository and commit info (for convenience in consumers)
pub repo_url: String,
/// Repository owner (when known). Optional for backward-compat.
#[serde(default)]
pub repo_owner: Option<String>,
/// Repository name (when known). Optional for backward-compat.
#[serde(default)]
pub repo_name: Option<String>,
pub commit_sha: String,
/// Outcome info
pub success: bool,
pub exit_code: i32,
/// Optional human summary
pub summary: Option<String>,
/// Completion timestamp
pub completed_at: OffsetDateTime,
}
fn default_jobresult_schema() -> String {
"jobresult.v1".to_string()
}
impl JobResult {
pub fn new(
request_id: Uuid,
repo_url: String,
commit_sha: String,
success: bool,
exit_code: i32,
summary: Option<String>,
) -> Self {
Self {
schema_version: default_jobresult_schema(),
request_id,
repo_url,
repo_owner: None,
repo_name: None,
commit_sha,
success,
exit_code,
summary,
completed_at: OffsetDateTime::now_utc(),
}
}
}
/// Combined dead-letter message that includes the original JobRequest and
/// an error summary so operators/tooling can inspect both together.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeadLetter {
#[serde(default = "default_deadletter_schema")]
pub schema_version: String, // e.g., "deadletter.v1"
pub request_id: Uuid,
pub occurred_at: OffsetDateTime,
/// Stage where the failure occurred (e.g., "prepare", "start", "other").
pub stage: String,
/// Human-readable error summary (single-line preferred).
pub error: String,
/// Original job request that triggered the failure.
pub original: JobRequest,
/// Optional bag of extra diagnostic info.
#[serde(default)]
pub extra: Option<std::collections::BTreeMap<String, String>>,
}
fn default_deadletter_schema() -> String {
"deadletter.v1".to_string()
}
impl DeadLetter {
pub fn new(stage: impl Into<String>, error: impl Into<String>, original: JobRequest) -> Self {
let request_id = original.request_id;
Self {
schema_version: default_deadletter_schema(),
request_id,
occurred_at: OffsetDateTime::now_utc(),
stage: stage.into(),
error: error.into(),
original,
extra: None,
}
}
}