use std::fmt;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::GitError,
hash::ObjectHash,
internal::object::{
ObjectTrait,
types::{ActorRef, Header, ObjectType},
},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PlanStepStatus {
Pending,
Progressing,
Completed,
Failed,
Skipped,
}
impl PlanStepStatus {
pub fn as_str(&self) -> &'static str {
match self {
PlanStepStatus::Pending => "pending",
PlanStepStatus::Progressing => "progressing",
PlanStepStatus::Completed => "completed",
PlanStepStatus::Failed => "failed",
PlanStepStatus::Skipped => "skipped",
}
}
}
impl fmt::Display for PlanStepStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlanStepEvent {
#[serde(flatten)]
header: Header,
plan_id: Uuid,
step_id: Uuid,
run_id: Uuid,
status: PlanStepStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
consumed_frames: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
produced_frames: Vec<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
spawned_task_id: Option<Uuid>,
#[serde(default, skip_serializing_if = "Option::is_none")]
outputs: Option<serde_json::Value>,
}
impl PlanStepEvent {
pub fn new(
created_by: ActorRef,
plan_id: Uuid,
step_id: Uuid,
run_id: Uuid,
status: PlanStepStatus,
) -> Result<Self, String> {
Ok(Self {
header: Header::new(ObjectType::PlanStepEvent, created_by)?,
plan_id,
step_id,
run_id,
status,
reason: None,
consumed_frames: Vec::new(),
produced_frames: Vec::new(),
spawned_task_id: None,
outputs: None,
})
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn plan_id(&self) -> Uuid {
self.plan_id
}
pub fn step_id(&self) -> Uuid {
self.step_id
}
pub fn run_id(&self) -> Uuid {
self.run_id
}
pub fn status(&self) -> &PlanStepStatus {
&self.status
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn consumed_frames(&self) -> &[Uuid] {
&self.consumed_frames
}
pub fn produced_frames(&self) -> &[Uuid] {
&self.produced_frames
}
pub fn spawned_task_id(&self) -> Option<Uuid> {
self.spawned_task_id
}
pub fn outputs(&self) -> Option<&serde_json::Value> {
self.outputs.as_ref()
}
pub fn set_reason(&mut self, reason: Option<String>) {
self.reason = reason;
}
pub fn set_consumed_frames(&mut self, consumed_frames: Vec<Uuid>) {
self.consumed_frames = consumed_frames;
}
pub fn set_produced_frames(&mut self, produced_frames: Vec<Uuid>) {
self.produced_frames = produced_frames;
}
pub fn set_spawned_task_id(&mut self, spawned_task_id: Option<Uuid>) {
self.spawned_task_id = spawned_task_id;
}
pub fn set_outputs(&mut self, outputs: Option<serde_json::Value>) {
self.outputs = outputs;
}
}
impl fmt::Display for PlanStepEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PlanStepEvent: {}", self.header.object_id())
}
}
impl ObjectTrait for PlanStepEvent {
fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
where
Self: Sized,
{
serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
fn get_type(&self) -> ObjectType {
ObjectType::PlanStepEvent
}
fn get_size(&self) -> usize {
match serde_json::to_vec(self) {
Ok(v) => v.len(),
Err(e) => {
tracing::warn!("failed to compute PlanStepEvent size: {}", e);
0
}
}
}
fn to_data(&self) -> Result<Vec<u8>, GitError> {
serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_plan_step_event_fields() {
let actor = ActorRef::agent("planner").expect("actor");
let mut event = PlanStepEvent::new(
actor,
Uuid::from_u128(0x1),
Uuid::from_u128(0x2),
Uuid::from_u128(0x3),
PlanStepStatus::Completed,
)
.expect("event");
let frame_a = Uuid::from_u128(0x10);
let frame_b = Uuid::from_u128(0x11);
let task_id = Uuid::from_u128(0x20);
event.set_reason(Some("done".to_string()));
event.set_consumed_frames(vec![frame_a]);
event.set_produced_frames(vec![frame_b]);
event.set_spawned_task_id(Some(task_id));
event.set_outputs(Some(serde_json::json!({"files": ["src/lib.rs"]})));
assert_eq!(event.status(), &PlanStepStatus::Completed);
assert_eq!(event.reason(), Some("done"));
assert_eq!(event.consumed_frames(), &[frame_a]);
assert_eq!(event.produced_frames(), &[frame_b]);
assert_eq!(event.spawned_task_id(), Some(task_id));
}
}