harmos_domain/
metadata.rs1mod codec;
13
14use std::{collections::BTreeMap, fmt};
20use serde::{Deserialize, Serialize};
22use time::OffsetDateTime;
23use crate::{MemorySize, Value};
25
26#[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#[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
73impl 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
176pub(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
196fn 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