Skip to main content

qubit_codec/value/
value_string_codec_descriptor.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//! Safe type-erased bidirectional string value-codec descriptors.
10
11use core::any::type_name;
12use core::fmt::Debug;
13use core::fmt::Formatter;
14use core::fmt::Result as FmtResult;
15use std::any::Any;
16use std::any::TypeId;
17use std::borrow::Borrow;
18use std::error::Error;
19
20use crate::ValueCodecExecutionError;
21use crate::ValueDecoder;
22use crate::ValueEncoder;
23
24/// Checked type-erased encoding entry point returning an owned string.
25type EncodeFn = fn(&dyn Any) -> Result<String, ValueCodecExecutionError>;
26/// Type-erased decoding entry point returning a boxed owned value.
27type DecodeFn = fn(&str) -> Result<Box<dyn Any>, ValueCodecExecutionError>;
28
29/// An immutable, safely erased bidirectional string codec for one value type.
30///
31/// Each operation creates a fresh default codec; no codec state is retained
32/// between calls. Type identities are process-local and names are diagnostic,
33/// not stable IDs.
34///
35/// # Examples
36///
37/// ```
38/// use core::convert::Infallible;
39///
40/// use qubit_codec::ValueDecoder;
41/// use qubit_codec::ValueEncoder;
42/// use qubit_codec::ValueStringCodecDescriptor;
43///
44/// #[derive(Default)]
45/// struct Text;
46/// impl ValueEncoder<u32> for Text {
47///     type Output = String;
48///     type Error = Infallible;
49///
50///     fn encode(&mut self, input: &u32) -> Result<String, Infallible> {
51///         Ok(input.to_string())
52///     }
53/// }
54/// impl ValueDecoder<str> for Text {
55///     type Output = u32;
56///     type Error = Infallible;
57///
58///     fn decode(&mut self, input: &str) -> Result<u32, Infallible> {
59///         Ok(input.len() as u32)
60///     }
61/// }
62///
63/// let descriptor = ValueStringCodecDescriptor::of::<Text, u32>();
64///
65/// // `encode` takes an erased `&dyn Any`, so the value type must be named.
66/// let value: u32 = 7;
67/// assert_eq!(descriptor.encode(&value).unwrap(), "7");
68///
69/// let decoded = descriptor.decode("hello").unwrap();
70/// assert_eq!(*decoded.downcast::<u32>().unwrap(), 5);
71/// ```
72#[derive(Clone, Copy)]
73pub struct ValueStringCodecDescriptor {
74    /// Returns the process-local identity of the concrete codec.
75    codec_type_id: fn() -> TypeId,
76    /// Returns the diagnostic name of the concrete codec.
77    codec_type_name: fn() -> &'static str,
78    /// Returns the process-local identity of the decoded value.
79    value_type_id: fn() -> TypeId,
80    /// Returns the diagnostic name of the decoded value.
81    value_type_name: fn() -> &'static str,
82    /// Checks the erased input type and invokes a fresh default encoder.
83    encode: EncodeFn,
84    /// Invokes a fresh default decoder and boxes its owned output.
85    decode: DecodeFn,
86}
87
88impl ValueStringCodecDescriptor {
89    /// Creates a descriptor for codec `C` and value type `V`.
90    ///
91    /// The codec error types must implement `Send + Sync` so execution errors
92    /// can retain their source across thread boundaries. The codec and value
93    /// types themselves need not implement these traits; `V` only needs to be
94    /// `'static` because decoded values are returned through `Any`.
95    /// Use [`Self::of_borrowed`] when the encoder accepts a borrowed view of
96    /// `V` instead of `V` itself.
97    ///
98    /// # Type Parameters
99    ///
100    /// - `C`: Default-constructible string encoder/decoder with owned errors.
101    /// - `V`: Owned value type accepted and produced by `C`.
102    ///
103    /// # Returns
104    ///
105    /// Returns immutable function metadata without constructing a codec.
106    #[must_use]
107    #[inline]
108    pub const fn of<C, V>() -> Self
109    where
110        C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
111        <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
112        <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
113        V: 'static,
114    {
115        Self::of_borrowed::<C, V, V>()
116    }
117
118    /// Creates a descriptor that encodes an owned value through a borrowed
119    /// view.
120    ///
121    /// The descriptor continues to accept and return the owned type `V`.
122    /// During each encode call it checks the erased value is exactly `V`, then
123    /// borrows an `I` view with [`Borrow`] for that call only. The view is
124    /// never stored. Each operation constructs a fresh `C::default()`
125    /// codec. Encoder and decoder errors must implement `Error + Send +
126    /// Sync + 'static`; `C` and `V` themselves need not be `Send` or
127    /// `Sync`.
128    ///
129    /// # Type Parameters
130    ///
131    /// - `C`: Default-constructible codec encoding `I` and decoding into `V`.
132    /// - `V`: Owned value type erased by this descriptor and produced by
133    ///   decode.
134    /// - `I`: Borrowed input view accepted by the encoder; it may be unsized.
135    ///
136    /// # Returns
137    ///
138    /// Returns immutable function metadata without constructing a codec.
139    ///
140    /// # Errors
141    ///
142    /// Encoding returns a type mismatch if the erased value is not `V`, or
143    /// retains the codec's source error. Decoding retains the decoder's source
144    /// error.
145    ///
146    /// # Examples
147    ///
148    /// ```
149    /// use std::io::Error;
150    /// use qubit_codec::{ValueDecoder, ValueEncoder, ValueStringCodecDescriptor};
151    ///
152    /// #[derive(Default)]
153    /// struct Utf8;
154    /// impl ValueEncoder<[u8]> for Utf8 {
155    ///     type Output = String;
156    ///     type Error = Error;
157    ///     fn encode(&mut self, input: &[u8]) -> Result<String, Error> {
158    ///         String::from_utf8(input.to_vec()).map_err(Error::other)
159    ///     }
160    /// }
161    /// impl ValueDecoder<str> for Utf8 {
162    ///     type Output = Vec<u8>;
163    ///     type Error = Error;
164    ///     fn decode(&mut self, input: &str) -> Result<Vec<u8>, Error> {
165    ///         Ok(input.as_bytes().to_vec())
166    ///     }
167    /// }
168    ///
169    /// let descriptor = ValueStringCodecDescriptor::of_borrowed::<Utf8, Vec<u8>, [u8]>();
170    /// let value = b"hello".to_vec();
171    /// assert_eq!(descriptor.encode(&value).unwrap(), "hello");
172    /// let decoded = descriptor.decode("hello").unwrap().downcast::<Vec<u8>>().unwrap();
173    /// assert_eq!(*decoded, value);
174    /// ```
175    ///
176    /// The owned type must implement [`Borrow`] for the selected input view,
177    /// and the decoder output must be exactly `V`:
178    ///
179    /// ```compile_fail
180    /// use std::io::Error;
181    /// use qubit_codec::{ValueDecoder, ValueEncoder, ValueStringCodecDescriptor};
182    ///
183    /// #[derive(Default)] struct Codec;
184    /// impl ValueEncoder<[u8]> for Codec {
185    ///     type Output = String; type Error = Error;
186    ///     fn encode(&mut self, input: &[u8]) -> Result<String, Error> {
187    ///         String::from_utf8(input.to_vec()).map_err(Error::other)
188    ///     }
189    /// }
190    /// impl ValueDecoder<str> for Codec {
191    ///     type Output = String; type Error = Error;
192    ///     fn decode(&mut self, input: &str) -> Result<String, Error> { Ok(input.to_owned()) }
193    /// }
194    /// let _ = ValueStringCodecDescriptor::of_borrowed::<Codec, String, [u8]>();
195    /// ```
196    ///
197    /// ```compile_fail
198    /// use std::io::Error;
199    /// use qubit_codec::{ValueDecoder, ValueEncoder, ValueStringCodecDescriptor};
200    ///
201    /// #[derive(Default)] struct Codec;
202    /// impl ValueEncoder<[u8]> for Codec {
203    ///     type Output = String; type Error = Error;
204    ///     fn encode(&mut self, input: &[u8]) -> Result<String, Error> {
205    ///         String::from_utf8(input.to_vec()).map_err(Error::other)
206    ///     }
207    /// }
208    /// impl ValueDecoder<str> for Codec {
209    ///     type Output = String; type Error = Error;
210    ///     fn decode(&mut self, input: &str) -> Result<String, Error> { Ok(input.to_owned()) }
211    /// }
212    /// let _ = ValueStringCodecDescriptor::of_borrowed::<Codec, Vec<u8>, [u8]>();
213    /// ```
214    #[must_use]
215    #[inline]
216    pub const fn of_borrowed<C, V, I>() -> Self
217    where
218        C: Default + ValueEncoder<I, Output = String> + ValueDecoder<str, Output = V> + 'static,
219        <C as ValueEncoder<I>>::Error: Error + Send + Sync + 'static,
220        <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
221        V: Borrow<I> + 'static,
222        I: ?Sized,
223    {
224        Self {
225            codec_type_id: TypeId::of::<C>,
226            codec_type_name: type_name::<C>,
227            value_type_id: TypeId::of::<V>,
228            value_type_name: type_name::<V>,
229            encode: encode::<C, V, I>,
230            decode: decode::<C, V>,
231        }
232    }
233
234    /// Returns the process-local identity of the concrete codec.
235    ///
236    /// # Returns
237    ///
238    /// Returns the `TypeId` of the codec this descriptor was built for.
239    #[must_use]
240    #[inline]
241    pub fn codec_type_id(&self) -> TypeId {
242        (self.codec_type_id)()
243    }
244
245    /// Returns the diagnostic name of the concrete codec.
246    ///
247    /// # Returns
248    ///
249    /// Returns the `type_name` of the codec, for diagnostics only.
250    #[must_use]
251    #[inline]
252    pub fn codec_type_name(&self) -> &'static str {
253        (self.codec_type_name)()
254    }
255
256    /// Returns the process-local identity of the decoded value.
257    ///
258    /// # Returns
259    ///
260    /// Returns the `TypeId` of the value this descriptor accepts and produces.
261    #[must_use]
262    #[inline]
263    pub fn value_type_id(&self) -> TypeId {
264        (self.value_type_id)()
265    }
266
267    /// Returns the diagnostic name of the decoded value.
268    ///
269    /// # Returns
270    ///
271    /// Returns the `type_name` of the value, for diagnostics only.
272    #[must_use]
273    #[inline]
274    pub fn value_type_name(&self) -> &'static str {
275        (self.value_type_name)()
276    }
277
278    /// Encodes one erased value after checking its concrete type.
279    ///
280    /// # Parameters
281    ///
282    /// - `value`: Borrowed value whose concrete type must match the descriptor.
283    ///
284    /// # Returns
285    ///
286    /// Returns an owned string produced by a fresh default codec.
287    ///
288    /// # Errors
289    ///
290    /// Returns a type mismatch or the typed encoder source error.
291    #[inline]
292    pub fn encode(&self, value: &dyn Any) -> Result<String, ValueCodecExecutionError> {
293        (self.encode)(value)
294    }
295
296    /// Decodes one string into a safely erased value of the declared type.
297    ///
298    /// # Parameters
299    ///
300    /// - `input`: String borrowed for one decoding operation.
301    ///
302    /// # Returns
303    ///
304    /// Returns a boxed owned value that can be downcast to the declared type.
305    ///
306    /// # Errors
307    ///
308    /// Returns the typed decoder source error.
309    #[inline]
310    pub fn decode(&self, input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError> {
311        (self.decode)(input)
312    }
313}
314
315impl Debug for ValueStringCodecDescriptor {
316    /// Formats descriptor metadata without invoking either codec function.
317    ///
318    /// # Parameters
319    ///
320    /// - `formatter`: Destination receiving the diagnostic type names.
321    ///
322    /// # Returns
323    ///
324    /// Returns success after both names have been formatted.
325    ///
326    /// # Errors
327    ///
328    /// Returns the underlying formatting failure.
329    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
330        formatter
331            .debug_struct("ValueStringCodecDescriptor")
332            .field("codec_type_name", &self.codec_type_name())
333            .field("value_type_name", &self.value_type_name())
334            .finish_non_exhaustive()
335    }
336}
337
338/// Downcasts an erased value and encodes it with a fresh default codec.
339///
340/// # Type Parameters
341///
342/// - `C`: Default codec encoding the selected borrowed view.
343/// - `V`: Concrete owned value type required by the descriptor.
344/// - `I`: Borrowed input view accepted by the encoder.
345///
346/// # Parameters
347///
348/// - `value`: Erased input borrowed until serialization completes.
349///
350/// # Returns
351///
352/// Returns the owned string produced by the codec.
353///
354/// # Errors
355///
356/// Returns `TypeMismatch` when the input has a different concrete type, or
357/// `EncodeFailed` with the codec source error when encoding fails.
358fn encode<C, V, I>(value: &dyn Any) -> Result<String, ValueCodecExecutionError>
359where
360    C: Default + ValueEncoder<I, Output = String>,
361    <C as ValueEncoder<I>>::Error: Error + Send + Sync + 'static,
362    V: Borrow<I> + 'static,
363    I: ?Sized,
364{
365    let value = value
366        .downcast_ref::<V>()
367        .ok_or_else(|| ValueCodecExecutionError::TypeMismatch {
368            expected_type: type_name::<V>(),
369            actual_type: value.type_id(),
370        })?;
371    <C as ValueEncoder<I>>::encode(&mut C::default(), <V as Borrow<I>>::borrow(value)).map_err(|source| {
372        ValueCodecExecutionError::EncodeFailed {
373            codec_type: type_name::<C>(),
374            source: Box::new(source),
375        }
376    })
377}
378
379/// Decodes one string with a fresh default codec and erases its typed output.
380///
381/// # Type Parameters
382///
383/// - `C`: Default codec with matching string encode/decode contracts.
384/// - `V`: Concrete owned value type produced by the descriptor.
385///
386/// # Parameters
387///
388/// - `input`: String slice borrowed until decoding completes.
389///
390/// # Returns
391///
392/// Returns the decoded value in a box that can be downcast to `V`.
393///
394/// # Errors
395///
396/// Returns `DecodeFailed` with the codec source error when decoding fails.
397fn decode<C, V>(input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError>
398where
399    C: Default + ValueDecoder<str, Output = V>,
400    <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
401    V: 'static,
402{
403    C::default()
404        .decode(input)
405        .map(|value| Box::new(value) as Box<dyn Any>)
406        .map_err(|source| ValueCodecExecutionError::DecodeFailed {
407            codec_type: type_name::<C>(),
408            source: Box::new(source),
409        })
410}