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([®ISTRATION])
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}