1#![forbid(unsafe_code)]
2#![deny(missing_docs)]
3#![cfg_attr(not(feature = "std"), no_std)]
4extern crate alloc;
7use alloc::string::{String, ToString};
8use serde::{Deserialize, Serialize};
9
10#[cfg(feature = "strict")]
11use regex::Regex;
12
13#[macro_export]
15macro_rules! newtype_id {
16 ($name:ident) => {
17 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
18 #[serde(transparent)]
19 #[doc = concat!(stringify!($name), " identifier newtype (string, trimmed; strict regex when feature=\"strict\").")]
20 pub struct $name(pub String);
21 impl core::fmt::Display for $name {
22 fn fmt(&self, f:&mut core::fmt::Formatter<'_>)->core::fmt::Result{ f.write_str(&self.0) }
23 }
24 impl core::str::FromStr for $name {
25 type Err = &'static str;
26 fn from_str(s:&str)->Result<Self,Self::Err>{
27 let s=s.trim();
28 if s.is_empty(){return Err("empty");}
29 #[cfg(feature="strict")]
30 {
31 static PAT: &str = r"^[A-Za-z0-9._:-]{1,128}$";
33 let re = Regex::new(PAT).unwrap();
34 if !re.is_match(s){ return Err("invalid chars"); }
35 }
36 Ok(Self(s.to_string()))
37 }
38 }
39 };
40}
41
42newtype_id!(AppId);
43newtype_id!(TenantId);
44newtype_id!(NodeId);
45newtype_id!(ActorId);
46newtype_id!(TraceId);
47
48#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
50pub struct Dim(pub u16);
51impl Dim {
52 pub fn parse(s: &str) -> Result<Self, &'static str> {
58 let s = s.trim();
59 s.strip_prefix("0x").map_or_else(
60 || s.parse::<u16>().map(Dim).map_err(|_| "bad dec"),
61 |h| u16::from_str_radix(h, 16).map(Dim).map_err(|_| "bad hex"),
62 )
63 }
64 pub fn from_hex(h: &str) -> Result<Self, &'static str> {
70 u16::from_str_radix(h, 16).map(Dim).map_err(|_| "bad hex")
71 }
72 #[must_use]
74 pub fn to_hex(self) -> alloc::string::String {
75 alloc::format!("0x{:04X}", self.0)
76 }
77 #[must_use]
79 pub const fn as_u16(self) -> u16 {
80 self.0
81 }
82}
83
84#[cfg(feature = "ulid")]
85pub mod gen {
87 use super::{ActorId, TraceId};
88 #[must_use]
90 pub fn new_ulid_trace() -> TraceId {
91 TraceId(ulid::Ulid::new().to_string())
92 }
93 #[must_use]
95 pub fn new_ulid_actor() -> ActorId {
96 ActorId(ulid::Ulid::new().to_string())
97 }
98}
99
100use core::fmt;
105use serde::{Deserializer, Serializer};
106use thiserror::Error;
107
108#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
110pub struct Cid32(pub [u8; 32]);
111
112impl fmt::Debug for Cid32 {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 write!(f, "Cid32({})", hex::encode(self.0))
115 }
116}
117impl fmt::Display for Cid32 {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 f.write_str(&hex::encode(self.0))
120 }
121}
122impl Serialize for Cid32 {
123 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
124 s.serialize_str(&hex::encode(self.0))
125 }
126}
127impl<'de> Deserialize<'de> for Cid32 {
128 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
129 let s = <&str>::deserialize(d)?;
130 let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
131 if bytes.len() != 32 {
132 return Err(serde::de::Error::custom("Cid32 must be 32 bytes"));
133 }
134 let mut out = [0u8; 32];
135 out.copy_from_slice(&bytes);
136 Ok(Cid32(out))
137 }
138}
139impl Cid32 {
140 pub fn from_hex(s: &str) -> Result<Self, AtomError> {
146 let bytes = hex::decode(s).map_err(|_| AtomError::Hex)?;
147 if bytes.len() != 32 {
148 return Err(AtomError::Length { expected: 32, actual: bytes.len() });
149 }
150 let mut out = [0u8; 32];
151 out.copy_from_slice(&bytes);
152 Ok(Cid32(out))
153 }
154 #[must_use]
156 pub fn to_hex(&self) -> String {
157 hex::encode(self.0)
158 }
159}
160
161#[derive(Clone, Copy, PartialEq, Eq, Hash, Default)]
163pub struct PublicKeyBytes(pub [u8; 32]);
164
165impl fmt::Debug for PublicKeyBytes {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 write!(f, "PublicKeyBytes({})", hex::encode(self.0))
168 }
169}
170impl fmt::Display for PublicKeyBytes {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.write_str(&hex::encode(self.0))
173 }
174}
175impl Serialize for PublicKeyBytes {
176 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
177 s.serialize_str(&hex::encode(self.0))
178 }
179}
180impl<'de> Deserialize<'de> for PublicKeyBytes {
181 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
182 let s = <&str>::deserialize(d)?;
183 let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
184 if bytes.len() != 32 {
185 return Err(serde::de::Error::custom("PublicKeyBytes must be 32 bytes"));
186 }
187 let mut out = [0u8; 32];
188 out.copy_from_slice(&bytes);
189 Ok(PublicKeyBytes(out))
190 }
191}
192
193#[derive(Clone, Copy, PartialEq, Eq, Hash)]
195pub struct SignatureBytes(pub [u8; 64]);
196
197impl Default for SignatureBytes {
198 fn default() -> Self {
199 Self([0u8; 64])
200 }
201}
202
203impl fmt::Debug for SignatureBytes {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 write!(f, "SignatureBytes({}..)", &hex::encode(self.0)[..16])
206 }
207}
208impl fmt::Display for SignatureBytes {
209 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210 f.write_str(&hex::encode(self.0))
211 }
212}
213impl Serialize for SignatureBytes {
214 fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
215 s.serialize_str(&hex::encode(self.0))
216 }
217}
218impl<'de> Deserialize<'de> for SignatureBytes {
219 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
220 let s = <&str>::deserialize(d)?;
221 let bytes = hex::decode(s).map_err(serde::de::Error::custom)?;
222 if bytes.len() != 64 {
223 return Err(serde::de::Error::custom("SignatureBytes must be 64 bytes"));
224 }
225 let mut out = [0u8; 64];
226 out.copy_from_slice(&bytes);
227 Ok(SignatureBytes(out))
228 }
229}
230
231#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
233pub struct Intent {
234 pub raw: String,
236 pub canon: alloc::vec::Vec<u8>,
238}
239
240impl Intent {
241 #[must_use]
243 pub fn from_raw(raw: impl Into<String>) -> Self {
244 let raw = raw.into();
245 let canon = normalize_ws(&raw).into_bytes();
246 Self { raw, canon }
247 }
248 #[must_use]
250 pub fn as_bytes(&self) -> &[u8] {
251 &self.canon
252 }
253}
254
255fn normalize_ws(s: &str) -> String {
257 let mut out = String::with_capacity(s.len());
258 let mut prev_space = false;
259 for ch in s.chars() {
260 let is_space = ch.is_whitespace();
261 if is_space {
262 if !prev_space {
263 out.push(' ');
264 }
265 } else {
266 out.push(ch);
267 }
268 prev_space = is_space;
269 }
270 out.trim().to_string()
271}
272
273#[derive(Debug, Error)]
275pub enum AtomError {
276 #[error("hex decode error")]
278 Hex,
279 #[error("length mismatch: expected {expected}, got {actual}")]
281 Length {
282 expected: usize,
284 actual: usize,
286 },
287 #[error("invalid intent")]
289 InvalidIntent,
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn cid32_roundtrip() {
298 let c = Cid32([0xAB; 32]);
299 let j = serde_json::to_string(&c).unwrap();
300 assert_eq!(j.len(), 66); let de: Cid32 = serde_json::from_str(&j).unwrap();
302 assert_eq!(c.0, de.0);
303 }
304
305 #[test]
306 fn intent_ws_canonical() {
307 let i1 = Intent::from_raw(" hello world ");
308 let i2 = Intent::from_raw("hello world");
309 assert_eq!(i1.canon, i2.canon);
310 }
311
312 #[test]
313 fn pk_sig_roundtrip() {
314 let pk = PublicKeyBytes([0x22; 32]);
315 let sig = SignatureBytes([0x33; 64]);
316 let jp = serde_json::to_string(&pk).unwrap();
317 let js = serde_json::to_string(&sig).unwrap();
318 let _dpk: PublicKeyBytes = serde_json::from_str(&jp).unwrap();
319 let _ds: SignatureBytes = serde_json::from_str(&js).unwrap();
320 }
321}