use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SignatureEnvelope {
pub agent_id: String,
pub action: String,
pub timestamp: DateTime<Utc>,
pub nonce: String,
pub body: HashMap<String, Value>,
}
impl SignatureEnvelope {
#[must_use]
pub fn new(
agent_id: String,
action: String,
timestamp: DateTime<Utc>,
nonce: String,
body: HashMap<String, Value>,
) -> Self {
Self {
agent_id,
action,
timestamp,
nonce,
body,
}
}
#[must_use]
pub fn to_value(&self) -> Value {
serde_json::json!({
"agentId": self.agent_id,
"action": self.action,
"timestamp": self.format_timestamp(),
"nonce": self.nonce,
"body": self.body,
})
}
#[must_use]
pub fn format_timestamp(&self) -> String {
self.timestamp.format("%Y-%m-%dT%H:%M:%SZ").to_string()
}
}
#[derive(Debug, Clone)]
pub struct EnvelopeBuilder {
agent_id: String,
}
impl EnvelopeBuilder {
#[must_use]
pub fn new(agent_id: String) -> Self {
Self { agent_id }
}
#[must_use]
pub fn build(&self, action: &str, body: HashMap<String, Value>) -> SignatureEnvelope {
SignatureEnvelope::new(
self.agent_id.clone(),
action.to_string(),
Utc::now(),
Uuid::new_v4().to_string(),
body,
)
}
#[must_use]
pub fn build_empty(&self, action: &str) -> SignatureEnvelope {
self.build(action, HashMap::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_envelope_builder_generates_unique_nonces() {
let builder = EnvelopeBuilder::new("test-agent".to_string());
let env1 = builder.build_empty("test_action");
let env2 = builder.build_empty("test_action");
assert_ne!(env1.nonce, env2.nonce, "Nonces should be unique");
}
#[test]
fn test_envelope_to_value() {
let envelope = SignatureEnvelope::new(
"agent-123".to_string(),
"star".to_string(),
DateTime::parse_from_rfc3339("2024-01-15T10:30:00Z")
.expect("valid timestamp")
.with_timezone(&Utc),
"nonce-456".to_string(),
HashMap::new(),
);
let value = envelope.to_value();
assert_eq!(value["agentId"], "agent-123");
assert_eq!(value["action"], "star");
assert_eq!(value["timestamp"], "2024-01-15T10:30:00Z");
assert_eq!(value["nonce"], "nonce-456");
}
#[test]
fn test_timestamp_format() {
let envelope = SignatureEnvelope::new(
"agent".to_string(),
"action".to_string(),
DateTime::parse_from_rfc3339("2024-06-15T14:30:45Z")
.expect("valid timestamp")
.with_timezone(&Utc),
"nonce".to_string(),
HashMap::new(),
);
assert_eq!(envelope.format_timestamp(), "2024-06-15T14:30:45Z");
}
}