git_internal/internal/object/
plan.rs1use std::fmt;
16
17use serde::{Deserialize, Serialize};
18use uuid::Uuid;
19
20use crate::{
21 errors::GitError,
22 hash::ObjectHash,
23 internal::object::{
24 ObjectTrait,
25 types::{ActorRef, Header, ObjectType},
26 },
27};
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "snake_case")]
32pub enum PlanStatus {
33 Pending,
35 InProgress,
37 Completed,
39 Failed,
41 Skipped,
43}
44
45impl PlanStatus {
46 pub fn as_str(&self) -> &'static str {
47 match self {
48 PlanStatus::Pending => "pending",
49 PlanStatus::InProgress => "in_progress",
50 PlanStatus::Completed => "completed",
51 PlanStatus::Failed => "failed",
52 PlanStatus::Skipped => "skipped",
53 }
54 }
55}
56
57impl fmt::Display for PlanStatus {
58 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59 write!(f, "{}", self.as_str())
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
65pub struct PlanStep {
66 pub intent: String,
67 pub inputs: Option<serde_json::Value>,
68 pub outputs: Option<serde_json::Value>,
69 pub checks: Option<serde_json::Value>,
70 pub owner_role: Option<String>,
71 pub status: PlanStatus,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
77pub struct Plan {
78 #[serde(flatten)]
79 header: Header,
80 run_id: Uuid,
81 plan_version: u32,
83 #[serde(default)]
84 steps: Vec<PlanStep>,
85}
86
87impl Plan {
88 pub fn new(repo_id: Uuid, created_by: ActorRef, run_id: Uuid) -> Result<Self, String> {
90 Ok(Self {
91 header: Header::new(ObjectType::Plan, repo_id, created_by)?,
92 run_id,
93 plan_version: 1,
94 steps: Vec::new(),
95 })
96 }
97
98 pub fn new_next(
103 repo_id: Uuid,
104 created_by: ActorRef,
105 run_id: Uuid,
106 previous_version: u32,
107 ) -> Result<Self, String> {
108 let next_version = previous_version
109 .checked_add(1)
110 .ok_or_else(|| "plan_version overflow".to_string())?;
111 Ok(Self {
112 header: Header::new(ObjectType::Plan, repo_id, created_by)?,
113 run_id,
114 plan_version: next_version,
115 steps: Vec::new(),
116 })
117 }
118
119 pub fn header(&self) -> &Header {
120 &self.header
121 }
122
123 pub fn run_id(&self) -> Uuid {
124 self.run_id
125 }
126
127 pub fn plan_version(&self) -> u32 {
128 self.plan_version
129 }
130
131 pub fn steps(&self) -> &[PlanStep] {
132 &self.steps
133 }
134
135 pub fn add_step(&mut self, step: PlanStep) {
136 self.steps.push(step);
137 }
138}
139
140impl fmt::Display for Plan {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "Plan: {}", self.header.object_id())
143 }
144}
145
146impl ObjectTrait for Plan {
147 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
148 where
149 Self: Sized,
150 {
151 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
152 }
153
154 fn get_type(&self) -> ObjectType {
155 ObjectType::Plan
156 }
157
158 fn get_size(&self) -> usize {
159 serde_json::to_vec(self).map(|v| v.len()).unwrap_or(0)
160 }
161
162 fn to_data(&self) -> Result<Vec<u8>, GitError> {
163 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_plan_version_ordering() {
173 let repo_id = Uuid::from_u128(0x0123456789abcdef0123456789abcdef);
174 let actor = ActorRef::human("jackie").expect("actor");
175 let run_id = Uuid::from_u128(0x1);
176
177 let plan_v1 = Plan::new(repo_id, actor.clone(), run_id).expect("plan");
178 let plan_v2 =
179 Plan::new_next(repo_id, actor.clone(), run_id, plan_v1.plan_version()).expect("plan");
180 let plan_v3 =
181 Plan::new_next(repo_id, actor.clone(), run_id, plan_v2.plan_version()).expect("plan");
182
183 let mut plans = [plan_v2.clone(), plan_v1.clone(), plan_v3.clone()];
184 plans.sort_by_key(|plan| plan.plan_version());
185
186 assert_eq!(plans[0].plan_version(), 1);
187 assert_eq!(plans[1].plan_version(), 2);
188 assert_eq!(plans[2].plan_version(), 3);
189
190 assert!(plan_v3.plan_version() > plan_v2.plan_version());
191 assert!(plan_v2.plan_version() > plan_v1.plan_version());
192 }
193}