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 pub fn encode(&self, value: &dyn Any) -> Result<String, ValueCodecExecutionError> {
180 (self.encode)(value)
181 }
182
183 /// Decodes one string into a safely erased value of the declared type.
184 ///
185 /// # Parameters
186 ///
187 /// - `input`: String borrowed for one decoding operation.
188 ///
189 /// # Returns
190 ///
191 /// Returns a boxed owned value that can be downcast to the declared type.
192 ///
193 /// # Errors
194 ///
195 /// Returns the typed decoder source error.
196 pub fn decode(&self, input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError> {
197 (self.decode)(input)
198 }
199}
200
201impl Debug for ValueStringCodecDescriptor {
202 /// Formats descriptor metadata without invoking either codec function.
203 ///
204 /// # Parameters
205 ///
206 /// - `formatter`: Destination receiving the diagnostic type names.
207 ///
208 /// # Returns
209 ///
210 /// Returns success after both names have been formatted.
211 ///
212 /// # Errors
213 ///
214 /// Returns the underlying formatting failure.
215 fn fmt(&self, formatter: &mut Formatter<'_>) -> FmtResult {
216 formatter
217 .debug_struct("ValueStringCodecDescriptor")
218 .field("codec_type_name", &self.codec_type_name())
219 .field("value_type_name", &self.value_type_name())
220 .finish_non_exhaustive()
221 }
222}
223
224/// Downcasts and encodes one typed value.
225fn encode<C, V>(value: &dyn Any) -> Result<String, ValueCodecExecutionError>
226where
227 C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
228 <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
229 <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
230 V: 'static,
231{
232 let value = value
233 .downcast_ref::<V>()
234 .ok_or_else(|| ValueCodecExecutionError::TypeMismatch {
235 expected_type: type_name::<V>(),
236 actual_type: value.type_id(),
237 })?;
238 C::default()
239 .encode(value)
240 .map_err(|source| ValueCodecExecutionError::EncodeFailed {
241 codec_type: type_name::<C>(),
242 source: Box::new(source),
243 })
244}
245
246/// Decodes one string and erases the typed output safely.
247fn decode<C, V>(input: &str) -> Result<Box<dyn Any>, ValueCodecExecutionError>
248where
249 C: Default + ValueEncoder<V, Output = String> + ValueDecoder<str, Output = V> + 'static,
250 <C as ValueEncoder<V>>::Error: Error + Send + Sync + 'static,
251 <C as ValueDecoder<str>>::Error: Error + Send + Sync + 'static,
252 V: 'static,
253{
254 C::default()
255 .decode(input)
256 .map(|value| Box::new(value) as Box<dyn Any>)
257 .map_err(|source| ValueCodecExecutionError::DecodeFailed {
258 codec_type: type_name::<C>(),
259 source: Box::new(source),
260 })
261}