Skip to main content

harmos_domain/state/
envelope.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14pub use crate::DefinitionId;
15
16/* === Imports === */
17
18// Standard Library Imports
19
20// Third Party Imports
21use serde::{Deserialize, Serialize};
22// Local Imports
23use super::entry::{Metadata, Position};
24
25/* === Constants === */
26
27/* === Macros === */
28
29/* === Type Aliases === */
30
31/* === Structs === */
32
33#[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/* === Enums === */
52
53#[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
66/* === Traits === */
67
68/* === Implementations === */
69
70impl 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
101/* === Public Functions === */
102
103/* === Private Functions === */
104
105fn 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/* === Tests === */