use crate::cesr::Versionage;
use crate::keri::core::serdering::{SadValue, SerderKERI};
use crate::keri::{versify, Ilks, KERIError, Kinds};
use chrono::{DateTime, Utc};
use indexmap::IndexMap;
pub struct ReplyEventBuilder {
route: String,
data: Option<IndexMap<String, SadValue>>,
stamp: Option<String>,
version: String,
kind: String,
}
impl ReplyEventBuilder {
pub fn new() -> Self {
Self {
route: String::new(),
data: None,
stamp: None,
version: "KERI10JSON000000_".to_string(),
kind: "JSON".to_string(),
}
}
pub fn with_route(mut self, route: String) -> Self {
self.route = route;
self
}
pub fn with_data(mut self, data: IndexMap<String, SadValue>) -> Self {
self.data = Some(data);
self
}
pub fn with_stamp(mut self, stamp: String) -> Self {
self.stamp = Some(stamp);
self
}
pub fn with_version(mut self, version: String) -> Self {
self.version = version;
self
}
pub fn with_kind(mut self, kind: String) -> Self {
self.kind = kind;
self
}
pub fn build(self) -> Result<SerderKERI, KERIError> {
if !Kinds::contains(&self.kind) {
return Err(KERIError::ValueError(format!(
"Invalid kind = {} for rpy.",
self.kind
)));
}
let vs = versify("KERI", &Versionage::from(self.version), &self.kind, 0)?;
let timestamp = self.stamp.unwrap_or_else(|| {
let now: DateTime<Utc> = Utc::now();
now.to_rfc3339()
});
let mut ked = IndexMap::new();
ked.insert("v".to_string(), SadValue::String(vs));
ked.insert("t".to_string(), SadValue::String(Ilks::RPY.to_string()));
ked.insert("d".to_string(), SadValue::String("".to_string()));
ked.insert("dt".to_string(), SadValue::String(timestamp));
ked.insert("r".to_string(), SadValue::String(self.route));
if let Some(data) = self.data {
ked.insert("a".to_string(), SadValue::Object(data));
} else {
ked.insert("a".to_string(), SadValue::Object(IndexMap::new()));
}
let serder = SerderKERI::from_sad_and_saids(&ked, None)?;
Ok(serder)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::keri::core::serdering::Serder;
#[test]
fn test_reply_event_builder_basic() -> Result<(), KERIError> {
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.build()?;
let ked = serder.ked();
assert_eq!(ked["t"].as_str().unwrap(), Ilks::RPY);
assert_eq!(ked["r"].as_str().unwrap(), "logs/processor");
assert!(ked.get("dt").is_some());
let a = ked["a"].clone();
let raw_str = std::str::from_utf8(serder.raw()).expect("bad utf8");
match &ked["a"] {
SadValue::Object(obj) => assert!(obj.is_empty()),
_ => panic!("Expected a field to be an object"),
}
Ok(())
}
#[test]
fn test_reply_event_with_data() -> Result<(), KERIError> {
let mut data = IndexMap::new();
data.insert(
"d".to_string(),
SadValue::String("EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM".to_string()),
);
data.insert(
"i".to_string(),
SadValue::String("EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM".to_string()),
);
data.insert(
"name".to_string(),
SadValue::String("John Jones".to_string()),
);
data.insert("role".to_string(), SadValue::String("Founder".to_string()));
let timestamp = "2020-08-22T17:50:12.988921+00:00".to_string();
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.with_data(data)
.with_stamp(timestamp.clone())
.build()?;
let ked = serder.ked();
assert_eq!(ked["dt"].as_str().unwrap(), timestamp);
let a = match &ked["a"] {
SadValue::Object(obj) => obj,
_ => panic!("Expected a field to be an object"),
};
assert_eq!(
a["d"].as_str().unwrap(),
"EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM"
);
assert_eq!(
a["i"].as_str().unwrap(),
"EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM"
);
assert_eq!(a["name"].as_str().unwrap(), "John Jones");
assert_eq!(a["role"].as_str().unwrap(), "Founder");
Ok(())
}
#[test]
fn test_reply_event_custom_version_kind() -> Result<(), KERIError> {
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.with_version("KERI10".to_string())
.with_kind("CBOR".to_string())
.build()?;
let ked = serder.ked();
let version = ked["v"].as_str().unwrap();
assert!(version.starts_with("KERI10CBOR"));
Ok(())
}
#[test]
fn test_reply_event_invalid_kind() -> Result<(), KERIError> {
let result = ReplyEventBuilder::new()
.with_kind("INVALID".to_string())
.build();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("Invalid kind"));
Ok(())
}
#[test]
fn test_reply_event_said_derivation() -> Result<(), KERIError> {
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.build()?;
let said = serder.said().expect("Failed to get SAID");
assert!(said.starts_with('E'));
assert_eq!(serder.ked()["d"].as_str().unwrap(), said);
Ok(())
}
#[test]
fn test_reply_matches_python_example() -> Result<(), KERIError> {
let mut data = IndexMap::new();
data.insert(
"d".to_string(),
SadValue::String("EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM".to_string()),
);
data.insert(
"i".to_string(),
SadValue::String("EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM".to_string()),
);
data.insert(
"name".to_string(),
SadValue::String("John Jones".to_string()),
);
data.insert("role".to_string(), SadValue::String("Founder".to_string()));
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.with_data(data)
.with_stamp("2020-08-22T17:50:12.988921+00:00".to_string())
.build()?;
let ked = serder.ked();
assert_eq!(ked["t"].as_str().unwrap(), Ilks::RPY);
assert_eq!(
ked["dt"].as_str().unwrap(),
"2020-08-22T17:50:12.988921+00:00"
);
assert_eq!(ked["r"].as_str().unwrap(), "logs/processor");
let a = match &ked["a"] {
SadValue::Object(obj) => obj,
_ => panic!("Expected a field to be an object"),
};
assert_eq!(
a["d"].as_str().unwrap(),
"EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM"
);
assert_eq!(
a["i"].as_str().unwrap(),
"EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM"
);
assert_eq!(a["name"].as_str().unwrap(), "John Jones");
assert_eq!(a["role"].as_str().unwrap(), "Founder");
Ok(())
}
#[test]
fn test_reply_event_builder_serialization() -> Result<(), KERIError> {
let mut data = IndexMap::new();
data.insert(
"d".to_string(),
SadValue::String("EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM".to_string()),
);
data.insert(
"i".to_string(),
SadValue::String("EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM".to_string()),
);
data.insert(
"name".to_string(),
SadValue::String("John Jones".to_string()),
);
data.insert("role".to_string(), SadValue::String("Founder".to_string()));
let timestamp = "2020-08-22T17:50:12.988921+00:00".to_string();
let serder = ReplyEventBuilder::new()
.with_route("logs/processor".to_string())
.with_data(data)
.with_stamp(timestamp)
.build()?;
let raw = serder.raw();
let raw_str = std::str::from_utf8(&raw).expect("bad utf8");
assert!(raw_str.starts_with("{\"v\":\"KERI10JSON"));
assert!(raw_str.contains("\"t\":\"rpy\""));
assert!(raw_str.contains("\"John Jones\""));
assert!(raw_str.contains("\"Founder\""));
assert!(raw_str.contains("\"r\":\"logs/processor\""));
Ok(())
}
#[test]
fn test_reply_with_empty_route() -> Result<(), KERIError> {
let serder = ReplyEventBuilder::new().build()?;
let ked = serder.ked();
assert_eq!(ked["t"].as_str().unwrap(), Ilks::RPY);
assert_eq!(ked["r"].as_str().unwrap(), "");
Ok(())
}
#[test]
fn test_reply_event_for_role_add() -> Result<(), KERIError> {
let route = "/end/role/add".to_string();
let mut data = IndexMap::new();
data.insert(
"cid".to_string(),
SadValue::String("BLK_YxcmK_sAsSW1CbNLJl_FA0gw0FKDuPr_xUwKcj7y".to_string()),
);
data.insert("role".to_string(), SadValue::String("watcher".to_string()));
data.insert(
"eid".to_string(),
SadValue::String("BF6YSJGAtVNmq3b7dpBi04Q0YdqvTfsk9PFkkZaR8LRr".to_string()),
);
let timestamp = "2021-01-01T00:00:00.000000+00:00".to_string();
let serder = ReplyEventBuilder::new()
.with_route(route)
.with_data(data)
.with_stamp(timestamp)
.build()?;
let ked = serder.ked();
assert_eq!(
ked["dt"].as_str().unwrap(),
"2021-01-01T00:00:00.000000+00:00"
);
let raw = serder.raw();
let expected = b"{\"v\":\"KERI10JSON000113_\",\"t\":\"rpy\",\"d\":\"EFlkeg-NociMRXHSGBSqARxV5y7zuT5z-ZpLZAkcoMkk\",\"dt\":\"2021-01-01T00:00:00.000000+00:00\",\"r\":\"/end/role/add\",\"a\":{\"cid\":\"BLK_YxcmK_sAsSW1CbNLJl_FA0gw0FKDuPr_xUwKcj7y\",\"role\":\"watcher\",\"eid\":\"BF6YSJGAtVNmq3b7dpBi04Q0YdqvTfsk9PFkkZaR8LRr\"}}";
assert_eq!(raw, expected);
let said = serder.said().expect("Failed to get SAID");
assert_eq!(said, "EFlkeg-NociMRXHSGBSqARxV5y7zuT5z-ZpLZAkcoMkk");
let a = match &ked["a"] {
SadValue::Object(obj) => obj,
_ => panic!("Expected a field to be an object"),
};
assert_eq!(
a["cid"].as_str().unwrap(),
"BLK_YxcmK_sAsSW1CbNLJl_FA0gw0FKDuPr_xUwKcj7y"
);
assert_eq!(a["role"].as_str().unwrap(), "watcher");
assert_eq!(
a["eid"].as_str().unwrap(),
"BF6YSJGAtVNmq3b7dpBi04Q0YdqvTfsk9PFkkZaR8LRr"
);
Ok(())
}
}