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)]
#[serde(deny_unknown_fields)]
#[serde(transparent)]
pub struct IntentSpec(pub serde_json::Value);
impl From<String> for IntentSpec {
fn from(value: String) -> Self {
Self(serde_json::Value::String(value))
}
}
impl From<&str> for IntentSpec {
fn from(value: &str) -> Self {
Self::from(value.to_string())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Intent {
#[serde(flatten)]
header: Header,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
parents: Vec<Uuid>,
prompt: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
spec: Option<IntentSpec>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
analysis_context_frames: Vec<Uuid>,
}
impl Intent {
pub fn new(created_by: ActorRef, prompt: impl Into<String>) -> Result<Self, String> {
Ok(Self {
header: Header::new(ObjectType::Intent, created_by)?,
parents: Vec::new(),
prompt: prompt.into(),
spec: None,
analysis_context_frames: Vec::new(),
})
}
pub fn new_revision_from(
created_by: ActorRef,
prompt: impl Into<String>,
parent: &Self,
) -> Result<Self, String> {
Self::new_revision_chain(created_by, prompt, &[parent.header.object_id()])
}
pub fn new_revision_chain(
created_by: ActorRef,
prompt: impl Into<String>,
parent_ids: &[Uuid],
) -> Result<Self, String> {
let mut intent = Self::new(created_by, prompt)?;
for id in parent_ids {
intent.add_parent(*id);
}
Ok(intent)
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn parents(&self) -> &[Uuid] {
&self.parents
}
pub fn prompt(&self) -> &str {
&self.prompt
}
pub fn spec(&self) -> Option<&IntentSpec> {
self.spec.as_ref()
}
pub fn analysis_context_frames(&self) -> &[Uuid] {
&self.analysis_context_frames
}
pub fn add_parent(&mut self, parent_id: Uuid) {
if parent_id == self.header.object_id() {
return;
}
if !self.parents.contains(&parent_id) {
self.parents.push(parent_id);
}
}
pub fn set_parents(&mut self, parents: Vec<Uuid>) {
self.parents = parents;
}
pub fn set_spec(&mut self, spec: Option<IntentSpec>) {
self.spec = spec;
}
pub fn set_analysis_context_frames(&mut self, analysis_context_frames: Vec<Uuid>) {
self.analysis_context_frames = analysis_context_frames;
}
}
impl fmt::Display for Intent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Intent: {}", self.header.object_id())
}
}
impl ObjectTrait for Intent {
fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
where
Self: Sized,
{
serde_json::from_slice(data).map_err(|e| GitError::InvalidIntentObject(e.to_string()))
}
fn get_type(&self) -> ObjectType {
ObjectType::Intent
}
fn get_size(&self) -> usize {
match serde_json::to_vec(self) {
Ok(v) => v.len(),
Err(e) => {
tracing::warn!("failed to compute Intent size: {}", e);
0
}
}
}
fn to_data(&self) -> Result<Vec<u8>, GitError> {
serde_json::to_vec(self).map_err(|e| GitError::InvalidIntentObject(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intent_creation() {
let actor = ActorRef::human("jackie").expect("actor");
let intent = Intent::new(actor, "Add pagination").expect("intent");
assert_eq!(intent.prompt(), "Add pagination");
assert!(intent.parents().is_empty());
assert!(intent.spec().is_none());
assert!(intent.analysis_context_frames().is_empty());
}
#[test]
fn test_intent_revision_graph() {
let actor = ActorRef::human("jackie").expect("actor");
let root = Intent::new(actor.clone(), "A").expect("intent");
let branch_a = Intent::new_revision_from(actor.clone(), "B", &root).expect("intent");
let branch_b = Intent::new_revision_chain(
actor,
"C",
&[root.header().object_id(), branch_a.header().object_id()],
)
.expect("intent");
assert_eq!(branch_a.parents(), &[root.header().object_id()]);
assert_eq!(
branch_b.parents(),
&[root.header().object_id(), branch_a.header().object_id()]
);
}
#[test]
fn test_spec_assignment() {
let actor = ActorRef::human("jackie").expect("actor");
let mut intent = Intent::new(actor, "A").expect("intent");
intent.set_spec(Some("structured spec".into()));
assert_eq!(intent.spec(), Some(&IntentSpec::from("structured spec")));
}
#[test]
fn test_analysis_context_frames() {
let actor = ActorRef::human("jackie").expect("actor");
let mut intent = Intent::new(actor, "A").expect("intent");
let frame_a = Uuid::from_u128(0x10);
let frame_b = Uuid::from_u128(0x11);
intent.set_analysis_context_frames(vec![frame_a, frame_b]);
assert_eq!(intent.analysis_context_frames(), &[frame_a, frame_b]);
}
}