git_internal/internal/object/
intent.rs1use std::fmt;
34
35use serde::{Deserialize, Serialize};
36use uuid::Uuid;
37
38use crate::{
39 errors::GitError,
40 hash::ObjectHash,
41 internal::object::{
42 ObjectTrait,
43 types::{ActorRef, Header, ObjectType},
44 },
45};
46
47#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53#[serde(deny_unknown_fields)]
54#[serde(transparent)]
55pub struct IntentSpec(pub serde_json::Value);
56
57impl From<String> for IntentSpec {
58 fn from(value: String) -> Self {
59 Self(serde_json::Value::String(value))
60 }
61}
62
63impl From<&str> for IntentSpec {
64 fn from(value: &str) -> Self {
65 Self::from(value.to_string())
66 }
67}
68
69#[derive(Debug, Clone, Serialize, Deserialize)]
75#[serde(deny_unknown_fields)]
76pub struct Intent {
77 #[serde(flatten)]
80 header: Header,
81 #[serde(default, skip_serializing_if = "Vec::is_empty")]
86 parents: Vec<Uuid>,
87 prompt: String,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
92 spec: Option<IntentSpec>,
93 #[serde(default, skip_serializing_if = "Vec::is_empty")]
99 analysis_context_frames: Vec<Uuid>,
100}
101
102impl Intent {
103 pub fn new(created_by: ActorRef, prompt: impl Into<String>) -> Result<Self, String> {
105 Ok(Self {
106 header: Header::new(ObjectType::Intent, created_by)?,
107 parents: Vec::new(),
108 prompt: prompt.into(),
109 spec: None,
110 analysis_context_frames: Vec::new(),
111 })
112 }
113
114 pub fn new_revision_from(
118 created_by: ActorRef,
119 prompt: impl Into<String>,
120 parent: &Self,
121 ) -> Result<Self, String> {
122 Self::new_revision_chain(created_by, prompt, &[parent.header.object_id()])
123 }
124
125 pub fn new_revision_chain(
130 created_by: ActorRef,
131 prompt: impl Into<String>,
132 parent_ids: &[Uuid],
133 ) -> Result<Self, String> {
134 let mut intent = Self::new(created_by, prompt)?;
135 for id in parent_ids {
136 intent.add_parent(*id);
137 }
138 Ok(intent)
139 }
140
141 pub fn header(&self) -> &Header {
143 &self.header
144 }
145
146 pub fn parents(&self) -> &[Uuid] {
148 &self.parents
149 }
150
151 pub fn prompt(&self) -> &str {
153 &self.prompt
154 }
155
156 pub fn spec(&self) -> Option<&IntentSpec> {
158 self.spec.as_ref()
159 }
160
161 pub fn analysis_context_frames(&self) -> &[Uuid] {
164 &self.analysis_context_frames
165 }
166
167 pub fn add_parent(&mut self, parent_id: Uuid) {
169 if parent_id == self.header.object_id() {
170 return;
171 }
172 if !self.parents.contains(&parent_id) {
173 self.parents.push(parent_id);
174 }
175 }
176
177 pub fn set_parents(&mut self, parents: Vec<Uuid>) {
180 self.parents = parents;
181 }
182
183 pub fn set_spec(&mut self, spec: Option<IntentSpec>) {
185 self.spec = spec;
186 }
187
188 pub fn set_analysis_context_frames(&mut self, analysis_context_frames: Vec<Uuid>) {
191 self.analysis_context_frames = analysis_context_frames;
192 }
193}
194
195impl fmt::Display for Intent {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 write!(f, "Intent: {}", self.header.object_id())
198 }
199}
200
201impl ObjectTrait for Intent {
202 fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
203 where
204 Self: Sized,
205 {
206 serde_json::from_slice(data).map_err(|e| GitError::InvalidIntentObject(e.to_string()))
207 }
208
209 fn get_type(&self) -> ObjectType {
210 ObjectType::Intent
211 }
212
213 fn get_size(&self) -> usize {
214 match serde_json::to_vec(self) {
215 Ok(v) => v.len(),
216 Err(e) => {
217 tracing::warn!("failed to compute Intent size: {}", e);
218 0
219 }
220 }
221 }
222
223 fn to_data(&self) -> Result<Vec<u8>, GitError> {
224 serde_json::to_vec(self).map_err(|e| GitError::InvalidIntentObject(e.to_string()))
225 }
226}
227
228#[cfg(test)]
229mod tests {
230 use super::*;
231
232 #[test]
239 fn test_intent_creation() {
240 let actor = ActorRef::human("jackie").expect("actor");
241 let intent = Intent::new(actor, "Add pagination").expect("intent");
242
243 assert_eq!(intent.prompt(), "Add pagination");
244 assert!(intent.parents().is_empty());
245 assert!(intent.spec().is_none());
246 assert!(intent.analysis_context_frames().is_empty());
247 }
248
249 #[test]
250 fn test_intent_revision_graph() {
251 let actor = ActorRef::human("jackie").expect("actor");
252 let root = Intent::new(actor.clone(), "A").expect("intent");
253 let branch_a = Intent::new_revision_from(actor.clone(), "B", &root).expect("intent");
254 let branch_b = Intent::new_revision_chain(
255 actor,
256 "C",
257 &[root.header().object_id(), branch_a.header().object_id()],
258 )
259 .expect("intent");
260
261 assert_eq!(branch_a.parents(), &[root.header().object_id()]);
262 assert_eq!(
263 branch_b.parents(),
264 &[root.header().object_id(), branch_a.header().object_id()]
265 );
266 }
267
268 #[test]
269 fn test_spec_assignment() {
270 let actor = ActorRef::human("jackie").expect("actor");
271 let mut intent = Intent::new(actor, "A").expect("intent");
272 intent.set_spec(Some("structured spec".into()));
273 assert_eq!(intent.spec(), Some(&IntentSpec::from("structured spec")));
274 }
275
276 #[test]
277 fn test_analysis_context_frames() {
278 let actor = ActorRef::human("jackie").expect("actor");
279 let mut intent = Intent::new(actor, "A").expect("intent");
280 let frame_a = Uuid::from_u128(0x10);
281 let frame_b = Uuid::from_u128(0x11);
282
283 intent.set_analysis_context_frames(vec![frame_a, frame_b]);
284
285 assert_eq!(intent.analysis_context_frames(), &[frame_a, frame_b]);
286 }
287}