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//! Local immutable value-codec registry.
10
11use std::collections::BTreeMap;
12
13use crate::ValueCodecId;
14use crate::ValueCodecRegistration;
15use crate::ValueCodecRegistryError;
16
17// Implements process-wide lookup for the bytes-wire specialization.
18#[path = "value_codec_registry/value_bytes_codec_registry.rs"]
19mod value_bytes_codec_registry;
20// Implements process-wide lookup for the string-wire specialization.
21#[path = "value_codec_registry/value_string_codec_registry.rs"]
22mod value_string_codec_registry;
23
24pub use value_bytes_codec_registry::ValueBytesCodecRegistry;
25pub use value_string_codec_registry::ValueStringCodecRegistry;
26
27/// An immutable value-codec registry sorted by stable ID.
28///
29/// # Type Parameters
30///
31/// - `D`: Static descriptor type identifying one wire representation. The
32///   registry API is implemented for descriptors that also satisfy `Copy`.
33///
34/// # Examples
35///
36/// ```
37/// use qubit_codec::ValueCodecRegistry;
38/// use qubit_codec::ValueStringCodecDescriptor;
39/// use qubit_codec::ValueStringCodecRegistry;
40///
41/// let registry: ValueCodecRegistry<ValueStringCodecDescriptor> =
42///     ValueStringCodecRegistry::empty();
43/// assert!(registry.get("missing.example").is_none());
44/// ```
45#[derive(Debug)]
46pub struct ValueCodecRegistry<D: 'static> {
47    /// Owned registrations sorted once by stable ID during construction.
48    registrations: Box<[ValueCodecRegistration<D>]>,
49    /// Stable ID to position mapping into the immutable registration slice.
50    indices: BTreeMap<ValueCodecId, usize>,
51}
52
53impl<D: Copy + 'static> ValueCodecRegistry<D> {
54    /// Builds a local registry from static registrations without consulting
55    /// or modifying the process-wide registry.
56    ///
57    /// # Parameters
58    ///
59    /// - `registrations`: Static entries copied into this registry; input order
60    ///   is ignored.
61    ///
62    /// # Returns
63    ///
64    /// An owned, immutable registry sorted by ID, independent of the iterator.
65    ///
66    /// # Errors
67    ///
68    /// Returns a duplicate-ID error when multiple entries claim one ID.
69    ///
70    /// # Examples
71    ///
72    /// This example uses a local static registration; it does not submit an
73    /// entry to the process-wide inventory.
74    ///
75    /// ```
76    /// use std::convert::Infallible;
77    /// use qubit_codec::ValueCodecId;
78    /// use qubit_codec::ValueCodecRegistration;
79    /// use qubit_codec::ValueCodecRegistrationSource;
80    /// use qubit_codec::ValueDecoder;
81    /// use qubit_codec::ValueEncoder;
82    /// use qubit_codec::ValueStringCodecDescriptor;
83    /// use qubit_codec::ValueStringCodecRegistration;
84    /// use qubit_codec::ValueStringCodecRegistry;
85    ///
86    /// #[derive(Default)]
87    /// struct LocalText;
88    ///
89    /// impl ValueEncoder<String> for LocalText {
90    ///     type Output = String;
91    ///     type Error = Infallible;
92    ///
93    ///     fn encode(&mut self, input: &String) -> Result<String, Infallible> {
94    ///         Ok(input.clone())
95    ///     }
96    /// }
97    ///
98    /// impl ValueDecoder<str> for LocalText {
99    ///     type Output = String;
100    ///     type Error = Infallible;
101    ///
102    ///     fn decode(&mut self, input: &str) -> Result<String, Infallible> {
103    ///         Ok(input.to_owned())
104    ///     }
105    /// }
106    ///
107    /// static DESCRIPTOR: ValueStringCodecDescriptor =
108    ///     ValueStringCodecDescriptor::of::<LocalText, String>();
109    /// static REGISTRATION: ValueStringCodecRegistration = ValueCodecRegistration::new(
110    ///     ValueCodecId::new("example.local.text"),
111    ///     &DESCRIPTOR,
112    ///     ValueCodecRegistrationSource::new("local-example", "example", file!(), line!()),
113    /// );
114    ///
115    /// let registry = ValueStringCodecRegistry::from_registrations([&REGISTRATION])
116    ///     .expect("unique local registration");
117    /// let descriptor = registry.get("example.local.text")
118    ///     .expect("registered locally").descriptor();
119    /// let encoded = descriptor.encode(&String::from("hello")).expect("encode");
120    /// assert_eq!("hello", encoded);
121    /// let decoded = descriptor.decode(&encoded).expect("decode");
122    /// assert_eq!("hello", *decoded.downcast::<String>().expect("String output"));
123    /// assert!(registry.get("example.missing").is_none());
124    /// ```
125    pub fn from_registrations(
126        registrations: impl IntoIterator<Item = &'static ValueCodecRegistration<D>>,
127    ) -> Result<Self, ValueCodecRegistryError> {
128        Self::build(registrations.into_iter().copied().collect())
129    }
130
131    /// Returns an empty local registry.
132    ///
133    /// # Returns
134    ///
135    /// A local registry with no registrations or indexed IDs.
136    #[must_use]
137    #[inline]
138    pub fn empty() -> Self {
139        Self {
140            registrations: Box::new([]),
141            indices: BTreeMap::new(),
142        }
143    }
144
145    /// Freezes registrations and rejects duplicate IDs.
146    ///
147    /// # Parameters
148    ///
149    /// - `registrations`: Owned entries sorted in place before indexing.
150    ///
151    /// # Returns
152    ///
153    /// An immutable registry owning the sorted entries and their lookup index.
154    ///
155    /// # Errors
156    ///
157    /// Returns `DuplicateId` with the lexically first conflicting ID and all
158    /// of its sources sorted by source fields. Repeated identical sources are
159    /// retained because each registration is reported.
160    pub(super) fn build(mut registrations: Vec<ValueCodecRegistration<D>>) -> Result<Self, ValueCodecRegistryError> {
161        registrations.sort_by_key(ValueCodecRegistration::id);
162        for (index, pair) in registrations.windows(2).enumerate() {
163            if pair[0].id() == pair[1].id() {
164                let id = pair[0].id();
165                let mut sources = registrations[index..]
166                    .iter()
167                    .take_while(|registration| registration.id() == id)
168                    .map(ValueCodecRegistration::source)
169                    .collect::<Vec<_>>();
170                sources.sort_unstable();
171                return Err(ValueCodecRegistryError::DuplicateId {
172                    id: id.as_str(),
173                    sources,
174                });
175            }
176        }
177        let indices = registrations
178            .iter()
179            .enumerate()
180            .map(|(index, registration)| (registration.id(), index))
181            .collect();
182        Ok(Self {
183            registrations: registrations.into_boxed_slice(),
184            indices,
185        })
186    }
187
188    /// Finds a registration by stable ID.
189    ///
190    /// # Parameters
191    ///
192    /// - `id`: Exact stable ID to look up, without normalization.
193    ///
194    /// # Returns
195    ///
196    /// `Some` borrows the matching registration from this registry; `None`
197    /// means the ID is absent. Lookup uses a logarithmic tree search.
198    #[inline]
199    pub fn get(&self, id: &str) -> Option<&ValueCodecRegistration<D>> {
200        let index = *self.indices.get(id)?;
201        self.registrations.get(index)
202    }
203
204    /// Returns registrations in deterministic ID order.
205    ///
206    /// # Returns
207    ///
208    /// The immutable registration slice borrowed from this registry without
209    /// allocation.
210    #[must_use]
211    #[inline]
212    pub fn registrations(&self) -> &[ValueCodecRegistration<D>] {
213        &self.registrations
214    }
215}