use std::fmt::Display;
use serde::{Deserialize, Serialize};
use crate::{
errors::GitError,
hash::ObjectHash,
internal::object::{
ObjectTrait,
types::{ActorRef, Header, ObjectType},
},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum SelectionStrategy {
Explicit,
Heuristic,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ContextItemKind {
File,
Url,
Snippet,
Command,
Image,
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ContextItem {
pub kind: ContextItemKind,
pub path: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub preview: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub blob: Option<ObjectHash>,
}
impl ContextItem {
pub fn new(kind: ContextItemKind, path: impl Into<String>) -> Result<Self, String> {
let path = path.into();
if path.trim().is_empty() {
return Err("path cannot be empty".to_string());
}
Ok(Self {
kind,
path,
preview: None,
blob: None,
})
}
pub fn set_blob(&mut self, blob: Option<ObjectHash>) {
self.blob = blob;
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct ContextSnapshot {
#[serde(flatten)]
header: Header,
selection_strategy: SelectionStrategy,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
items: Vec<ContextItem>,
#[serde(default, skip_serializing_if = "Option::is_none")]
summary: Option<String>,
}
impl ContextSnapshot {
pub fn new(
created_by: ActorRef,
selection_strategy: SelectionStrategy,
) -> Result<Self, String> {
Ok(Self {
header: Header::new(ObjectType::ContextSnapshot, created_by)?,
selection_strategy,
items: Vec::new(),
summary: None,
})
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn selection_strategy(&self) -> &SelectionStrategy {
&self.selection_strategy
}
pub fn items(&self) -> &[ContextItem] {
&self.items
}
pub fn summary(&self) -> Option<&str> {
self.summary.as_deref()
}
pub fn add_item(&mut self, item: ContextItem) {
self.items.push(item);
}
pub fn set_summary(&mut self, summary: Option<String>) {
self.summary = summary;
}
}
impl Display for ContextSnapshot {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ContextSnapshot: {}", self.header.object_id())
}
}
impl ObjectTrait for ContextSnapshot {
fn from_bytes(data: &[u8], _hash: ObjectHash) -> Result<Self, GitError>
where
Self: Sized,
{
serde_json::from_slice(data).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
fn get_type(&self) -> ObjectType {
ObjectType::ContextSnapshot
}
fn get_size(&self) -> usize {
match serde_json::to_vec(self) {
Ok(v) => v.len(),
Err(e) => {
tracing::warn!("failed to compute ContextSnapshot size: {}", e);
0
}
}
}
fn to_data(&self) -> Result<Vec<u8>, GitError> {
serde_json::to_vec(self).map_err(|e| GitError::InvalidObjectInfo(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_context_snapshot_accessors_and_mutators() {
let actor = ActorRef::agent("coder").expect("actor");
let mut snapshot =
ContextSnapshot::new(actor, SelectionStrategy::Heuristic).expect("snapshot");
assert_eq!(snapshot.selection_strategy(), &SelectionStrategy::Heuristic);
assert!(snapshot.items().is_empty());
assert!(snapshot.summary().is_none());
let item = ContextItem::new(ContextItemKind::File, "src/main.rs").expect("item");
snapshot.add_item(item);
snapshot.set_summary(Some("selected by relevance".to_string()));
assert_eq!(snapshot.items().len(), 1);
assert_eq!(snapshot.summary(), Some("selected by relevance"));
}
}