Skip to main content

qubit_codec/value/
value_codec_registry.rs

1// =============================================================================
2//    Copyright (c) 2026 Haixing Hu.
3//
4//    SPDX-License-Identifier: Apache-2.0
5//
6//    Licensed under the Apache License, Version 2.0.
7// =============================================================================
8
9//! Immutable local and process-wide value-codec registries.
10
11use std::collections::BTreeMap;
12use std::sync::OnceLock;
13
14use inventory::iter;
15
16use crate::ValueBytesCodecDescriptor;
17use crate::ValueBytesCodecRegistrationFactory;
18use crate::ValueCodecId;
19use crate::ValueCodecRegistration;
20use crate::ValueCodecRegistryError;
21use crate::ValueStringCodecDescriptor;
22use crate::ValueStringCodecRegistrationFactory;
23
24/// An immutable string-wire value-codec registry sorted by stable ID.
25pub type ValueStringCodecRegistry = ValueCodecRegistry<ValueStringCodecDescriptor>;
26
27/// An immutable bytes-wire value-codec registry sorted by stable ID.
28pub type ValueBytesCodecRegistry = ValueCodecRegistry<ValueBytesCodecDescriptor>;
29
30/// An immutable value-codec registry sorted by stable ID.
31///
32/// # Type Parameters
33///
34/// - `D`: Copyable, static descriptor identifying one wire representation.
35///
36/// # Examples
37///
38/// ```
39/// use qubit_codec::ValueCodecRegistry;
40/// use qubit_codec::ValueStringCodecDescriptor;
41/// use qubit_codec::ValueStringCodecRegistry;
42///
43/// let registry: ValueCodecRegistry<ValueStringCodecDescriptor> =
44///     ValueStringCodecRegistry::empty();
45/// assert!(registry.get("missing.example").is_none());
46/// ```
47#[derive(Debug)]
48pub struct ValueCodecRegistry<D: 'static> {
49    /// Owned registrations sorted once by stable ID during construction.
50    registrations: Box<[ValueCodecRegistration<D>]>,
51    /// Stable ID to position mapping into the immutable registration slice.
52    indices: BTreeMap<ValueCodecId, usize>,
53}
54
55impl<D: Copy + 'static> ValueCodecRegistry<D> {
56    /// Builds a local registry from static registrations.
57    ///
58    /// # Parameters
59    ///
60    /// - `registrations`: Static entries copied into this registry; input order
61    ///   is ignored.
62    ///
63    /// # Returns
64    ///
65    /// An owned, immutable registry sorted by ID, independent of the iterator.
66    ///
67    /// # Errors
68    ///
69    /// Returns a duplicate-ID error when multiple entries claim one ID.
70    pub fn from_registrations(
71        registrations: impl IntoIterator<Item = &'static ValueCodecRegistration<D>>,
72    ) -> Result<Self, ValueCodecRegistryError> {
73        Self::build(registrations.into_iter().copied().collect())
74    }
75
76    /// Returns an empty local registry.
77    ///
78    /// # Returns
79    ///
80    /// A local registry with no registrations or indexed IDs.
81    #[must_use]
82    #[inline]
83    pub fn empty() -> Self {
84        Self {
85            registrations: Box::new([]),
86            indices: BTreeMap::new(),
87        }
88    }
89
90    /// Freezes registrations and rejects duplicate IDs.
91    ///
92    /// # Parameters
93    ///
94    /// - `registrations`: Owned entries sorted in place before indexing.
95    ///
96    /// # Returns
97    ///
98    /// An immutable registry owning the sorted entries and their lookup index.
99    ///
100    /// # Errors
101    ///
102    /// Returns `DuplicateId` with the lexically first conflicting ID and all
103    /// of its sources sorted by source fields. Repeated identical sources are
104    /// retained because each registration is reported.
105    fn build(mut registrations: Vec<ValueCodecRegistration<D>>) -> Result<Self, ValueCodecRegistryError> {
106        registrations.sort_by_key(ValueCodecRegistration::id);
107        for (index, pair) in registrations.windows(2).enumerate() {
108            if pair[0].id() == pair[1].id() {
109                let id = pair[0].id();
110                let mut sources = registrations[index..]
111                    .iter()
112                    .take_while(|registration| registration.id() == id)
113                    .map(ValueCodecRegistration::source)
114                    .collect::<Vec<_>>();
115                sources.sort_unstable();
116                return Err(ValueCodecRegistryError::DuplicateId {
117                    id: id.as_str(),
118                    sources,
119                });
120            }
121        }
122        let indices = registrations
123            .iter()
124            .enumerate()
125            .map(|(index, registration)| (registration.id(), index))
126            .collect();
127        Ok(Self {
128            registrations: registrations.into_boxed_slice(),
129            indices,
130        })
131    }
132
133    /// Finds a registration by stable ID.
134    ///
135    /// # Parameters
136    ///
137    /// - `id`: Exact stable ID to look up, without normalization.
138    ///
139    /// # Returns
140    ///
141    /// `Some` borrows the matching registration from this registry; `None`
142    /// means the ID is absent. Lookup uses a logarithmic tree search.
143    #[must_use]
144    #[inline]
145    pub fn get(&self, id: &str) -> Option<&ValueCodecRegistration<D>> {
146        let index = *self.indices.get(id)?;
147        self.registrations.get(index)
148    }
149
150    /// Returns registrations in deterministic ID order.
151    ///
152    /// # Returns
153    ///
154    /// The immutable registration slice borrowed from this registry without
155    /// allocation.
156    #[must_use]
157    #[inline]
158    pub fn registrations(&self) -> &[ValueCodecRegistration<D>] {
159        &self.registrations
160    }
161}
162
163impl ValueStringCodecRegistry {
164    /// Initializes and returns the process-wide linked string registry.
165    ///
166    /// Initialization is synchronized and cached, including construction
167    /// failures.
168    ///
169    /// # Returns
170    ///
171    /// A shared static registry containing all linked string registrations.
172    ///
173    /// # Errors
174    ///
175    /// Returns the cached construction error when linked registrations
176    /// conflict. The error identifies the lexically first duplicate ID and
177    /// all declarations for that ID in source-field order.
178    pub fn try_global() -> Result<&'static Self, ValueCodecRegistryError> {
179        // Caches both successful construction and duplicate-ID failures.
180        static REGISTRY: OnceLock<Result<ValueStringCodecRegistry, ValueCodecRegistryError>> = OnceLock::new();
181        match REGISTRY.get_or_init(|| {
182            let registrations = iter::<ValueStringCodecRegistrationFactory>
183                .into_iter()
184                .map(|factory| (factory.0)())
185                .collect();
186            Self::build(registrations)
187        }) {
188            Ok(registry) => Ok(registry),
189            Err(error) => Err(error.clone()),
190        }
191    }
192
193    /// Returns the process-wide string registry or panics with a stable
194    /// diagnostic.
195    ///
196    /// # Returns
197    ///
198    /// A shared static registry, initialized at most once by `try_global`.
199    ///
200    /// # Panics
201    ///
202    /// Panics when linked registrations conflict.
203    #[must_use]
204    pub fn global() -> &'static Self {
205        Self::try_global().unwrap_or_else(|error| panic!("invalid global value string codec registry: {error}"))
206    }
207}
208
209impl ValueBytesCodecRegistry {
210    /// Initializes and returns the process-wide linked bytes registry.
211    ///
212    /// Initialization is synchronized and cached, including construction
213    /// failures.
214    ///
215    /// # Returns
216    ///
217    /// A shared static registry containing all linked bytes registrations.
218    ///
219    /// # Errors
220    ///
221    /// Returns the cached construction error when linked registrations
222    /// conflict. The error identifies the lexically first duplicate ID and
223    /// all declarations for that ID in source-field order.
224    pub fn try_global() -> Result<&'static Self, ValueCodecRegistryError> {
225        // Caches both successful construction and duplicate-ID failures.
226        static REGISTRY: OnceLock<Result<ValueBytesCodecRegistry, ValueCodecRegistryError>> = OnceLock::new();
227        match REGISTRY.get_or_init(|| {
228            let registrations = iter::<ValueBytesCodecRegistrationFactory>
229                .into_iter()
230                .map(|factory| (factory.0)())
231                .collect();
232            Self::build(registrations)
233        }) {
234            Ok(registry) => Ok(registry),
235            Err(error) => Err(error.clone()),
236        }
237    }
238
239    /// Returns the process-wide bytes registry or panics with a stable
240    /// diagnostic.
241    ///
242    /// # Returns
243    ///
244    /// A shared static registry, initialized at most once by `try_global`.
245    ///
246    /// # Panics
247    ///
248    /// Panics when linked registrations conflict.
249    #[must_use]
250    pub fn global() -> &'static Self {
251        Self::try_global().unwrap_or_else(|error| panic!("invalid global value bytes codec registry: {error}"))
252    }
253}