Skip to main content

numcodecs_registry/
lib.rs

1//! [![CI Status]][workflow] [![MSRV]][repo] [![Latest Version]][crates.io] [![Rust Doc Crate]][docs.rs] [![Rust Doc Main]][docs]
2//!
3//! [CI Status]: https://img.shields.io/github/actions/workflow/status/juntyr/numcodecs-rs/ci.yml?branch=main
4//! [workflow]: https://github.com/juntyr/numcodecs-rs/actions/workflows/ci.yml?query=branch%3Amain
5//!
6//! [MSRV]: https://img.shields.io/badge/MSRV-1.87.0-blue
7//! [repo]: https://github.com/juntyr/numcodecs-rs
8//!
9//! [Latest Version]: https://img.shields.io/crates/v/numcodecs-registry
10//! [crates.io]: https://crates.io/crates/numcodecs-registry
11//!
12//! [Rust Doc Crate]: https://img.shields.io/docsrs/numcodecs-registry
13//! [docs.rs]: https://docs.rs/numcodecs-registry/
14//!
15//! [Rust Doc Main]: https://img.shields.io/badge/docs-main-blue
16//! [docs]: https://juntyr.github.io/numcodecs-rs/numcodecs_registry
17//!
18//! Registries for compression codecs implementing the [`numcodecs`] API.
19//!
20//! [`numcodecs`]: https://numcodecs.readthedocs.io/en/stable/
21
22use std::{error::Error, sync::Arc};
23
24use numcodecs::{DynCodec, ErasedDynCodec, ErasedError};
25use serde::Deserializer;
26
27/// Registry of codec types.
28pub trait Registry: 'static + Send + Sync {
29    /// Error type that may be returned during
30    /// [`get_codec`][`Registry::get_codec`].
31    type Error: 'static + Send + Sync + Error;
32
33    /// Instantiate a codec of any type from its `config`uration.
34    ///
35    /// The config *must* include the `id` field with the
36    /// [`DynCodecType::codec_id`].
37    ///
38    /// # Errors
39    ///
40    /// Errors if no codec with a matching `id` has been registered, or if
41    /// constructing the codec fails.
42    fn get_codec<'de, D: Deserializer<'de>>(
43        &self,
44        config: D,
45    ) -> Result<ErasedDynCodec, Self::Error>;
46
47    /// Instantiate a codec with a concrete type from its `config`uration.
48    ///
49    /// Returns [`None`] if the constructed codec is of the wrong type.
50    ///
51    /// The config *must* include the `id` field with the
52    /// [`DynCodecType::codec_id`].
53    ///
54    /// # Errors
55    ///
56    /// Errors if no codec with a matching `id` has been registered, or if
57    /// constructing the codec fails.
58    fn get_codec_typed<'de, T: DynCodec, D: Deserializer<'de>>(
59        &self,
60        config: D,
61    ) -> Result<Option<T>, Self::Error> {
62        self.get_codec(config).map(|codec| codec.downcast().ok())
63    }
64}
65
66impl<R: Registry> Registry for Box<R> {
67    type Error = R::Error;
68
69    fn get_codec<'de, D: Deserializer<'de>>(
70        &self,
71        config: D,
72    ) -> Result<ErasedDynCodec, Self::Error> {
73        R::get_codec(self, config)
74    }
75
76    fn get_codec_typed<'de, T: DynCodec, D: Deserializer<'de>>(
77        &self,
78        config: D,
79    ) -> Result<Option<T>, Self::Error> {
80        R::get_codec_typed(self, config)
81    }
82}
83
84impl<R: Registry> Registry for Arc<R> {
85    type Error = R::Error;
86
87    fn get_codec<'de, D: Deserializer<'de>>(
88        &self,
89        config: D,
90    ) -> Result<ErasedDynCodec, Self::Error> {
91        R::get_codec(self, config)
92    }
93
94    fn get_codec_typed<'de, T: DynCodec, D: Deserializer<'de>>(
95        &self,
96        config: D,
97    ) -> Result<Option<T>, Self::Error> {
98        R::get_codec_typed(self, config)
99    }
100}
101
102/// Type-erased [`Registry`].
103pub struct ErasedRegistry {
104    registry: Box<dyn ErasedRegistryDispatch>,
105}
106
107impl ErasedRegistry {
108    /// Erase the type information of the concrete `registry`.
109    pub fn new<T: Registry>(registry: T) -> Self {
110        Self {
111            registry: Box::new(registry),
112        }
113    }
114}
115
116impl Registry for ErasedRegistry {
117    type Error = ErasedError;
118
119    fn get_codec<'de, D: Deserializer<'de>>(
120        &self,
121        config: D,
122    ) -> Result<ErasedDynCodec, Self::Error> {
123        self.registry
124            .erased_get_codec(&mut <dyn erased_serde::Deserializer>::erase(config))
125    }
126}
127
128trait ErasedRegistryDispatch: 'static + Send + Sync {
129    fn erased_get_codec(
130        &self,
131        config: &mut dyn erased_serde::Deserializer,
132    ) -> Result<ErasedDynCodec, ErasedError>;
133}
134
135impl<T: Registry> ErasedRegistryDispatch for T {
136    fn erased_get_codec(
137        &self,
138        config: &mut dyn erased_serde::Deserializer,
139    ) -> Result<ErasedDynCodec, ErasedError> {
140        match self.get_codec(config) {
141            Ok(codec) => Ok(codec),
142            Err(err) => Err(ErasedError::new(err)),
143        }
144    }
145}
146
147/// Global registry singleton.
148///
149/// If the global registry is used, its backing registry must be provided
150/// exactly once using [`export_global`].
151///
152/// The global registry must not be used to provide the backing of itself,
153/// which would result in an infinite loop at runtime.
154pub struct GlobalRegistry;
155
156impl GlobalRegistry {
157    fn get() -> &'static ErasedRegistry {
158        #[expect(unsafe_code)]
159        unsafe extern "C" {
160            #[expect(improper_ctypes)]
161            safe fn _numcodecs_registry_get_global_registry() -> &'static ErasedRegistry;
162        }
163
164        _numcodecs_registry_get_global_registry()
165    }
166
167    /// Deserialize an [`ErasedDynCodec`] from its `config` by looking up the
168    /// codec `id` in the [`GlobalRegistry`].
169    ///
170    /// The config *must* include the `id` field with the
171    /// [`DynCodecType::codec_id`].
172    ///
173    /// # Errors
174    ///
175    /// Errors if no codec with a matching `id` has been registered, or if
176    /// constructing the codec fails.
177    pub fn codec_from_config<'de, D: Deserializer<'de>>(
178        config: D,
179    ) -> Result<ErasedDynCodec, D::Error> {
180        Self.get_codec(config).map_err(serde::de::Error::custom)
181    }
182}
183
184impl Registry for GlobalRegistry {
185    type Error = ErasedError;
186
187    fn get_codec<'de, D: Deserializer<'de>>(
188        &self,
189        config: D,
190    ) -> Result<ErasedDynCodec, Self::Error> {
191        Self::get().get_codec(config)
192    }
193
194    fn get_codec_typed<'de, T: DynCodec, D: Deserializer<'de>>(
195        &self,
196        config: D,
197    ) -> Result<Option<T>, Self::Error> {
198        Self::get().get_codec_typed(config)
199    }
200}
201
202#[macro_export]
203/// `export_global! { static REGISTRY: ty = expr; }` exports the provided
204/// registry as the global registry singleton.
205///
206/// This macro must only be used at most once in every binary or shared
207/// library.
208macro_rules! export_global {
209    (static REGISTRY: $ty:ty = $init:expr;) => {
210        const _: () = {
211            use std::sync::LazyLock;
212
213            use $crate::ErasedRegistry;
214
215            static _GLOBAL_REGISTRY: LazyLock<ErasedRegistry> =
216                LazyLock::new(|| ErasedRegistry::new($init));
217
218            #[allow(improper_ctypes, unsafe_code)]
219            #[unsafe(no_mangle)]
220            extern "C" fn _numcodecs_registry_get_global_registry() -> &'static ErasedRegistry {
221                LazyLock::force(&_GLOBAL_REGISTRY)
222            }
223        };
224    };
225}
226
227#[derive(Debug, thiserror::Error)]
228#[error("codec not found")]
229/// Codec was not found in the registry
230pub struct CodecNotFoundError;
231
232/// Empty registry that contains no codecs
233pub struct EmptyRegistry;
234
235impl Registry for EmptyRegistry {
236    type Error = CodecNotFoundError;
237
238    fn get_codec<'de, D: Deserializer<'de>>(
239        &self,
240        _config: D,
241    ) -> Result<ErasedDynCodec, Self::Error> {
242        Err(CodecNotFoundError)
243    }
244
245    fn get_codec_typed<'de, T: DynCodec, D: Deserializer<'de>>(
246        &self,
247        _config: D,
248    ) -> Result<Option<T>, Self::Error> {
249        Err(CodecNotFoundError)
250    }
251}