Skip to main content

qubit_validator/
validator_id.rs

1// =============================================================================
2//    Copyright (c) 2025 - 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! Stable validator identifiers.
10
11use core::borrow::Borrow;
12
13use crate::ValidatorIdError;
14use crate::internal::DottedIdentifierError;
15use crate::internal::validate_dotted_identifier;
16
17/// A validated, process-independent validator identifier.
18///
19/// The value is non-empty and consists of one or more dot-separated segments.
20/// Each segment starts with an ASCII letter and continues with ASCII letters,
21/// digits, or underscores. Empty segments, whitespace, non-ASCII letters, and
22/// hyphens are rejected. No trimming or case normalization is performed.
23///
24/// # Examples
25///
26/// ```
27/// use qubit_validator::ValidatorId;
28/// use qubit_validator::ValidatorIdError;
29///
30/// let id = ValidatorId::try_new("qubit.text.non_empty")?;
31/// assert_eq!(id.as_str(), "qubit.text.non_empty");
32/// let error = ValidatorId::try_new("qubit..non_empty").unwrap_err();
33/// assert_eq!(error, ValidatorIdError::EmptySegment);
34/// assert_eq!(
35///     ValidatorId::try_new("_custom.invalid"),
36///     Err(ValidatorIdError::InvalidSegment),
37/// );
38/// # Ok::<(), ValidatorIdError>(())
39/// ```
40#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
41pub struct ValidatorId(
42    /// Validated static protocol string.
43    &'static str,
44);
45
46impl ValidatorId {
47    /// Creates a stable ID from a static string.
48    ///
49    /// # Parameters
50    ///
51    /// - `value`: A static string containing the complete protocol value.
52    ///
53    /// # Returns
54    ///
55    /// The validated identifier.
56    ///
57    /// # Panics
58    ///
59    /// Panics when `value` violates the syntax documented on [`ValidatorId`].
60    #[must_use]
61    #[inline]
62    pub const fn new(value: &'static str) -> Self {
63        match Self::try_new(value) {
64            Ok(id) => id,
65            Err(_) => panic!("invalid validator ID"),
66        }
67    }
68
69    /// Validates and creates a stable ID.
70    ///
71    /// # Parameters
72    ///
73    /// - `value`: A static string containing the complete protocol value.
74    ///
75    /// # Returns
76    ///
77    /// The validated identifier when `value` follows the protocol.
78    ///
79    /// # Errors
80    ///
81    /// Returns [`ValidatorIdError::Empty`] for an empty string,
82    /// [`ValidatorIdError::EmptySegment`] for an empty dot-separated segment,
83    /// or [`ValidatorIdError::InvalidSegment`] for a segment with invalid
84    /// characters or an invalid first character.
85    #[inline]
86    pub const fn try_new(value: &'static str) -> Result<Self, ValidatorIdError> {
87        match validate_dotted_identifier(value) {
88            Ok(()) => Ok(Self(value)),
89            Err(DottedIdentifierError::Empty) => Err(ValidatorIdError::Empty),
90            Err(DottedIdentifierError::EmptySegment) => Err(ValidatorIdError::EmptySegment),
91            Err(DottedIdentifierError::InvalidSegment) => Err(ValidatorIdError::InvalidSegment),
92        }
93    }
94
95    /// Returns the complete stable ID.
96    #[must_use]
97    #[inline]
98    pub const fn as_str(self) -> &'static str {
99        self.0
100    }
101}
102
103impl Borrow<str> for ValidatorId {
104    /// Borrows the complete validated identifier as text.
105    ///
106    /// # Returns
107    ///
108    /// The identifier text, borrowed from this value.
109    #[inline]
110    fn borrow(&self) -> &str {
111        self.0
112    }
113}