Skip to main content

numcodecs/
erased.rs

1use std::{any::Any, error::Error, fmt};
2
3use schemars::{JsonSchema, Schema, SchemaGenerator};
4use serde::{Deserializer, Serialize, Serializer};
5
6use crate::{AnyArray, AnyArrayView, AnyArrayViewMut, AnyCowArray, Codec, DynCodec, DynCodecType};
7
8/// Type-erased [`Error`] type.
9pub struct ErasedError {
10    error: Box<dyn 'static + Error + Send + Sync>,
11}
12
13impl ErasedError {
14    /// Erase the type information of the concrete `err`or.
15    pub fn new<T: 'static + Error + Send + Sync>(err: T) -> Self {
16        let err: Box<dyn 'static + Error + Send + Sync> = Box::new(err);
17
18        // avoid double-erasing Self(Self(...))
19        match err.downcast::<Self>() {
20            Ok(err) => *err,
21            Err(err) => Self { error: err },
22        }
23    }
24}
25
26impl fmt::Debug for ErasedError {
27    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
28        fmt::Debug::fmt(&self.error, fmt)
29    }
30}
31
32impl fmt::Display for ErasedError {
33    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
34        fmt::Display::fmt(&self.error, fmt)
35    }
36}
37
38impl Error for ErasedError {
39    fn source(&self) -> Option<&(dyn Error + 'static)> {
40        self.error.source()
41    }
42}
43
44/// Type-erased dynamically typed compression codec.
45pub struct ErasedDynCodec {
46    codec: Box<dyn ErasedDynCodecDispatch>,
47}
48
49impl ErasedDynCodec {
50    /// Erase the type information of the concrete `codec`.
51    pub fn new<T: DynCodec>(codec: T) -> Self {
52        let codec: Box<dyn ErasedDynCodecDispatch> = Box::new(codec);
53
54        // avoid double-erasing Self(Self(...))
55        if codec.erased_as_any().is::<Self>() {
56            let raw = Box::into_raw(codec);
57            #[expect(unsafe_code, clippy::cast_ptr_alignment)]
58            // SAFETY: we have checked that self.codec is of type Self
59            let codec = unsafe { Box::from_raw(raw.cast::<Self>()) };
60            return *codec;
61        }
62
63        Self { codec }
64    }
65
66    /// Try to downcast into a concretely-typed codec.
67    ///
68    /// # Errors
69    ///
70    /// Returns `self` if the type-erased codec is not of the concrete type.
71    pub fn downcast<T: DynCodec>(self) -> Result<T, Self> {
72        if self.codec.erased_as_any().is::<T>() {
73            let raw = Box::into_raw(self.codec);
74            #[expect(unsafe_code)]
75            // SAFETY: we have checked that self.codec is of type T
76            let codec = unsafe { Box::from_raw(raw.cast::<T>()) };
77            Ok(*codec)
78        } else {
79            Err(self)
80        }
81    }
82
83    /// Try to downcast to a concretely-typed codec reference.
84    #[must_use]
85    pub fn downcast_ref<T: DynCodec>(&self) -> Option<&T> {
86        self.codec.erased_as_any().downcast_ref()
87    }
88
89    /// Try to downcast to a concretely-typed mutable codec reference.
90    #[must_use]
91    pub fn downcast_mut<T: DynCodec>(&mut self) -> Option<&mut T> {
92        self.codec.erased_as_any_mut().downcast_mut()
93    }
94
95    /// Generate the schema for any codec config.
96    pub fn codec_config_schema(generator: &mut SchemaGenerator) -> Schema {
97        #[derive(JsonSchema)]
98        #[schemars(extend("additionalProperties" = {"type": "object"}))]
99        /// The configuration for a codec.
100        struct Codec {
101            /// The `codec_id` of the codec, which is looked up in the global
102            /// registry.
103            #[expect(dead_code)]
104            id: String,
105        }
106
107        Codec::json_schema(generator)
108    }
109}
110
111impl Clone for ErasedDynCodec {
112    fn clone(&self) -> Self {
113        Self {
114            codec: self.codec.erased_clone(),
115        }
116    }
117}
118
119impl Codec for ErasedDynCodec {
120    type Error = ErasedError;
121
122    fn encode(&self, data: AnyCowArray) -> Result<AnyArray, Self::Error> {
123        self.codec.erased_encode(data)
124    }
125
126    fn decode(&self, encoded: AnyCowArray) -> Result<AnyArray, Self::Error> {
127        self.codec.erased_decode(encoded)
128    }
129
130    fn decode_into(
131        &self,
132        encoded: AnyArrayView,
133        decoded: AnyArrayViewMut,
134    ) -> Result<(), Self::Error> {
135        self.codec.erased_decode_into(encoded, decoded)
136    }
137}
138
139impl DynCodec for ErasedDynCodec {
140    type Type = ErasedDynCodecType;
141
142    fn ty(&self) -> Self::Type {
143        ErasedDynCodecType {
144            ty: self.codec.erased_ty(),
145        }
146    }
147
148    fn get_config<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
149        erased_serde::serialize(self.codec.erased_as_serialize(), serializer)
150    }
151}
152
153/// Type-erased dynamically typed compression codec type.
154pub struct ErasedDynCodecType {
155    ty: Box<dyn ErasedDynCodecTypeDispatch>,
156}
157
158impl ErasedDynCodecType {
159    /// Erase the type information of the concrete codec `ty`pe.
160    pub fn new<T: DynCodecType>(ty: T) -> Self {
161        let ty: Box<dyn ErasedDynCodecTypeDispatch> = Box::new(ty);
162
163        // avoid double-erasing Self(Self(...))
164        if ty.erased_as_any().is::<Self>() {
165            let raw = Box::into_raw(ty);
166            #[expect(unsafe_code, clippy::cast_ptr_alignment)]
167            // SAFETY: we have checked that self.codec is of type Self
168            let ty = unsafe { Box::from_raw(raw.cast::<Self>()) };
169            return *ty;
170        }
171
172        Self { ty }
173    }
174
175    /// Try to downcast into a concretely-typed codec type.
176    ///
177    /// # Errors
178    ///
179    /// Returns `self` if the type-erased codec type is not of the concrete
180    /// type.
181    pub fn downcast<T: DynCodecType>(self) -> Result<T, Self> {
182        if self.ty.erased_as_any().is::<T>() {
183            let raw = Box::into_raw(self.ty);
184            #[expect(unsafe_code)]
185            // SAFETY: we have checked that self.ty is of type T
186            let ty = unsafe { Box::from_raw(raw.cast::<T>()) };
187            Ok(*ty)
188        } else {
189            Err(self)
190        }
191    }
192
193    /// Try to downcast to a concretely-typed codec type reference.
194    #[must_use]
195    pub fn downcast_ref<T: DynCodecType>(&self) -> Option<&T> {
196        self.ty.erased_as_any().downcast_ref()
197    }
198
199    /// Try to downcast to a concretely-typed mutable codec type reference.
200    #[must_use]
201    pub fn downcast_mut<T: DynCodecType>(&mut self) -> Option<&mut T> {
202        self.ty.erased_as_any_mut().downcast_mut()
203    }
204}
205
206impl DynCodecType for ErasedDynCodecType {
207    type Codec = ErasedDynCodec;
208
209    fn codec_id(&self) -> &str {
210        self.ty.erased_codec_id()
211    }
212
213    fn codec_config_schema(&self) -> Schema {
214        self.ty.erased_codec_config_schema()
215    }
216
217    fn codec_from_config<'de, D: Deserializer<'de>>(
218        &self,
219        config: D,
220    ) -> Result<Self::Codec, D::Error> {
221        match self
222            .ty
223            .erased_codec_from_config(&mut <dyn erased_serde::Deserializer>::erase(config))
224        {
225            Ok(codec) => Ok(ErasedDynCodec { codec }),
226            Err(err) => Err(serde::de::Error::custom(err)), // TODO: improve
227        }
228    }
229}
230
231trait ErasedDynCodecDispatch: 'static + Send + Sync {
232    fn erased_encode(&self, data: AnyCowArray) -> Result<AnyArray, ErasedError>;
233    fn erased_decode(&self, encoded: AnyCowArray) -> Result<AnyArray, ErasedError>;
234    fn erased_decode_into(
235        &self,
236        encoded: AnyArrayView,
237        decoded: AnyArrayViewMut,
238    ) -> Result<(), ErasedError>;
239
240    fn erased_clone(&self) -> Box<dyn ErasedDynCodecDispatch>;
241
242    fn erased_ty(&self) -> Box<dyn ErasedDynCodecTypeDispatch>;
243
244    fn erased_as_any(&self) -> &dyn Any;
245    fn erased_as_any_mut(&mut self) -> &mut dyn Any;
246
247    fn erased_as_serialize(&self) -> &dyn erased_serde::Serialize;
248}
249
250trait ErasedDynCodecTypeDispatch: 'static + Send + Sync {
251    fn erased_codec_id(&self) -> &str;
252    fn erased_codec_config_schema(&self) -> Schema;
253    fn erased_codec_from_config(
254        &self,
255        config: &mut dyn erased_serde::Deserializer,
256    ) -> Result<Box<dyn ErasedDynCodecDispatch>, erased_serde::Error>;
257
258    fn erased_as_any(&self) -> &dyn Any;
259    fn erased_as_any_mut(&mut self) -> &mut dyn Any;
260}
261
262impl<T: DynCodec> ErasedDynCodecDispatch for T {
263    fn erased_encode(&self, data: AnyCowArray) -> Result<AnyArray, ErasedError> {
264        Codec::encode(self, data).map_err(ErasedError::new)
265    }
266
267    fn erased_decode(&self, encoded: AnyCowArray) -> Result<AnyArray, ErasedError> {
268        Codec::decode(self, encoded).map_err(ErasedError::new)
269    }
270
271    fn erased_decode_into(
272        &self,
273        encoded: AnyArrayView,
274        decoded: AnyArrayViewMut,
275    ) -> Result<(), ErasedError> {
276        Codec::decode_into(self, encoded, decoded).map_err(ErasedError::new)
277    }
278
279    fn erased_clone(&self) -> Box<dyn ErasedDynCodecDispatch> {
280        Box::new(Clone::clone(self))
281    }
282
283    fn erased_ty(&self) -> Box<dyn ErasedDynCodecTypeDispatch> {
284        Box::new(DynCodec::ty(self))
285    }
286
287    fn erased_as_any(&self) -> &dyn Any {
288        self
289    }
290
291    fn erased_as_any_mut(&mut self) -> &mut dyn Any {
292        self
293    }
294
295    fn erased_as_serialize(&self) -> &dyn erased_serde::Serialize {
296        #[repr(transparent)]
297        struct SerializeDynCodec<T: DynCodec>(T);
298
299        impl<T: DynCodec> Serialize for SerializeDynCodec<T> {
300            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
301                DynCodec::get_config(&self.0, serializer)
302            }
303        }
304
305        #[expect(unsafe_code)]
306        // SAFETY: SerializeDynCodec is a transparent newtype around Self
307        unsafe {
308            &*std::ptr::from_ref(self).cast::<SerializeDynCodec<Self>>()
309        }
310    }
311}
312
313impl<T: DynCodecType> ErasedDynCodecTypeDispatch for T {
314    fn erased_codec_id(&self) -> &str {
315        DynCodecType::codec_id(self)
316    }
317
318    fn erased_codec_config_schema(&self) -> Schema {
319        DynCodecType::codec_config_schema(self)
320    }
321
322    fn erased_codec_from_config(
323        &self,
324        config: &mut dyn erased_serde::Deserializer,
325    ) -> Result<Box<dyn ErasedDynCodecDispatch>, erased_serde::Error> {
326        match DynCodecType::codec_from_config(self, config) {
327            Ok(codec) => Ok(Box::new(codec)),
328            Err(err) => Err(err),
329        }
330    }
331
332    fn erased_as_any(&self) -> &dyn Any {
333        self
334    }
335
336    fn erased_as_any_mut(&mut self) -> &mut dyn Any {
337        self
338    }
339}