Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

Create and serialize most Message kinds #37

Merged
merged 5 commits into from
Dec 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion crates/protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ jsonschema = "0.17.1"
serde = { version = "1.0.193", features = ["derive"] }
serde_json = "1.0.108"
type-safe-id = { version = "0.2.1", features = ["serde"] }
thiserror = "1.0.50"
thiserror = "1.0.50"
serde_with = "3.4.0"
83 changes: 83 additions & 0 deletions crates/protocol/src/message/close.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
use ::serde::{Deserialize, Serialize};
use chrono::Utc;
use serde_with::skip_serializing_none;
use type_safe_id::{DynamicType, TypeSafeId};

use super::{Message, MessageError, MessageKind, MessageMetadata};

pub struct Close;

impl Close {
pub fn create(
from: String,
to: String,
exchange_id: TypeSafeId<DynamicType>,
reason: Option<String>,
) -> Result<Message<CloseData>, MessageError> {
let metadata = MessageMetadata {
from,
to,
kind: MessageKind::Close,
id: MessageKind::Close.typesafe_id()?,
exchange_id: exchange_id,
created_at: Utc::now(),
};

let data = CloseData { reason };

Ok(Message {
metadata,
data,
signature: None,
})
}
}

/// A struct representing the data contained within the [`Message`] for a [`Close`].
///
/// See [Quote](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#close) for more
/// information.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[skip_serializing_none]
#[serde(rename_all = "camelCase")]
pub struct CloseData {
/// an explanation of why the exchange is being closed/completed
reason: Option<String>,
}
Comment on lines +43 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just realized, we might want to not serialize empty fields. I'm not sure if this behavior is defined in the current tbDEX spec or not. On first glance, I don't think there's been a decision made.

Regardless, there's two ways I know of where we could skip serializing empty optional fields:

  • Annotate the optional field with #[serde(skip_serializing_if = "Option::is_none")]
  • Use the serde_with crate, which comes with a nice #[skip_serializing_none] macro

For reference, we're using the serde_with crate over in web5-rs, as it makes the code a little bit less messy, and maintains the readability a bit nicer.

Don't think we should take action on this right now, but I'll go ahead and make a task in the backlog for this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added task here: #38

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point! IMO we should not serialize None fields. I pushed a commit adding serde_with since it was a pretty easy additional. If we change our minds I'll strip it out.


