harmos_domain/state/
envelope.rs1pub use crate::DefinitionId;
15
16use serde::{Deserialize, Serialize};
22use super::entry::{Metadata, Position};
24
25#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34pub struct SystemFact {
35 pub source: FactSource,
36 pub name: String,
37 pub version: u32,
38 pub payload: Vec<u8>,
39}
40
41#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
42pub struct Envelope<P> {
43 pub position: Position,
44 pub kind: Kind,
45 pub id: DefinitionId,
46 pub version: u32,
47 pub metadata: Metadata,
48 pub payload: P,
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
54pub enum Kind {
55 Transaction,
56 Record,
57}
58
59#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
60pub enum FactSource {
61 Work(String),
62 Guest(String),
63 Sidecar(String),
64}
65
66impl SystemFact {
71 pub fn identify(&self) -> (DefinitionId, u32) {
72 let id = match &self.source {
73 FactSource::Work(service) => format!("harmos:work/{service}/{}", self.name),
74 FactSource::Guest(guest) => format!("harmos:guest/{guest}/{}", self.name),
75 FactSource::Sidecar(owner) => format!("harmos:sidecar/{owner}/{}", self.name),
76 };
77
78 (DefinitionId::new(id), self.version)
79 }
80
81 pub(crate) fn validate_admission(&self) -> bool {
82 if !matches!(self.source, FactSource::Sidecar(_)) {
83 return true;
84 }
85 system_identity(&self.identify().0).is_ok_and(|value| value == Some((self.source.clone(), self.name.clone())))
86 }
87
88 pub fn restore<'de, D>(id: &DefinitionId, version: u32, payload: D) -> Result<Option<Self>, D::Error>
89 where
90 D: serde::Deserializer<'de>,
91 {
92 let Some((source, name)) = system_identity(id).map_err(serde::de::Error::custom)? else {
93 return Ok(None);
94 };
95 let payload = Vec::<u8>::deserialize(payload)?;
96
97 Ok(Some(Self { source, name, version, payload }))
98 }
99}
100
101fn system_identity(id: &DefinitionId) -> Result<Option<(FactSource, String)>, &'static str> {
106 if let Some(namespaced) = id.as_str().strip_prefix("harmos:work/") {
107 let (service, name) = namespaced.split_once('/').ok_or("a work fact id must contain its local fact name")?;
108 if service.is_empty() || name.is_empty() {
109 return Err("a work fact id must name both service and fact");
110 }
111 return Ok(Some((FactSource::Work(service.to_owned()), name.to_owned())));
112 }
113
114 if let Some(namespaced) = id.as_str().strip_prefix("harmos:guest/") {
115 let (guest, name) = namespaced.split_once('/').ok_or("a guest fact id must contain its local fact name")?;
116 if guest.is_empty() || name.is_empty() {
117 return Err("a guest fact id must name both guest and fact");
118 }
119 return Ok(Some((FactSource::Guest(guest.to_owned()), name.to_owned())));
120 }
121
122 if let Some(namespaced) = id.as_str().strip_prefix("harmos:sidecar/") {
123 let (owner, name) = namespaced.split_once('/').ok_or("a sidecar fact id must contain its local fact name")?;
124 if !owner.as_bytes().first().is_some_and(u8::is_ascii_lowercase)
125 || !owner.bytes().all(|b| b.is_ascii_lowercase() || b.is_ascii_digit() || b == b'.' || b == b'-')
126 || name.is_empty()
127 {
128 return Err("a sidecar fact must name a portable artifact owner and nonempty fact");
129 }
130 return Ok(Some((FactSource::Sidecar(owner.to_owned()), name.to_owned())));
131 }
132 Ok(None)
133}
134
135