git_internal/internal/object/
run.rs1use std::{collections::HashMap, fmt};
27
28use serde::{Deserialize, Serialize};
29use uuid::Uuid;
30
31use crate::{
32 errors::GitError,
33 hash::ObjectHash,
34 internal::object::{
35 ObjectTrait,
36 integrity::IntegrityHash,
37 types::{ActorRef, Header, ObjectType},
38 },
39};
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub struct Environment {
49 pub os: String,
51 pub arch: String,
53 pub cwd: String,
55 #[serde(flatten)]
57 pub extra: HashMap<String, serde_json::Value>,
58}
59
60impl Environment {
61 pub fn capture() -> Self {
64 Self {
65 os: std::env::consts::OS.to_string(),
66 arch: std::env::consts::ARCH.to_string(),
67 cwd: std::env::current_dir()
68 .map(|p| p.to_string_lossy().to_string())
69 .unwrap_or_else(|e| {
70 tracing::warn!("Failed to get current directory: {}", e);
71 "unknown".to_string()
72 }),
73 extra: HashMap::new(),
74 }
75 }
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct Run {
86 #[serde(flatten)]
89 header: Header,
90 task: Uuid,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 plan: Option<Uuid>,
95 commit: IntegrityHash,
97 #[serde(default, skip_serializing_if = "Option::is_none")]
99 snapshot: Option<Uuid>,
100 #[serde(default, skip_serializing_if = "Option::is_none")]
102 environment: Option<Environment>,
103}
104
105impl Run {
106 pub fn new(created_by: ActorRef, task: Uuid, commit: impl AsRef<str>) -> Result<Self, String> {
109 let commit = commit.as_ref().parse()?;
110 Ok(Self {
111 header: Header::new(ObjectType::Run, created_by)?,
112 task,
113 plan: None,
114 commit,
115 snapshot: None,
116 environment: Some(Environment::capture()),
117 })
118 }
119
120 pub fn header(&self) -> &Header {
122 &self.header
123 }
124
125 pub fn task(&self) -> Uuid {
127 self.task
128 }
129
130 pub fn plan(&self) -> Option<Uuid> {
132 self.plan
133 }
134
135 pub fn set_plan(&mut self, plan: Option<Uuid>) {
137 self.plan = plan;
138 }
139
140 pub fn commit(&self) -> &IntegrityHash {
142 &self.commit
143 }
144
145 pub fn snapshot(&self) -> Option<Uuid> {
147 self.snapshot
148 }
149
150 pub fn set_snapshot(&mut self, snapshot: Option<Uuid>) {
153 self.snapshot = snapshot;
154 }
155
156 pub fn environment(&self) -> Option<&Environment> {
158 self.environment.as_ref()
159 }
160}
161
162impl fmt::Display for Run {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "Run: {}", self.header.object_id())
165 }
166}
167
168impl ObjectTrait for Run {
169 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
170 where
171 Self: Sized,
172 {
173 serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
174 }
175
176 fn get_type(&self) -> ObjectType {
177 ObjectType::Run
178 }
179
180 fn get_size(&self) -> usize {
181 match serde_json::to_vec(self) {
182 Ok(v) => v.len(),
183 Err(e) => {
184 tracing::warn!("failed to compute Run size: {}", e);
185 0
186 }
187 }
188 }
189
190 fn to_data(&self) -> Result<Vec<u8>, GitError> {
191 serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 fn test_hash_hex() -> String {
204 IntegrityHash::compute(b"ai-process-test").to_hex()
205 }
206
207 #[test]
208 fn test_new_objects_creation() {
209 let actor = ActorRef::agent("test-agent").expect("actor");
210 let base_hash = test_hash_hex();
211 let run = Run::new(actor, Uuid::from_u128(0x1), &base_hash).expect("run");
212
213 let env = run.environment().expect("environment");
214 assert!(!env.os.is_empty());
215 assert!(!env.arch.is_empty());
216 assert!(!env.cwd.is_empty());
217 }
218
219 #[test]
220 fn test_run_plan_and_snapshot() {
221 let actor = ActorRef::agent("test-agent").expect("actor");
222 let base_hash = test_hash_hex();
223 let mut run = Run::new(actor, Uuid::from_u128(0x1), &base_hash).expect("run");
224 let plan_id = Uuid::from_u128(0x10);
225 let snapshot_id = Uuid::from_u128(0x20);
226
227 run.set_plan(Some(plan_id));
228 run.set_snapshot(Some(snapshot_id));
229
230 assert_eq!(run.plan(), Some(plan_id));
231 assert_eq!(run.snapshot(), Some(snapshot_id));
232 }
233}