#[cfg(test)]
mod tests {
use super::*;
use crate::test_data::TestData;

#[test]
fn can_create() {
let close = Close::create(
"did:example:from_1234".to_string(),
"did:example:to_1234".to_string(),
MessageKind::Rfq.typesafe_id().unwrap(),
Some("I don't want to do business with you any more".to_string()),
)
.expect("failed to create Close");

assert_eq!(
close.data.reason,
Some("I don't want to do business with you any more".to_string())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤣 love it!

);
assert_eq!(close.metadata.id.type_prefix(), "close");
}

#[test]
fn can_parse_close_from_json() {
let close = TestData::get_close(
"did:example:from_1234".to_string(),
MessageKind::Rfq
.typesafe_id()
.expect("failed to generate exchange_id"),
);
let json = serde_json::to_string(&close).expect("failed to serialize Close");
let parsed_close: Message<CloseData> =
serde_json::from_str(&json).expect("failed to deserialize Close");
assert_eq!(close, parsed_close);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
pub mod close;
pub mod order;
pub mod order_status;
pub mod quote;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use serde_json::to_string;
use type_safe_id::{DynamicType, TypeSafeId};

/// An enum representing all possible [`Message`] kinds.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum MessageKind {
Close,
Expand All @@ -15,7 +20,7 @@ pub enum MessageKind {
}

/// A struct representing the metadata present on every [`Message`].
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageMetadata {
/// The message's ID
Expand All @@ -34,7 +39,7 @@ pub struct MessageMetadata {
}

/// A struct representing the structure and common functionality available to all Messages.
#[derive(Debug, Deserialize, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Message<T> {
/// An object containing fields about the message
Expand Down
72 changes: 72 additions & 0 deletions crates/protocol/src/message/order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use ::serde::{Deserialize, Serialize};
use chrono::Utc;
use type_safe_id::{DynamicType, TypeSafeId};

use super::{Message, MessageError, MessageKind, MessageMetadata};

pub struct Order;

impl Order {
pub fn create(
from: String,
to: String,
exchange_id: TypeSafeId<DynamicType>,
) -> Result<Message<OrderData>, MessageError> {
let metadata = MessageMetadata {
from,
to,
kind: MessageKind::Order,
id: MessageKind::Order.typesafe_id()?,
exchange_id: exchange_id,
created_at: Utc::now(),
};

let data = OrderData ;

Ok(Message {
metadata,
data,
signature: None,
})
}
}

/// A struct representing the data contained within the [`Message`] for an [`Order`].
/// Currently, [`Order`] contains no data fields.
///
/// See [Order](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#order) for more
/// information.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderData;

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn can_create() {
let order = Order::create(
"did:example:from_1234".to_string(),
"did:example:to_1234".to_string(),
MessageKind::Rfq.typesafe_id().unwrap(),
)
.expect("failed to create Order");

assert_eq!(order.metadata.id.type_prefix(), "order");
}

#[test]
fn can_parse_order_from_json() {
let order = Order::create(
"did:example:from_1234".to_string(),
"did:example:to_1234".to_string(),
MessageKind::Rfq.typesafe_id().unwrap(),
)
.expect("Could not create Order");
let json: String = serde_json::to_string(&order).expect("failed to serialize Order");
let parsed_order: Message<OrderData> =
serde_json::from_str(&json).expect("failed to deserialize Order");
assert_eq!(order, parsed_order);
}
}
80 changes: 80 additions & 0 deletions crates/protocol/src/message/order_status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use ::serde::{Deserialize, Serialize};
use chrono::Utc;
use type_safe_id::{DynamicType, TypeSafeId};

use super::{Message, MessageError, MessageKind, MessageMetadata};

pub struct OrderStatus;

impl OrderStatus {
pub fn create(
from: String,
to: String,
exchange_id: TypeSafeId<DynamicType>,
order_status: String,
) -> Result<Message<OrderStatusData>, MessageError> {
let metadata = MessageMetadata {
from,
to,
kind: MessageKind::OrderStatus,
id: MessageKind::OrderStatus.typesafe_id()?,
exchange_id: exchange_id,
created_at: Utc::now(),
};

let data = OrderStatusData {
order_status,
};

Ok(Message {
metadata,
data,
signature: None,
})
}
}

/// A struct representing the data contained within the [`Message`] for an [`OrderStatus`].
/// Currently, [`OrderStatus`] contains no data fields.
///
/// See [OrderStatus](https://github.com/TBD54566975/tbdex/tree/main/specs/protocol#orderstatus) for more
/// information.
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct OrderStatusData {
/// Current status of Order that's being executed
order_status: String,
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn can_create() {
let order_status: Message<OrderStatusData> = OrderStatus::create(
"did:example:from_1234".to_string(),
"did:example:to_1234".to_string(),
MessageKind::Rfq.typesafe_id().unwrap(),
"COMPLETED".to_string()
)
.expect("failed to create OrderStatus");

assert_eq!(order_status.metadata.id.type_prefix(), "orderstatus");
}

#[test]
fn can_parse_order_status_from_json() {
let order_status = OrderStatus::create(
"did:example:from_1234".to_string(),
"did:example:to_1234".to_string(),
MessageKind::Rfq.typesafe_id().unwrap(),
"COMPLETED".to_string()
)
.expect("Could not create OrderStatus");
let json: String = serde_json::to_string(&order_status).expect("failed to serialize OrderStatus");
let parsed_order_status: Message<OrderStatusData> =
serde_json::from_str(&json).expect("failed to deserialize OrderStatus");
assert_eq!(order_status, parsed_order_status);
}
}
Loading
Loading