Skip to main content

harmos_domain/
metadata.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12mod codec;
13
14/* === Re-Exports === */
15
16/* === Imports === */
17
18// Standard Library Imports
19use std::{collections::BTreeMap, fmt};
20// Third Party Imports
21use serde::{Deserialize, Serialize};
22use time::OffsetDateTime;
23// Local Imports
24use crate::{MemorySize, Value};
25
26/* === Constants === */
27
28/* === Macros === */
29
30/* === Type Aliases === */
31
32/* === Structs === */
33
34#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
35#[serde(transparent)]
36pub struct Identifier(String);
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
39pub struct Metadata {
40    pub identifier: Identifier,
41    pub label: String,
42
43    #[serde(default)]
44    pub description: Option<String>,
45
46    #[serde(default, deserialize_with = "unique_tags")]
47    pub tags: Vec<String>,
48
49    #[serde(default)]
50    pub attributes: BTreeMap<String, Value>,
51
52    #[serde(default, with = "crate::value::optional_datetime")]
53    pub created_at: Option<OffsetDateTime>,
54
55    #[serde(default, with = "crate::value::optional_datetime")]
56    pub updated_at: Option<OffsetDateTime>,
57}
58
59/* === Enums === */
60
61#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
62pub enum IdentifierError {
63    #[error("identifier must not be empty")]
64    Empty,
65
66    #[error("identifier must start with an ASCII letter, number, or underscore")]
67    InvalidStart,
68
69    #[error("identifier may contain only ASCII letters, numbers, '.', '_', or '-'")]
70    InvalidCharacter,
71}
72
73/* === Traits === */
74
75/* === Implementations === */
76
77impl MemorySize for Metadata {
78    fn memory_size(&self) -> usize {
79        let fields = [
80            (self.identifier.memory_size(), std::mem::size_of_val(&self.identifier)),
81            (self.label.memory_size(), std::mem::size_of_val(&self.label)),
82            (self.description.memory_size(), std::mem::size_of_val(&self.description)),
83            (self.tags.memory_size(), std::mem::size_of_val(&self.tags)),
84            (self.attributes.memory_size(), std::mem::size_of_val(&self.attributes)),
85        ];
86        fields.into_iter().fold(std::mem::size_of::<Self>(), |bytes, (owned, inline)| {
87            if owned == usize::MAX { usize::MAX } else { bytes.saturating_add(owned.saturating_sub(inline)) }
88        })
89    }
90}
91
92impl MemorySize for Identifier {
93    fn memory_size(&self) -> usize {
94        self.0.memory_size()
95    }
96}
97
98impl Identifier {
99    pub fn new(value: impl Into<String>) -> Result<Self, IdentifierError> {
100        let value = value.into();
101        validate_identifier(&value)?;
102
103        Ok(Self(value))
104    }
105
106    pub fn declared(value: impl Into<String>, kind: &str) -> Self {
107        let value = value.into();
108        Self::new(value.clone()).unwrap_or_else(|error| panic!("invalid {kind} identifier {value:?}: {error}"))
109    }
110
111    pub fn as_str(&self) -> &str {
112        &self.0
113    }
114}
115
116impl crate::__private::PostcardValue for Identifier {
117    fn write(&self, bytes: &mut Vec<u8>) {
118        crate::__private::PostcardValue::write(&self.0, bytes);
119    }
120    fn size(&self) -> usize {
121        crate::__private::PostcardValue::size(&self.0)
122    }
123    fn memory(input: &mut crate::__private::PostcardReader<'_>) -> Result<usize, crate::MessageError> {
124        let text: &str = input.value()?;
125        validate_identifier(text).map_err(|_| crate::MessageError::borrowed("invalid identifier"))?;
126        crate::__private::decoding_add(std::mem::size_of::<Self>(), text.len())
127    }
128    fn read(input: &mut crate::__private::PostcardReader<'_>) -> Result<Self, crate::MessageError> {
129        let text: &str = input.value()?;
130        validate_identifier(text).map_err(|_| crate::MessageError::borrowed("invalid identifier"))?;
131        Ok(Self(Box::<str>::from(text).into_string()))
132    }
133}
134
135impl fmt::Display for Identifier {
136    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
137        formatter.write_str(self.as_str())
138    }
139}
140
141impl<'de> Deserialize<'de> for Identifier {
142    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
143        let value = String::deserialize(deserializer)?;
144        Self::new(value).map_err(serde::de::Error::custom)
145    }
146}
147
148impl Metadata {
149    pub fn new(identifier: Identifier, label: impl Into<String>, description: Option<String>) -> Self {
150        Self::definition(identifier, label, description, std::iter::empty::<String>())
151    }
152
153    pub fn definition(
154        identifier: Identifier, label: impl Into<String>, description: Option<String>, tags: impl IntoIterator<Item = impl Into<String>>,
155    ) -> Self {
156        let mut unique_tags = Vec::new();
157        for tag in tags {
158            let tag = tag.into();
159            if !unique_tags.contains(&tag) {
160                unique_tags.push(tag);
161            }
162        }
163
164        Self {
165            identifier,
166            label: label.into(),
167            description,
168            tags: unique_tags,
169            attributes: BTreeMap::new(),
170            created_at: None,
171            updated_at: None,
172        }
173    }
174}
175
176/* === Public Functions === */
177
178pub(crate) const fn validate_identifier(value: &str) -> Result<(), IdentifierError> {
179    let bytes = value.as_bytes();
180    if bytes.is_empty() {
181        return Err(IdentifierError::Empty);
182    }
183    if !(bytes[0].is_ascii_alphanumeric() || bytes[0] == b'_') {
184        return Err(IdentifierError::InvalidStart);
185    }
186    let mut index = 1;
187    while index < bytes.len() {
188        if !(bytes[index].is_ascii_alphanumeric() || matches!(bytes[index], b'.' | b'_' | b'-')) {
189            return Err(IdentifierError::InvalidCharacter);
190        }
191        index += 1;
192    }
193    Ok(())
194}
195
196/* === Private Functions === */
197
198fn unique_tags<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Vec<String>, D::Error> {
199    let tags = Vec::<String>::deserialize(deserializer)?;
200    let mut unique = Vec::new();
201
202    for tag in tags {
203        if !unique.contains(&tag) {
204            unique.push(tag);
205        }
206    }
207
208    Ok(unique)
209}
210
211/* === Tests === */