Skip to main content

qubit_codec/value/
value_codec_id.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5// =============================================================================
6
7//! Stable value-codec identifiers.
8
9use core::borrow::Borrow;
10
11use crate::ValueCodecIdError;
12
13/// A validated, process-independent value-codec identifier.
14///
15/// # Examples
16///
17/// ```
18/// use qubit_codec::ValueCodecId;
19///
20/// let id = ValueCodecId::new("example.u16");
21/// assert_eq!(id.as_str(), "example.u16");
22/// ```
23#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
24pub struct ValueCodecId(&'static str);
25
26impl ValueCodecId {
27    /// Creates an ID from a static string.
28    ///
29    /// # Panics
30    ///
31    /// Panics when `value` violates the point-separated ASCII protocol.
32    #[must_use]
33    pub const fn new(value: &'static str) -> Self {
34        match Self::try_new(value) {
35            Ok(id) => id,
36            Err(_) => panic!("invalid value codec ID"),
37        }
38    }
39
40    /// Validates and creates an ID.
41    ///
42    /// # Errors
43    ///
44    /// Returns the exact stable-ID protocol violation.
45    pub const fn try_new(value: &'static str) -> Result<Self, ValueCodecIdError> {
46        match validate(value) {
47            Ok(()) => Ok(Self(value)),
48            Err(error) => Err(error),
49        }
50    }
51
52    /// Returns the complete stable ID.
53    #[must_use]
54    pub const fn as_str(self) -> &'static str {
55        self.0
56    }
57}
58
59impl Borrow<str> for ValueCodecId {
60    /// Borrows the stable identifier for lookup without allocating.
61    fn borrow(&self) -> &str {
62        self.0
63    }
64}
65
66/// Validates one point-separated ASCII identifier.
67const fn validate(value: &str) -> Result<(), ValueCodecIdError> {
68    let bytes = value.as_bytes();
69    if bytes.is_empty() {
70        return Err(ValueCodecIdError::Empty);
71    }
72    let mut start = 0;
73    let mut index = 0;
74    while index < bytes.len() {
75        if bytes[index] == b'.' {
76            if start == index || index + 1 == bytes.len() {
77                return Err(ValueCodecIdError::EmptySegment);
78            }
79            if let Err(error) = validate_segment(bytes, start, index) {
80                return Err(error);
81            }
82            start = index + 1;
83        }
84        index += 1;
85    }
86    validate_segment(bytes, start, bytes.len())
87}
88
89/// Validates one non-empty ID segment.
90const fn validate_segment(bytes: &[u8], start: usize, end: usize) -> Result<(), ValueCodecIdError> {
91    if start == end || !bytes[start].is_ascii_alphabetic() {
92        return Err(ValueCodecIdError::InvalidSegment);
93    }
94    let mut index = start + 1;
95    while index < end {
96        let byte = bytes[index];
97        if !(byte.is_ascii_alphanumeric() || byte == b'_') {
98            return Err(ValueCodecIdError::InvalidSegment);
99        }
100        index += 1;
101    }
102    Ok(())
103}