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 /// # Parameters
127 ///
128 /// * `id` - Stable identifier to look up.
129 ///
130 /// # Returns
131 ///
132 /// Returns `Some` for a registered ID and `None` when the ID is unknown.
133 #[inline]
134 pub fn get(&self, id: &str) -> Option<&ValidatorRegistration> {
135 self.registrations
136 .binary_search_by(|registration| registration.id().as_str().cmp(id))
137 .ok()
138 .and_then(|index| self.registrations.get(index))
139 }
140
141 /// Returns registrations sorted by stable ID.
142 ///
143 /// # Returns
144 ///
145 /// Returns a borrowed slice of all registrations in stable-ID order.
146 #[must_use]
147 #[inline]
148 pub fn registrations(&self) -> &[ValidatorRegistration] {
149 &self.registrations
150 }
151
152 /// Binds a rule by ID and input shape.
153 ///
154 /// # Parameters
155 ///
156 /// * `id` - Stable identifier of the registered rule to bind.
157 /// * `input` - Input shape that the bound rule must accept.
158 /// * `params` - Named arguments supplied to the rule.
159 ///
160 /// # Errors
161 ///
162 /// Returns `MissingRule` when `id` has no registration. This error has
163 /// no resolved rule ID; callers that need the requested text retain `id`.
164 /// Signature and preparation errors for a found rule carry its ID.
165 ///
166 /// # Returns
167 ///
168 /// Returns the bound validator when the registration exists and accepts
169 /// the supplied input and parameters.
170 pub fn bind(
171 &self,
172 id: &str,
173 input: InputType,
174 params: &[NamedValidationArgument<'_>],
175 ) -> Result<BoundValidator, BindError> {
176 let registration = self.get(id).ok_or_else(|| BindError::new(BindErrorKind::MissingRule))?;
177 registration
178 .descriptor()
179 .bind_for_validated(registration.id(), input, params)
180 .map_err(|error| error.with_rule(registration.id()))
181 }
182
183 /// Sorts and validates owned registrations before freezing the registry.
184 ///
185 /// # Parameters
186 ///
187 /// * `registrations` - Owned registrations to sort and validate.
188 ///
189 /// # Errors
190 ///
191 /// Returns duplicate-ID metadata or the first invalid descriptor.
192 ///
193 /// # Returns
194 ///
195 /// Returns a registry containing the validated registrations sorted by ID.
196 fn build(mut registrations: Vec<ValidatorRegistration>) -> Result<Self, ValidatorRegistryError> {
197 registrations.sort_by_key(|registration| (registration.id(), registration.source()));
198 let mut index = 0;
199 while index < registrations.len() {
200 let id = registrations[index].id();
201 let end = index
202 + registrations[index..]
203 .iter()
204 .take_while(|registration| registration.id() == id)
205 .count();
206 if end - index > 1 {
207 let sources = registrations[index..end]
208 .iter()
209 .map(ValidatorRegistration::source)
210 .collect();
211 return Err(ValidatorRegistryError::DuplicateId {
212 id: id.as_str(),
213 sources,
214 });
215 }
216 index = end;
217 }
218 for registration in ®istrations {
219 if let Err(error) = registration.descriptor().validate_definition() {
220 return Err(ValidatorRegistryError::InvalidDescriptor {
221 id: registration.id(),
222 registration_source: registration.source(),
223 kind: error.kind(),
224 dependency: error.dependency().map(str::to_owned),
225 });
226 }
227 }
228 Ok(Self {
229 registrations: registrations.into_boxed_slice(),
230 })
231 }
232}