use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
use crate::error::{EnvelopeError, slug};
use crate::id::{CorrelationId, MessageKind, PatternId};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Envelope {
pub pattern: PatternId,
pub correlation: CorrelationId,
pub kind: MessageKind,
pub payload: serde_json::Value,
}
#[derive(Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
struct WireEnvelope {
#[serde(rename = "patternId")]
pattern_id: String,
correlation: String,
kind: String,
payload: serde_json::Value,
}
impl Envelope {
pub fn from_typed<T: Serialize>(
pattern: PatternId,
correlation: CorrelationId,
kind: MessageKind,
body: &T,
) -> Result<Self, EnvelopeError> {
if !pattern.allows(kind) {
return Err(EnvelopeError {
slug: slug::CONV_KIND_INVALID,
detail: format!("kind {kind} is not in pattern {pattern}'s closed set"),
});
}
crate::finite::check(body).map_err(|refusal| EnvelopeError {
slug: slug::CONV_SCHEMA_INVALID,
detail: refusal.detail().to_owned(),
})?;
let payload = serde_json::to_value(body).map_err(|error| EnvelopeError {
slug: slug::CONV_SCHEMA_INVALID,
detail: format!("typed payload did not serialize: {error}"),
})?;
Ok(Self {
pattern,
correlation,
kind,
payload,
})
}
pub fn decode_payload<T: DeserializeOwned>(&self) -> Result<T, EnvelopeError> {
serde_json::from_value(self.payload.clone()).map_err(|error| EnvelopeError {
slug: slug::CONV_SCHEMA_INVALID,
detail: format!("typed payload did not deserialize: {error}"),
})
}
pub fn encode(&self) -> Result<Vec<u8>, EnvelopeError> {
let wire = WireEnvelope {
pattern_id: self.pattern.as_str().to_owned(),
correlation: self.correlation.to_string(),
kind: self.kind.as_str().to_owned(),
payload: self.payload.clone(),
};
serde_json::to_vec(&wire).map_err(|error| EnvelopeError {
slug: slug::CONV_ENVELOPE_MALFORMED,
detail: format!("envelope did not encode: {error}"),
})
}
pub fn decode(bytes: &[u8]) -> Result<Self, EnvelopeError> {
let wire: WireEnvelope = serde_json::from_slice(bytes).map_err(|error| EnvelopeError {
slug: slug::CONV_ENVELOPE_MALFORMED,
detail: format!("bytes are not a conversation envelope: {error}"),
})?;
let pattern = PatternId::parse_exact(&wire.pattern_id).ok_or_else(|| EnvelopeError {
slug: slug::CONV_PATTERN_UNKNOWN,
detail: format!("unknown pattern contract: {}", wire.pattern_id),
})?;
let correlation = wire.correlation.parse().map_err(|error| EnvelopeError {
slug: slug::CONV_CORRELATION_INVALID,
detail: format!("correlation {:?} is not typed: {error}", wire.correlation),
})?;
let kind = MessageKind::parse_exact(&wire.kind).ok_or_else(|| EnvelopeError {
slug: slug::CONV_KIND_INVALID,
detail: format!("unknown kind: {}", wire.kind),
})?;
if !pattern.allows(kind) {
return Err(EnvelopeError {
slug: slug::CONV_KIND_INVALID,
detail: format!("kind {kind} is not in pattern {pattern}'s closed set"),
});
}
Ok(Self {
pattern,
correlation,
kind,
payload: wire.payload,
})
}
}