Skip to main content

harmos_domain/
identity.rs

1/* ***
2 *
3 * Harmos
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 *
7 * ***
8 */
9
10/* === Modules === */
11
12/* === Re-Exports === */
13
14/* === Imports === */
15
16// Standard Library Imports
17use std::fmt;
18
19// Third Party Imports
20use serde::{Deserialize, Serialize};
21
22// Local Imports
23
24/* === Constants === */
25
26/* === Macros === */
27
28/* === Type Aliases === */
29
30/* === Structs === */
31
32#[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
42/* === Enums === */
43
44/* === Traits === */
45
46// The Stable Identity And Semantic Version Of A Declared Definition.
47pub trait Identified {
48    fn definition() -> (DefinitionId, u32);
49}
50
51/* === Implementations === */
52
53impl 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
63// The Unit Definition Of A Catalog That Declares Nothing.
64impl 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
76/* === Public Functions === */
77
78pub 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/* === Private Functions === */
140
141/* === Tests === */