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}