Hey folks, I would like some help cleaning up my code I want to create a more structured way to handle Nats messages and having the code littered with "nested.message.subject.whatever" is not great, as mistakes can be made!
Currently I'm doing this:
#[derive(Debug, Serialize, Deserialize)]
pub enum AccountRequest {
AccountCreate(CreateAccountRequest),
AccountGet(Uuid),
}
impl NatsMessage<AccountRequest> for AccountRequest {
fn subject(&self) -> &str {
match self {
AccountRequest::AccountCreate(_) => "account.create",
AccountRequest::AccountGet(_) => "account.get",
}
}
fn payload(&self) -> Vec<u8> {
match self {
AccountRequest::AccountCreate(data) => serde_json::to_vec(data).unwrap(),
AccountRequest::AccountGet(id) => id.to_string().into_bytes(),
}
}
fn from_message(msg: &async_nats::Message) -> Option<Self> {
match msg.subject.as_str() {
"account.create" => {
serde_json::from_slice::<models::account::CreateAccountRequest>(&msg.payload)
.ok()
.map(AccountRequest::AccountCreate)
}
"account.get" => {
let id = std::str::from_utf8(&msg.payload).ok();
id.and_then(|id| Uuid::parse_str(id).ok())
.map(AccountRequest::AccountGet)
}
_ => None,
}
}
}
And my trait declaration here:
pub trait NatsMessage<T> {
fn subject(&self) -> &str;
fn payload(&self) -> Vec<u8>;
fn from_message(msg: &async_nats::Message) -> Option<T>;
fn to_message(&self) -> async_nats::Message {
let payload = self.payload();
let length = payload.len();
let subject = Subject::from(self.subject());
async_nats::Message {
subject,
reply: None,
payload: payload.into(),
headers: None,
status: None,
description: None,
length,
}
}
}
But even doing this I have the hardcoded strings duplicated in both from_message
and subject
methods.
the nats Message struct looks like this
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Message {
/// Subject to which message is published to.
pub subject: Subject,
/// Optional reply subject to which response can be published by [crate::Subscriber].
/// Used for request-response pattern with [crate::Client::request].
pub reply: Option<Subject>,
/// Payload of the message. Can be any arbitrary data format.
pub payload: Bytes,
/// Optional headers.
pub headers: Option<HeaderMap>,
/// Optional Status of the message. Used mostly for internal handling.
pub status: Option<StatusCode>,
/// Optional [status][crate::Message::status] description.
pub description: Option<String>,
pub length: usize,
}
I'm currently only interested in subject and body, and essentially for a given type of messages I want to ensure that the bytes payload received, can be serialized and deserialized in the correct way. I am also not looking to handle wildcards at the moment.
Is there a way to do it with serde that I'm obviously missing? Or should I rethink the approach and maybe look at (learning) proc macros?