qubit_validator/registry/validator_registry/validator_registry.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//! Registry type and implementation for multi-signature validator definitions.
10
11#[cfg(feature = "inventory")]
12use std::sync::OnceLock;
13
14#[cfg(feature = "inventory")]
15use inventory::iter;
16
17use super::super::ValidatorRegistration;
18use crate::NamedValidationArgument;
19use crate::ValidatorRegistryError;
20use crate::binding::BindError;
21use crate::binding::BindErrorKind;
22use crate::binding::BoundValidator;
23use crate::binding::InputType;
24
25/// A deterministic local registry containing one definition per stable ID.
26///
27/// # Examples
28///
29/// ```
30/// use qubit_validator::ValidatorRegistry;
31///
32/// let registry = ValidatorRegistry::empty();
33/// assert!(registry.registrations().is_empty());
34/// assert!(registry.get("example.unknown").is_none());
35/// ```
36#[derive(Debug)]
37pub struct ValidatorRegistry {
38 /// Registrations sorted by stable identifier.
39 registrations: Box<[ValidatorRegistration]>,
40}
41
42impl ValidatorRegistry {
43 /// Builds a registry by taking ownership of registration values.
44 ///
45 /// # Type Parameters
46 ///
47 /// * `I` - An iterable whose items can be converted into registrations.
48 ///
49 /// # Parameters
50 ///
51 /// * `registrations` - Values to validate and store in the registry.
52 ///
53 /// # Errors
54 ///
55 /// Returns every source which declared a duplicated stable ID, or the
56 /// identifier and cause of the first invalid descriptor.
57 ///
58 /// # Returns
59 ///
60 /// Returns a registry containing the validated registrations sorted by ID.
61 pub fn from_registrations<I>(registrations: I) -> Result<Self, ValidatorRegistryError>
62 where
63 I: IntoIterator,
64 I::Item: Into<ValidatorRegistration>,
65 {
66 Self::build(registrations.into_iter().map(Into::into).collect())
67 }
68
69 /// Returns an empty registry.
70 ///
71 /// # Returns
72 ///
73 /// Returns a registry with no registrations.
74 #[must_use]
75 #[inline]
76 pub fn empty() -> Self {
77 Self {
78 registrations: Box::new([]),
79 }
80 }
81
82 /// Initializes and returns the process-wide inventory registry.
83 ///
84 /// # Errors
85 ///
86 /// Returns the cached error when linked registrations contain a duplicate
87 /// ID or an invalid descriptor. This method is available only with the
88 /// `inventory` feature.
89 ///
90 /// # Returns
91 ///
92 /// Returns the process-wide registry when initialization succeeds.
93 #[cfg(feature = "inventory")]
94 pub fn try_global() -> Result<&'static Self, ValidatorRegistryError> {
95 static REGISTRY: OnceLock<Result<ValidatorRegistry, ValidatorRegistryError>> = OnceLock::new();
96 match REGISTRY.get_or_init(|| {
97 let registrations = iter::<crate::ValidatorRegistrationFactory>
98 .into_iter()
99 .map(|factory| (factory.0)())
100 .collect();
101 Self::build(registrations)
102 }) {
103 Ok(registry) => Ok(registry),
104 Err(error) => Err(error.clone()),
105 }
106 }
107
108 /// Returns the process-wide inventory registry.
109 ///
110 /// # Panics
111 ///
112 /// Panics when linked registrations contain duplicate IDs or an invalid
113 /// descriptor. This method is available only with the `inventory` feature.
114 ///
115 /// # Returns
116 ///
117 /// Returns the initialized process-wide registry.
118 #[cfg(feature = "inventory")]
119 #[must_use]
120 pub fn global() -> &'static Self {
121 Self::try_global().unwrap_or_else(|error| panic!("invalid global validator registry: {error}"))
122 }
123
124 /// Finds a registration by stable ID.
125 ///
126 /// # Returns
127 ///
128 /// Returns `Some` for a registered ID and `None` when the ID is unknown.
129 #[inline]
130 pub fn get(&self, id: &str) -> Option<&ValidatorRegistration> {
131 self.registrations
132 .binary_search_by(|registration| registration.id().as_str().cmp(id))
133 .ok()
134 .and_then(|index| self.registrations.get(index))
135 }
136
137 /// Returns registrations sorted by stable ID.
138 ///
139 /// # Returns
140 ///
141 /// Returns a borrowed slice of all registrations in stable-ID order.
142 #[must_use]
143 #[inline]
144 pub fn registrations(&self) -> &[ValidatorRegistration] {
145 &self.registrations
146 }
147
148 /// Binds a rule by ID and input shape.
149 ///
150 /// # Parameters
151 ///
152 /// * `id` - Stable identifier of the registered rule to bind.
153 /// * `input` - Input shape that the bound rule must accept.
154 /// * `params` - Named arguments supplied to the rule.
155 ///
156 /// # Errors
157 ///
158 /// Returns `MissingRule` when `id` has no registration. This error has
159 /// no resolved rule ID; callers that need the requested text retain `id`.
160 /// Signature and preparation errors for a found rule carry its ID.
161 ///
162 /// # Returns
163 ///
164 /// Returns the bound validator when the registration exists and accepts
165 /// the supplied input and parameters.
166 pub fn bind(
167 &self,
168 id: &str,
169 input: InputType,
170 params: &[NamedValidationArgument<'_>],
171 ) -> Result<BoundValidator, BindError> {
172 let registration = self.get(id).ok_or_else(|| BindError::new(BindErrorKind::MissingRule))?;
173 registration
174 .descriptor()
175 .bind_for_validated(registration.id(), input, params)
176 .map_err(|error| error.with_rule(registration.id()))
177 }
178
179 /// Sorts and validates owned registrations before freezing the registry.
180 ///
181 /// # Parameters
182 ///
183 /// * `registrations` - Owned registrations to sort and validate.
184 ///
185 /// # Errors
186 ///
187 /// Returns duplicate-ID metadata or the first invalid descriptor.
188 ///
189 /// # Returns
190 ///
191 /// Returns a registry containing the validated registrations sorted by ID.
192 fn build(mut registrations: Vec<ValidatorRegistration>) -> Result<Self, ValidatorRegistryError> {
193 registrations.sort_by_key(|registration| (registration.id(), registration.source()));
194 let mut index = 0;
195 while index < registrations.len() {
196 let id = registrations[index].id();
197 let end = index
198 + registrations[index..]
199 .iter()
200 .take_while(|registration| registration.id() == id)
201 .count();
202 if end - index > 1 {
203 let sources = registrations[index..end]
204 .iter()
205 .map(ValidatorRegistration::source)
206 .collect();
207 return Err(ValidatorRegistryError::DuplicateId {
208 id: id.as_str(),
209 sources,
210 });
211 }
212 index = end;
213 }
214 for registration in ®istrations {
215 if let Err(error) = registration.descriptor().validate_definition() {
216 return Err(ValidatorRegistryError::InvalidDescriptor {
217 id: registration.id(),
218 registration_source: registration.source(),
219 kind: error.kind(),
220 dependency: error.dependency().map(str::to_owned),
221 });
222 }
223 }
224 Ok(Self {
225 registrations: registrations.into_boxed_slice(),
226 })
227 }
228}