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
7//! Immutable local and process-wide value-codec registries.
8
9use std::collections::BTreeMap;
10use std::sync::OnceLock;
11
12use crate::ValueCodecId;
13use crate::ValueCodecRegistration;
14use crate::ValueCodecRegistrationFactory;
15use crate::ValueCodecRegistryError;
16
17/// An immutable value-codec registry sorted by stable ID.
18///
19/// # Examples
20///
21/// ```
22/// use qubit_codec::ValueCodecRegistry;
23///
24/// let registry = ValueCodecRegistry::empty();
25/// assert!(registry.get("missing.example").is_none());
26/// ```
27#[derive(Debug)]
28pub struct ValueCodecRegistry {
29    registrations: Box<[ValueCodecRegistration]>,
30    indices: BTreeMap<ValueCodecId, usize>,
31}
32
33impl ValueCodecRegistry {
34    /// Builds a local registry from static registrations.
35    ///
36    /// # Errors
37    ///
38    /// Returns a duplicate-ID error when multiple entries claim one ID.
39    pub fn from_registrations(
40        registrations: impl IntoIterator<Item = &'static ValueCodecRegistration>,
41    ) -> Result<Self, ValueCodecRegistryError> {
42        Self::build(registrations.into_iter().copied().collect())
43    }
44
45    /// Returns an empty local registry.
46    #[must_use]
47    pub fn empty() -> Self {
48        Self {
49            registrations: Box::new([]),
50            indices: BTreeMap::new(),
51        }
52    }
53
54    /// Initializes and returns the process-wide linked registry.
55    ///
56    /// # Errors
57    ///
58    /// Returns the cached construction error when linked registrations
59    /// conflict.
60    pub fn try_global() -> Result<&'static Self, ValueCodecRegistryError> {
61        static REGISTRY: OnceLock<Result<ValueCodecRegistry, ValueCodecRegistryError>> = OnceLock::new();
62        match REGISTRY.get_or_init(|| {
63            let registrations = inventory::iter::<ValueCodecRegistrationFactory>
64                .into_iter()
65                .map(|factory| (factory.0)())
66                .collect();
67            Self::build(registrations)
68        }) {
69            Ok(registry) => Ok(registry),
70            Err(error) => Err(error.clone()),
71        }
72    }
73
74    /// Returns the process-wide registry or panics with a stable diagnostic.
75    ///
76    /// # Panics
77    ///
78    /// Panics when linked registrations conflict.
79    #[must_use]
80    pub fn global() -> &'static Self {
81        Self::try_global().unwrap_or_else(|error| panic!("invalid global value codec registry: {error}"))
82    }
83
84    /// Finds a registration by stable ID.
85    ///
86    /// Returns `None` when `id` is absent.
87    #[must_use]
88    pub fn get(&self, id: &str) -> Option<&ValueCodecRegistration> {
89        let index = *self.indices.get(id)?;
90        self.registrations.get(index)
91    }
92
93    /// Returns registrations in deterministic ID order.
94    #[must_use]
95    pub fn registrations(&self) -> &[ValueCodecRegistration] {
96        &self.registrations
97    }
98
99    /// Freezes registrations and rejects duplicate IDs.
100    fn build(mut registrations: Vec<ValueCodecRegistration>) -> Result<Self, ValueCodecRegistryError> {
101        registrations.sort_by_key(ValueCodecRegistration::id);
102        for pair in registrations.windows(2) {
103            if pair[0].id() == pair[1].id() {
104                let mut sources = pair.iter().map(ValueCodecRegistration::source).collect::<Vec<_>>();
105                sources.sort_unstable();
106                return Err(ValueCodecRegistryError::DuplicateId {
107                    id: pair[0].id().as_str(),
108                    sources,
109                });
110            }
111        }
112        let indices = registrations
113            .iter()
114            .enumerate()
115            .map(|(index, registration)| (registration.id(), index))
116            .collect();
117        Ok(Self {
118            registrations: registrations.into_boxed_slice(),
119            indices,
120        })
121    }
122}