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::error::Error;
18
19use crate::ValueCodecExecutionError;
20use crate::ValueDecoder;
21use crate::ValueEncoder;
22
23/// Checked type-erased encoding entry point returning an owned string.
24type EncodeFn = fn(&dyn Any) -> Result<String, ValueCodecExecutionError>;
25/// Type-erased decoding entry point returning a boxed owned value.
26type DecodeFn = fn(&str) -> Result<Box<dyn Any>, ValueCodecExecutionError>;
27
28/// An immutable, safely erased bidirectional string codec for one value type.
29///
30/// Each operation creates a fresh default codec; no codec state is retained
31/// between calls. Type identities are process-local and names are diagnostic,
32/// not stable IDs.
33///
34/// # Examples
35///
36/// ```
37/// use core::convert::Infallible;
38///
39/// use qubit_codec::ValueDecoder;
40/// use qubit_codec::ValueEncoder;
41/// use qubit_codec::ValueStringCodecDescriptor;
42///
43/// #[derive(Default)]
44/// struct Text;
45/// impl ValueEncoder<u32> for Text {
46///     type Output = String;
47///     type Error = Infallible;
48///
49///     fn encode(&mut self, input: &u32) -> Result<String, Infallible> {
50///         Ok(input.to_string())
51///     }
52/// }
53/// impl ValueDecoder<str> for Text {
54///     type Output = u32;
55///     type Error = Infallible;
56///
57///     fn decode(&mut self, input: &str) -> Result<u32, Infallible> {
58///         Ok(input.len() as u32)
59///     }
60/// }
61///
62/// let descriptor = ValueStringCodecDescriptor::of::<Text, u32>();
63///
64/// // `encode` takes an erased `&dyn Any`, so the value type must be named.
65/// let value: u32 = 7;
66/// assert_eq!(descriptor.encode(&value).unwrap(), "7");
67///
68/// let decoded = descriptor.decode("hello").unwrap();
69/// assert_eq!(*decoded.downcast::<u32>().unwrap(), 5);
70/// ```
71#[derive(Clone, Copy)]
72pub struct ValueStringCodecDescriptor {
73    /// Returns the process-local identity of the concrete codec.
74    codec_type_id: fn() -> TypeId,
75    /// Returns the diagnostic name of the concrete codec.
76    codec_type_name: fn() -> &'static str,
77    /// Returns the process-local identity of the decoded value.
78    value_type_id: fn() -> TypeId,
79    /// Returns the diagnostic name of the decoded value.
80    value_type_name: fn() -> &'static str,
81    /// Checks the erased input type and invokes a fresh default encoder.
82    encode: EncodeFn,
83    /// Invokes a fresh default decoder and boxes its owned output.
84    decode: DecodeFn,
85}
86
87impl ValueStringCodecDescriptor {
88    /// Creates a descriptor for codec `C` and value type `V`.
89    ///
90    /// Both codec error types must implement `Send + Sync` so execution errors
91    /// can retain their source across thread boundaries. The codec and value
92    /// types themselves need not implement these traits; `V` only needs to be
93    /// `'static` because decoded values are returned through `Any`.
94    ///
95    /// # Type Parameters
96    ///
97    /// - `C`: Default-constructible string encoder/decoder with owned errors.
98    /// - `V`: Owned value type accepted and produced by `C`.
99    ///
100    /// # Returns
101    ///
102    /// Returns immutable function metadata without constructing a codec.
103    #[must_use]
104    #[inline]
105    pub const fn of<C, V>() -> Self
106    where
107        C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
108        <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
109        <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
110        V: 'static,
111    {
112        Self {
113            codec_type_id: TypeId::of::<C>,
114            codec_type_name: type_name::<C>,
115            value_type_id: TypeId::of::<V>,
116            value_type_name: type_name::<V>,
117            encode: encode::<C, V>,
118            decode: decode::<C, V>,
119        }
120    }
121
122    /// Returns the process-local identity of the concrete codec.
123    ///
124    /// # Returns
125    ///
126    /// Returns the `TypeId` of the codec this descriptor was built for.
127    #[must_use]
128    #[inline]
129    pub fn codec_type_id(&self) -> TypeId {
130        (self.codec_type_id)()
131    }
132
133    /// Returns the diagnostic name of the concrete codec.
134    ///
135    /// # Returns
136    ///
137    /// Returns the `type_name` of the codec, for diagnostics only.
138    #[must_use]
139    #[inline]
140    pub fn codec_type_name(&self) -> &'static str {
141        (self.codec_type_name)()
142    }
143
144    /// Returns the process-local identity of the decoded value.
145    ///
146    /// # Returns
147    ///
148    /// Returns the `TypeId` of the value this descriptor accepts and produces.
149    #[must_use]
150    #[inline]
151    pub fn value_type_id(&self) -> TypeId {
152        (self.value_type_id)()
153    }
154
155    /// Returns the diagnostic name of the decoded value.
156    ///
157    /// # Returns
158    ///
159    /// Returns the `type_name` of the value, for diagnostics only.
160    #[must_use]
161    #[inline]
162    pub fn value_type_name(&self) -> &'static str {
163        (self.value_type_name)()
164    }
165
166    /// Encodes one erased value after checking its concrete type.
167    ///
168    /// # Parameters
169    ///
170    /// - `value`: Borrowed value whose concrete type must match the descriptor.
171    ///
172    /// # Returns
173    ///
174    /// Returns an owned string produced by a fresh default codec.
175    ///
176    /// # Errors
177    ///
178    /// Returns a type mismatch or the typed encoder source error.
179    #[inline]
180    pub fn encode(&self, value: &dyn Any) -> Result<String, ValueCodecExecutionError> {
181        (self.encode)(value)
182    }
183
184    /// Decodes one string into a safely erased value of the declared type.
185    ///
186    /// # Parameters
187    ///
188    /// - `input`: String borrowed for one decoding operation.
189    ///
190    /// # Returns
191    ///
192    /// Returns a boxed owned value that can be downcast to the declared type.
193    ///
194    /// # Errors
195    ///
196    /// Returns the typed decoder source error.
197    #[inline]
198    pub fn decode(&self, input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError> {
199        (self.decode)(input)
200    }
201}
202
203impl Debug for ValueStringCodecDescriptor {
204    /// Formats descriptor metadata without invoking either codec function.
205    ///
206    /// # Parameters
207    ///
208    /// - `formatter`: Destination receiving the diagnostic type names.
209    ///
210    /// # Returns
211    ///
212    /// Returns success after both names have been formatted.
213    ///
214    /// # Errors
215    ///
216    /// Returns the underlying formatting failure.
217    fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
218        formatter
219            .debug_struct("ValueStringCodecDescriptor")
220            .field("codec_type_name", &self.codec_type_name())
221            .field("value_type_name", &self.value_type_name())
222            .finish_non_exhaustive()
223    }
224}
225
226/// Downcasts an erased value and encodes it with a fresh default codec.
227///
228/// # Type Parameters
229///
230/// - `C`: Default codec with matching string encode/decode contracts.
231/// - `V`: Concrete owned value type required by the descriptor.
232///
233/// # Parameters
234///
235/// - `value`: Erased input borrowed until serialization completes.
236///
237/// # Returns
238///
239/// Returns the owned string produced by the codec.
240///
241/// # Errors
242///
243/// Returns `TypeMismatch` when the input has a different concrete type, or
244/// `EncodeFailed` with the codec source error when encoding fails.
245fn encode<C, V>(value: &dyn Any) -> Result<String, ValueCodecExecutionError>
246where
247    C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
248    <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
249    <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
250    V: 'static,
251{
252    let value = value
253        .downcast_ref::<V>()
254        .ok_or_else(|| ValueCodecExecutionError::TypeMismatch {
255            expected_type: type_name::<V>(),
256            actual_type: value.type_id(),
257        })?;
258    C::default()
259        .encode(value)
260        .map_err(|source| ValueCodecExecutionError::EncodeFailed {
261            codec_type: type_name::<C>(),
262            source: Box::new(source),
263        })
264}
265
266/// Decodes one string with a fresh default codec and erases its typed output.
267///
268/// # Type Parameters
269///
270/// - `C`: Default codec with matching string encode/decode contracts.
271/// - `V`: Concrete owned value type produced by the descriptor.
272///
273/// # Parameters
274///
275/// - `input`: String slice borrowed until decoding completes.
276///
277/// # Returns
278///
279/// Returns the decoded value in a box that can be downcast to `V`.
280///
281/// # Errors
282///
283/// Returns `DecodeFailed` with the codec source error when decoding fails.
284fn decode<C, V>(input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError>
285where
286    C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
287    <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
288    <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
289    V: 'static,
290{
291    C::default()
292        .decode(input)
293        .map(|value| Box::new(value) as Box<dyn Any>)
294        .map_err(|source| ValueCodecExecutionError::DecodeFailed {
295            codec_type: type_name::<C>(),
296            source: Box::new(source),
297        })
298}