harmos_domain/state/
record.rs1use serde::{Deserialize, Serialize, de::DeserializeOwned};
20use super::envelope::{DefinitionId, FactSource, SystemFact};
22use crate::Identified;
23
24pub type PreparedMembers = Vec<(Option<SystemFact>, Prepared)>;
31
32#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(deny_unknown_fields)]
36pub struct Prepared {
37 pub id: DefinitionId,
38 pub version: u32,
39 pub payload: serde_json::Value,
40}
41
42#[derive(Default)]
43pub struct Records {
44 members: Vec<(Option<SystemFact>, Prepared)>,
45 bytes: usize,
46 refusal: Option<PreparationError>,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
52pub enum PreparationError {
53 #[error("required journal capture failed")]
54 CaptureFailed,
55
56 #[error("a commit member could not be encoded")]
57 Encoding,
58
59 #[error("the commit exceeds its record or payload limit")]
60 Limit,
61
62 #[error("an emitted record is not a current member of the frozen runtime catalog")]
63 Membership,
64
65 #[error("a record has an invalid or reserved source identity")]
66 Identity,
67}
68
69pub trait Record: Identified + Serialize {}
72
73pub trait RecordProjection<R> {
74 fn project(&self) -> Option<&R>;
75}
76
77pub trait CatalogCodec {
78 fn identify(&self) -> (DefinitionId, u32);
79 fn encode<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>;
80}
81
82pub trait RecordCatalog: CatalogCodec + Sized {
83 fn restore(id: &DefinitionId, version: u32, payload: &serde_json::Value) -> Result<Option<Self>, serde_json::Error>;
84}
85
86impl<R> RecordProjection<R> for R {
89 fn project(&self) -> Option<&R> {
90 Some(self)
91 }
92}
93
94impl<T: Identified + Serialize> CatalogCodec for T {
95 fn identify(&self) -> (DefinitionId, u32) {
96 T::definition()
97 }
98
99 fn encode<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
100 self.serialize(serializer)
101 }
102}
103
104impl<T: Record + DeserializeOwned> RecordCatalog for T {
105 fn restore(id: &DefinitionId, version: u32, payload: &serde_json::Value) -> Result<Option<Self>, serde_json::Error> {
106 if (id, version) != (&T::definition().0, T::definition().1) {
107 return Ok(None);
108 }
109 T::deserialize(payload).map(Some)
110 }
111}
112
113impl RecordCatalog for () {
114 fn restore(_: &DefinitionId, _: u32, _: &serde_json::Value) -> Result<Option<Self>, serde_json::Error> {
115 Ok(None)
116 }
117}
118
119impl Prepared {
120 pub fn encode(value: &impl CatalogCodec) -> Result<Self, PreparationError> {
121 let (id, version) = value.identify();
122 if id.as_str().len() > Records::MAX_BYTES {
123 return Err(PreparationError::Limit);
124 }
125 struct Bounded {
126 bytes: Vec<u8>,
127 overflow: bool,
128 }
129 impl std::io::Write for Bounded {
130 fn write(&mut self, bytes: &[u8]) -> std::io::Result<usize> {
131 if self.bytes.len().checked_add(bytes.len()).is_none_or(|n| n > Records::MAX_BYTES) {
132 self.overflow = true;
133 return Err(std::io::Error::other("commit payload limit"));
134 }
135 self.bytes.extend_from_slice(bytes);
136 Ok(bytes.len())
137 }
138 fn flush(&mut self) -> std::io::Result<()> {
139 Ok(())
140 }
141 }
142 let mut writer = Bounded { bytes: Vec::new(), overflow: false };
143 let result = value.encode(&mut serde_json::Serializer::new(&mut writer));
144 if writer.overflow {
145 return Err(PreparationError::Limit);
146 }
147 result.map_err(|_| PreparationError::Encoding)?;
148 let payload = serde_json::from_slice(&writer.bytes).map_err(|_| PreparationError::Encoding)?;
149 let prepared = Self { id, version, payload };
150 if prepared.bytes()? > Records::MAX_BYTES {
151 return Err(PreparationError::Limit);
152 }
153 Ok(prepared)
154 }
155
156 pub fn typed_record(value: &impl CatalogCodec) -> Result<Self, PreparationError> {
157 let prepared = Self::encode(value)?;
158 if ["harmos:work/", "harmos:guest/", "harmos:sidecar/"].iter().any(|prefix| prepared.id.as_str().starts_with(prefix)) {
159 return Err(PreparationError::Identity);
160 }
161 Ok(prepared)
162 }
163
164 pub fn system(fact: &SystemFact) -> Result<Self, PreparationError> {
165 let owner = match &fact.source {
166 FactSource::Work(owner) | FactSource::Guest(owner) | FactSource::Sidecar(owner) => owner,
167 };
168 if owner.len().checked_add(fact.name.len()).and_then(|n| n.checked_add(16)).is_none_or(|n| n > Records::MAX_BYTES) {
169 return Err(PreparationError::Limit);
170 }
171 if !fact.validate_admission() {
172 return Err(PreparationError::Identity);
173 }
174 if fact.payload.len() > Records::MAX_BYTES / 4 {
175 return Err(PreparationError::Limit);
176 }
177 let (id, version) = fact.identify();
178 let prepared = Self { id, version, payload: serde_json::json!(fact.payload) };
179 if prepared.bytes()? > Records::MAX_BYTES {
180 return Err(PreparationError::Limit);
181 }
182 Ok(prepared)
183 }
184
185 pub fn bytes(&self) -> Result<usize, PreparationError> {
186 if self.id.as_str().len() > Records::MAX_BYTES {
187 return Err(PreparationError::Limit);
188 }
189 let payload = serde_json::to_vec(&self.payload).map_err(|_| PreparationError::Encoding)?.len();
190 let identity = serde_json::to_vec(&self.id).map_err(|_| PreparationError::Encoding)?.len();
191 payload.checked_add(identity).and_then(|n| n.checked_add(4)).ok_or(PreparationError::Limit)
192 }
193}
194
195impl Records {
196 pub const MAX_RECORDS: usize = 256;
197 pub const MAX_BYTES: usize = 4 * 1024 * 1024;
198
199 pub fn push<R: Record>(&mut self, record: R) -> Result<(), PreparationError> {
200 if let Some(refusal) = self.refusal {
201 return Err(refusal);
202 }
203 let result = (|| {
204 if self.members.len() == Self::MAX_RECORDS {
205 return Err(PreparationError::Limit);
206 }
207 let prepared = Prepared::typed_record(&record)?;
208 self.reserve(prepared.bytes()?)?;
209 self.members.push((None, prepared));
210 Ok(())
211 })();
212 if let Err(refusal) = result {
213 self.refusal = Some(refusal);
214 }
215 result
216 }
217
218 pub fn push_system(&mut self, fact: SystemFact) -> Result<(), PreparationError> {
219 if let Some(refusal) = self.refusal {
220 return Err(refusal);
221 }
222 let result = (|| {
223 if self.members.len() == Self::MAX_RECORDS {
224 return Err(PreparationError::Limit);
225 }
226 let prepared = Prepared::system(&fact)?;
227 self.reserve(prepared.bytes()?)?;
228 self.members.push((Some(fact), prepared));
229 Ok(())
230 })();
231 if let Err(refusal) = result {
232 self.refusal = Some(refusal);
233 }
234 result
235 }
236
237 fn reserve(&mut self, bytes: usize) -> Result<(), PreparationError> {
238 self.bytes = self.bytes.checked_add(bytes).filter(|n| *n <= Self::MAX_BYTES).ok_or(PreparationError::Limit)?;
239 Ok(())
240 }
241
242 pub fn collect<S, T: super::Transaction<S> + CatalogCodec>(
243 transaction: &T, state: &S,
244 ) -> Result<(Prepared, PreparedMembers), PreparationError> {
245 let prepared = Prepared::encode(transaction)?;
246 let mut out = Self::default();
247 out.reserve(prepared.bytes()?)?;
248 transaction.records(state, &mut out);
249 if let Some(refusal) = out.refusal {
250 return Err(refusal);
251 }
252 Ok((prepared, out.members))
253 }
254}
255
256