1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7#[serde(deny_unknown_fields)]
8pub struct SagaPayloadContract {
9 pub content_type: String,
11 pub schema_id: String,
13}
14
15impl Default for SagaPayloadContract {
16 fn default() -> Self {
22 Self {
23 content_type: "application/json".to_owned(),
24 schema_id: String::new(),
25 }
26 }
27}
28
29impl SagaPayloadContract {
30 pub fn validate(&self) -> Result<(), SagaPayloadError> {
36 let Some((kind, subtype)) = self.content_type.split_once('/') else {
37 return Err(SagaPayloadError);
38 };
39 if self.content_type.len() > 128
40 || kind.is_empty()
41 || subtype.is_empty()
42 || !kind
43 .bytes()
44 .chain(subtype.bytes())
45 .all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b"!#$&^_.+-".contains(&b))
46 || self.schema_id.len() > 256
47 || self.schema_id.trim() != self.schema_id
48 || self.schema_id.chars().any(char::is_control)
49 || (self.content_type != "application/json" && self.schema_id.is_empty())
50 {
51 return Err(SagaPayloadError);
52 }
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(deny_unknown_fields)]
60pub struct SagaPayload {
61 pub content_type: String,
63 pub schema_id: String,
65 pub body: Vec<u8>,
67}
68
69impl SagaPayload {
70 pub fn new(
76 content_type: impl Into<String>,
77 schema_id: impl Into<String>,
78 body: Vec<u8>,
79 ) -> Result<Self, SagaPayloadError> {
80 let payload = Self {
81 content_type: content_type.into(),
82 schema_id: schema_id.into(),
83 body,
84 };
85 payload.validate()?;
86 Ok(payload)
87 }
88
89 pub fn validate(&self) -> Result<(), SagaPayloadError> {
95 self.contract().validate()?;
96 if self.content_type == "application/json" {
97 serde_json::from_slice::<serde_json::Value>(&self.body)
98 .map_err(|_| SagaPayloadError)?;
99 }
100 Ok(())
101 }
102
103 pub fn contract(&self) -> SagaPayloadContract {
109 SagaPayloadContract {
110 content_type: self.content_type.clone(),
111 schema_id: self.schema_id.clone(),
112 }
113 }
114
115 pub fn json(value: serde_json::Value) -> Self {
121 Self {
122 content_type: "application/json".to_owned(),
123 schema_id: String::new(),
124 body: serde_json::to_vec(&value).expect("JSON value serialization"),
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct SagaPayloadError;
132
133impl std::fmt::Display for SagaPayloadError {
134 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 formatter.write_str("Saga payload contract is invalid")
141 }
142}
143
144impl std::error::Error for SagaPayloadError {}