harmos_domain/
identity.rs1use std::fmt;
18
19use serde::{Deserialize, Serialize};
21
22#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
33#[serde(transparent)]
34pub struct DefinitionId(String);
35
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct Requirement {
38 pub id: String,
39 pub version: semver::VersionReq,
40}
41
42pub trait Identified {
48 fn definition() -> (DefinitionId, u32);
49}
50
51impl DefinitionId {
54 pub fn new(id: impl Into<String>) -> Self {
55 Self(id.into())
56 }
57
58 pub fn as_str(&self) -> &str {
59 &self.0
60 }
61}
62
63impl Identified for () {
65 fn definition() -> (DefinitionId, u32) {
66 (DefinitionId::new("harmos:unit"), 1)
67 }
68}
69
70impl fmt::Display for DefinitionId {
71 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
72 formatter.write_str(&self.0)
73 }
74}
75
76pub const fn artifact_subset(id: &str) -> bool {
79 let bytes = id.as_bytes();
80 if bytes.is_empty() || !bytes[0].is_ascii_lowercase() {
81 return false;
82 }
83
84 let mut index = 0;
85 while index < bytes.len() {
86 let byte = bytes[index];
87 if !(byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'-' | b'.' | b'_')) {
88 return false;
89 }
90 index += 1;
91 }
92
93 true
94}
95
96pub const fn qualified_len(owner: &str, name: &str) -> usize {
97 owner.len() + 1 + name.len()
98}
99
100pub const fn qualify<const N: usize>(owner: &str, name: &str) -> [u8; N] {
101 assert!(N == qualified_len(owner, name));
102 let mut output = [0; N];
103 let mut cursor = 0;
104 let owner = owner.as_bytes();
105 let mut index = 0;
106 while index < owner.len() {
107 output[cursor] = owner[index];
108 cursor += 1;
109 index += 1;
110 }
111 output[cursor] = b'.';
112 cursor += 1;
113 let name = name.as_bytes();
114 let mut index = 0;
115 while index < name.len() {
116 output[cursor] = name[index];
117 cursor += 1;
118 index += 1;
119 }
120
121 output
122}
123
124pub const fn qualified(bytes: &'static [u8]) -> &'static str {
125 match core::str::from_utf8(bytes) {
126 Ok(id) => id,
127 Err(_) => panic!("a qualified id is built from two portable names"),
128 }
129}
130
131pub fn stream_subset(name: &str) -> bool {
132 name.split('.').all(|segment| {
133 let mut characters = segment.chars();
134 characters.next().is_some_and(|first| first == '_' || first.is_ascii_alphabetic())
135 && characters.all(|character| character == '_' || character == '-' || character.is_ascii_alphanumeric())
136 })
137}
138
139