git_internal/internal/object/
decision.rs1use std::fmt;
55
56use serde::{Deserialize, Serialize};
57use uuid::Uuid;
58
59use crate::{
60 errors::GitError,
61 hash::ObjectHash,
62 internal::object::{
63 ObjectTrait,
64 integrity::IntegrityHash,
65 types::{ActorRef, Header, ObjectType},
66 },
67};
68
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
71#[serde(rename_all = "snake_case")]
72pub enum DecisionType {
73 Commit,
75 Checkpoint,
77 Abandon,
79 Retry,
81 Rollback,
83 #[serde(untagged)]
84 Other(String),
85}
86
87impl fmt::Display for DecisionType {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 match self {
90 DecisionType::Commit => write!(f, "commit"),
91 DecisionType::Checkpoint => write!(f, "checkpoint"),
92 DecisionType::Abandon => write!(f, "abandon"),
93 DecisionType::Retry => write!(f, "retry"),
94 DecisionType::Rollback => write!(f, "rollback"),
95 DecisionType::Other(s) => write!(f, "{}", s),
96 }
97 }
98}
99
100impl From<String> for DecisionType {
101 fn from(s: String) -> Self {
102 match s.as_str() {
103 "commit" => DecisionType::Commit,
104 "checkpoint" => DecisionType::Checkpoint,
105 "abandon" => DecisionType::Abandon,
106 "retry" => DecisionType::Retry,
107 "rollback" => DecisionType::Rollback,
108 _ => DecisionType::Other(s),
109 }
110 }
111}
112
113impl From<&str> for DecisionType {
114 fn from(s: &str) -> Self {
115 match s {
116 "commit" => DecisionType::Commit,
117 "checkpoint" => DecisionType::Checkpoint,
118 "abandon" => DecisionType::Abandon,
119 "retry" => DecisionType::Retry,
120 "rollback" => DecisionType::Rollback,
121 _ => DecisionType::Other(s.to_string()),
122 }
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Decision {
132 #[serde(flatten)]
134 header: Header,
135 run_id: Uuid,
140 decision_type: DecisionType,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
154 chosen_patchset_id: Option<Uuid>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
161 result_commit_sha: Option<IntegrityHash>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
169 checkpoint_id: Option<String>,
170 #[serde(default, skip_serializing_if = "Option::is_none")]
178 rationale: Option<String>,
179}
180
181impl Decision {
182 pub fn new(
184 created_by: ActorRef,
185 run_id: Uuid,
186 decision_type: impl Into<DecisionType>,
187 ) -> Result<Self, String> {
188 Ok(Self {
189 header: Header::new(ObjectType::Decision, created_by)?,
190 run_id,
191 decision_type: decision_type.into(),
192 chosen_patchset_id: None,
193 result_commit_sha: None,
194 checkpoint_id: None,
195 rationale: None,
196 })
197 }
198
199 pub fn header(&self) -> &Header {
200 &self.header
201 }
202
203 pub fn run_id(&self) -> Uuid {
204 self.run_id
205 }
206
207 pub fn decision_type(&self) -> &DecisionType {
208 &self.decision_type
209 }
210
211 pub fn chosen_patchset_id(&self) -> Option<Uuid> {
212 self.chosen_patchset_id
213 }
214
215 pub fn result_commit_sha(&self) -> Option<&IntegrityHash> {
216 self.result_commit_sha.as_ref()
217 }
218
219 pub fn checkpoint_id(&self) -> Option<&str> {
220 self.checkpoint_id.as_deref()
221 }
222
223 pub fn rationale(&self) -> Option<&str> {
224 self.rationale.as_deref()
225 }
226
227 pub fn set_chosen_patchset_id(&mut self, chosen_patchset_id: Option<Uuid>) {
228 self.chosen_patchset_id = chosen_patchset_id;
229 }
230
231 pub fn set_result_commit_sha(&mut self, result_commit_sha: Option<IntegrityHash>) {
232 self.result_commit_sha = result_commit_sha;
233 }
234
235 pub fn set_checkpoint_id(&mut self, checkpoint_id: Option<String>) {
236 self.checkpoint_id = checkpoint_id;
237 }
238
239 pub fn set_rationale(&mut self, rationale: Option<String>) {
240 self.rationale = rationale;
241 }
242}
243
244impl fmt::Display for Decision {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 write!(f, "Decision: {}", self.header.object_id())
247 }
248}
249
250impl ObjectTrait for Decision {
251 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
252 where
253 Self: Sized,
254 {
255 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
256 }
257
258 fn get_type(&self) -> ObjectType {
259 ObjectType::Decision
260 }
261
262 fn get_size(&self) -> usize {
263 match serde_json::to_vec(self) {
264 Ok(v) => v.len(),
265 Err(e) => {
266 tracing::warn!("failed to compute Decision size: {}", e);
267 0
268 }
269 }
270 }
271
272 fn to_data(&self) -> Result<Vec<u8>, GitError> {
273 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_decision_fields() {
283 let actor = ActorRef::agent("test-agent").expect("actor");
284 let run_id = Uuid::from_u128(0x1);
285 let patchset_id = Uuid::from_u128(0x2);
286 let expected_hash = IntegrityHash::compute(b"decision-hash");
287
288 let mut decision = Decision::new(actor, run_id, "commit").expect("decision");
289 decision.set_chosen_patchset_id(Some(patchset_id));
290 decision.set_result_commit_sha(Some(expected_hash));
291 decision.set_rationale(Some("tests passed".to_string()));
292
293 assert_eq!(decision.chosen_patchset_id(), Some(patchset_id));
294 assert_eq!(decision.result_commit_sha(), Some(&expected_hash));
295 assert_eq!(decision.rationale(), Some("tests passed"));
296 assert_eq!(decision.decision_type(), &DecisionType::Commit);
297 }
298}