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(
79 content_type: impl Into<String>,
80 schema_id: impl Into<String>,
81 body: Vec<u8>,
82 ) -> Result<Self, SagaPayloadError> {
83 let payload = Self {
84 content_type: content_type.into(),
85 schema_id: schema_id.into(),
86 body,
87 };
88 payload.validate()?;
89 Ok(payload)
90 }
91
92 pub fn validate(&self) -> Result<(), SagaPayloadError> {
98 self.contract().validate()?;
99 if self.content_type == "application/json" {
100 serde_json::from_slice::<serde_json::Value>(&self.body)
101 .map_err(|_| SagaPayloadError)?;
102 }
103 Ok(())
104 }
105
106 pub fn contract(&self) -> SagaPayloadContract {
112 SagaPayloadContract {
113 content_type: self.content_type.clone(),
114 schema_id: self.schema_id.clone(),
115 }
116 }
117
118 pub fn json(value: serde_json::Value) -> Self {
125 Self {
126 content_type: "application/json".to_owned(),
127 schema_id: String::new(),
128 body: serde_json::to_vec(&value).expect("JSON value serialization"),
129 }
130 }
131}
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq)]
135pub struct SagaPayloadError;
136
137impl std::fmt::Display for SagaPayloadError {
138 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 formatter.write_str("Saga payload contract is invalid")
146 }
147}
148
149impl std::error::Error for SagaPayloadError {}