use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use super::Attachment;
use crate::error::{err_msg, ErrorKind, Result, ToResult};
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Message {
pub id: String,
#[serde(default = "default_typ")]
pub typ: String,
#[serde(rename = "type")]
pub type_: String,
pub body: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub thid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pthid: Option<String>,
#[serde(flatten)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub extra_headers: HashMap<String, Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_time: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires_time: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from_prior: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<Attachment>>,
}
const PLAINTEXT_TYP: &str = "application/didcomm-plain+json";
fn default_typ() -> String {
PLAINTEXT_TYP.to_string()
}
impl Message {
pub fn build(id: String, type_: String, body: Value) -> MessageBuilder {
MessageBuilder::new(id, type_, body)
}
pub(crate) fn from_str(s: &str) -> Result<Message> {
serde_json::from_str(s).to_didcomm("Unable deserialize jwm")
}
pub(crate) fn validate(self) -> Result<Self> {
if self.typ != PLAINTEXT_TYP {
Err(err_msg(
ErrorKind::Malformed,
format!("`typ` must be \"{}\"", PLAINTEXT_TYP),
))?;
}
Ok(self)
}
}
pub struct MessageBuilder {
id: String,
type_: String,
body: Value,
from: Option<String>,
to: Option<Vec<String>>,
thid: Option<String>,
pthid: Option<String>,
extra_headers: HashMap<String, Value>,
created_time: Option<u64>,
expires_time: Option<u64>,
from_prior: Option<String>,
attachments: Option<Vec<Attachment>>,
}
impl MessageBuilder {
fn new(id: String, type_: String, body: Value) -> Self {
MessageBuilder {
id,
type_,
body,
from: None,
to: None,
thid: None,
pthid: None,
extra_headers: HashMap::new(),
created_time: None,
expires_time: None,
from_prior: None,
attachments: None,
}
}
pub fn to(mut self, to: String) -> Self {
if let Some(ref mut sto) = self.to {
sto.push(to);
self
} else {
self.to = Some(vec![to]);
self
}
}
pub fn to_many(mut self, to: Vec<String>) -> Self {
if let Some(ref mut sto) = self.to {
let mut to = to;
sto.append(&mut to);
self
} else {
self.to = Some(to);
self
}
}
pub fn from(mut self, from: String) -> Self {
self.from = Some(from);
self
}
pub fn thid(mut self, thid: String) -> Self {
self.thid = Some(thid);
self
}
pub fn pthid(mut self, pthid: String) -> Self {
self.pthid = Some(pthid);
self
}
pub fn header(mut self, key: String, value: Value) -> Self {
self.extra_headers.insert(key, value);
self
}
pub fn created_time(mut self, created_time: u64) -> Self {
self.created_time = Some(created_time);
self
}
pub fn expires_time(mut self, expires_time: u64) -> Self {
self.expires_time = Some(expires_time);
self
}
pub fn from_prior(mut self, from_prior: String) -> Self {
self.from_prior = Some(from_prior);
self
}
pub fn attachment(mut self, attachment: Attachment) -> Self {
if let Some(ref mut attachments) = self.attachments {
attachments.push(attachment);
self
} else {
self.attachments = Some(vec![attachment]);
self
}
}
pub fn attachments(mut self, attachments: Vec<Attachment>) -> Self {
if let Some(ref mut sattachments) = self.attachments {
let mut attachments = attachments;
sattachments.append(&mut attachments);
self
} else {
self.attachments = Some(attachments);
self
}
}
pub fn finalize(self) -> Message {
Message {
id: self.id,
typ: PLAINTEXT_TYP.to_owned(),
type_: self.type_,
body: self.body,
to: self.to,
thid: self.thid,
pthid: self.pthid,
from: self.from,
extra_headers: self.extra_headers,
created_time: self.created_time,
expires_time: self.expires_time,
from_prior: self.from_prior,
attachments: self.attachments,
}
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn message_build_works() {
let message = Message::build(
"example-1".into(),
"example/v1".into(),
json!("example-body"),
)
.to("did:example:1".into())
.to_many(vec!["did:example:2".into(), "did:example:3".into()])
.from("did:example:4".into())
.thid("example-thread-1".into())
.pthid("example-parent-thread-1".into())
.header("example-header-1".into(), json!("example-header-1-value"))
.header("example-header-2".into(), json!("example-header-2-value"))
.created_time(10000)
.expires_time(20000)
.attachment(
Attachment::base64("ZXhhbXBsZQ==".into())
.id("attachment1".into())
.finalize(),
)
.attachments(vec![
Attachment::json(json!("example"))
.id("attachment2".into())
.finalize(),
Attachment::json(json!("example"))
.id("attachment3".into())
.finalize(),
])
.finalize();
assert_eq!(message.id, "example-1");
assert_eq!(message.typ, "application/didcomm-plain+json");
assert_eq!(message.type_, "example/v1");
assert_eq!(message.body, json!("example-body"));
assert_eq!(message.from, Some("did:example:4".into()));
assert_eq!(message.thid, Some("example-thread-1".into()));
assert_eq!(message.pthid, Some("example-parent-thread-1".into()));
assert_eq!(message.created_time, Some(10000));
assert_eq!(message.expires_time, Some(20000));
assert_eq!(
message.to,
Some(vec![
"did:example:1".into(),
"did:example:2".into(),
"did:example:3".into()
])
);
let extra_headers = message.extra_headers;
assert_eq!(extra_headers.len(), 2);
assert!(extra_headers.contains_key(&"example-header-1".to_owned()));
assert_eq!(
extra_headers[&"example-header-1".to_owned()],
"example-header-1-value"
);
assert!(extra_headers.contains_key(&"example-header-2".to_owned()));
assert_eq!(
extra_headers[&"example-header-2".to_owned()],
"example-header-2-value"
);
let attachments = message.attachments.expect("attachments is some.");
assert_eq!(attachments.len(), 3);
assert_eq!(attachments[0].id, Some("attachment1".into()));
assert_eq!(attachments[1].id, Some("attachment2".into()));
assert_eq!(attachments[2].id, Some("attachment3".into()));
}
}