use std::fmt;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
errors::GitError,
hash::ObjectHash,
internal::object::{
ObjectTrait,
integrity::IntegrityHash,
types::{ActorRef, Header, ObjectType},
},
};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum IntentEventKind {
Analyzed,
Completed,
Cancelled,
#[serde(untagged)]
Other(String),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct IntentEvent {
#[serde(flatten)]
header: Header,
intent_id: Uuid,
kind: IntentEventKind,
#[serde(default, skip_serializing_if = "Option::is_none")]
reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
result_commit: Option<IntegrityHash>,
#[serde(default, skip_serializing_if = "Option::is_none")]
next_intent_id: Option<Uuid>,
}
impl IntentEvent {
pub fn new(
created_by: ActorRef,
intent_id: Uuid,
kind: IntentEventKind,
) -> Result<Self, String> {
Ok(Self {
header: Header::new(ObjectType::IntentEvent, created_by)?,
intent_id,
kind,
reason: None,
result_commit: None,
next_intent_id: None,
})
}
pub fn header(&self) -> &Header {
&self.header
}
pub fn intent_id(&self) -> Uuid {
self.intent_id
}
pub fn kind(&self) -> &IntentEventKind {
&self.kind
}
pub fn reason(&self) -> Option<&str> {
self.reason.as_deref()
}
pub fn result_commit(&self) -> Option<&IntegrityHash> {
self.result_commit.as_ref()
}
pub fn next_intent_id(&self) -> Option<Uuid> {
self.next_intent_id
}
pub fn set_reason(&mut self, reason: Option<String>) {
self.reason = reason;
}
pub fn set_result_commit(&mut self, result_commit: Option<IntegrityHash>) {
self.result_commit = result_commit;
}
pub fn set_next_intent_id(&mut self, next_intent_id: Option<Uuid>) {
self.next_intent_id = next_intent_id;
}
}
impl fmt::Display for IntentEvent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "IntentEvent: {}", self.header.object_id())
}
}
impl ObjectTrait for IntentEvent {
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::IntentEvent
}
fn get_size(&self) -> usize {
match serde_json::to_vec(self) {
Ok(v) => v.len(),
Err(e) => {
tracing::warn!("failed to compute IntentEvent 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_intent_event_fields() {
let actor = ActorRef::agent("planner").expect("actor");
let mut event = IntentEvent::new(actor, Uuid::from_u128(0x1), IntentEventKind::Completed)
.expect("event");
let hash = IntegrityHash::compute(b"commit");
let next_intent_id = Uuid::from_u128(0x2);
event.set_reason(Some("done".to_string()));
event.set_result_commit(Some(hash));
event.set_next_intent_id(Some(next_intent_id));
assert_eq!(event.kind(), &IntentEventKind::Completed);
assert_eq!(event.reason(), Some("done"));
assert_eq!(event.result_commit(), Some(&hash));
assert_eq!(event.next_intent_id(), Some(next_intent_id));
}
#[test]
fn test_intent_event_kind_accepts_unknown_string() {
let kind: IntentEventKind =
serde_json::from_str("\"waiting_for_human_review\"").expect("kind");
assert_eq!(
kind,
IntentEventKind::Other("waiting_for_human_review".to_string())
);
}
}