Skip to main content

qubit_codec_misc/
hex_codec_config.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//! Configuration shared by hexadecimal byte codecs.
9
10/// Configuration shared by [`crate::HexCodec`] encoding and decoding
11/// operations.
12///
13/// The configuration is intentionally separate from the codec value so the
14/// same formatting and decoding policy can be reused by multiple codecs.
15///
16/// # Examples
17///
18/// ```
19/// use qubit_codec_misc::HexCodec;
20/// use qubit_codec_misc::HexCodecConfig;
21///
22/// let config = HexCodecConfig::new()
23///     .with_uppercase(true)
24///     .with_prefix("0x");
25/// let codec = HexCodec::from_config(config);
26/// assert_eq!(codec.encode(&[0x1f, 0x8b]), "0x1F8B");
27/// ```
28#[must_use]
29#[derive(Debug, Clone, PartialEq, Eq)]
30pub struct HexCodecConfig {
31    /// Whether to use uppercase hexadecimal digits while encoding.
32    pub(crate) uppercase: bool,
33    /// The prefix to use before the whole encoded string.
34    pub(crate) prefix: Option<String>,
35    /// The prefix to use before each encoded byte.
36    pub(crate) byte_prefix: Option<String>,
37    /// The separator to use between bytes in the encoded string.
38    pub(crate) separator: Option<String>,
39    /// Whether to ignore ASCII whitespace while decoding.
40    pub(crate) ignore_ascii_whitespace: bool,
41    /// Whether to ignore ASCII case when matching configured prefixes.
42    pub(crate) ignore_prefix_case: bool,
43}
44
45impl HexCodecConfig {
46    /// Creates a lowercase configuration without prefixes or separators.
47    ///
48    /// # Returns
49    /// A configuration with lowercase encoding and all optional formatting
50    /// tokens disabled.
51    #[inline]
52    pub fn new() -> Self {
53        Self {
54            uppercase: false,
55            prefix: None,
56            byte_prefix: None,
57            separator: None,
58            ignore_ascii_whitespace: false,
59            ignore_prefix_case: false,
60        }
61    }
62
63    /// Returns whether encoded digits use uppercase characters.
64    ///
65    /// # Returns
66    /// `true` if uppercase hexadecimal digits are used during encoding.
67    #[must_use]
68    #[inline]
69    pub fn is_uppercase(&self) -> bool {
70        self.uppercase
71    }
72
73    /// Returns the configured whole-output prefix.
74    ///
75    /// # Returns
76    /// `Some` contains the prefix written before the encoded bytes; `None`
77    /// means no whole-output prefix is configured.
78    #[inline]
79    pub fn prefix(&self) -> Option<&str> {
80        self.prefix.as_deref()
81    }
82
83    /// Returns the configured per-byte prefix.
84    ///
85    /// # Returns
86    /// `Some` contains the prefix written before each encoded byte; `None`
87    /// means no per-byte prefix is configured.
88    #[inline]
89    pub fn byte_prefix(&self) -> Option<&str> {
90        self.byte_prefix.as_deref()
91    }
92
93    /// Returns the configured separator.
94    ///
95    /// # Returns
96    /// `Some` contains the separator written between bytes; `None` means no
97    /// separator is configured.
98    #[inline]
99    pub fn separator(&self) -> Option<&str> {
100        self.separator.as_deref()
101    }
102
103    /// Returns whether ASCII whitespace is ignored while decoding.
104    ///
105    /// # Returns
106    /// `true` if ASCII whitespace is skipped during decoding.
107    #[must_use]
108    #[inline]
109    pub fn ignores_ascii_whitespace(&self) -> bool {
110        self.ignore_ascii_whitespace
111    }
112
113    /// Returns whether configured prefixes are matched case-insensitively.
114    ///
115    /// # Returns
116    /// `true` if ASCII letter case is ignored when matching prefixes.
117    #[must_use]
118    #[inline]
119    pub fn ignores_prefix_case(&self) -> bool {
120        self.ignore_prefix_case
121    }
122
123    /// Sets whether encoded digits should be uppercase.
124    ///
125    /// # Parameters
126    /// - `uppercase`: Whether encoding uses uppercase hexadecimal digits.
127    ///
128    /// # Returns
129    /// The updated configuration.
130    #[inline]
131    pub fn with_uppercase(mut self, uppercase: bool) -> Self {
132        self.uppercase = uppercase;
133        self
134    }
135
136    /// Sets a whole-output prefix.
137    ///
138    /// # Parameters
139    /// - `prefix`: Text written once before all encoded bytes.
140    ///
141    /// # Returns
142    /// The updated configuration.
143    #[inline]
144    pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
145        self.prefix = Some(prefix.into());
146        self
147    }
148
149    /// Sets a per-byte prefix.
150    ///
151    /// # Parameters
152    /// - `prefix`: Text written before each encoded byte.
153    ///
154    /// # Returns
155    /// The updated configuration.
156    #[inline]
157    pub fn with_byte_prefix(mut self, prefix: impl Into<String>) -> Self {
158        self.byte_prefix = Some(prefix.into());
159        self
160    }
161
162    /// Sets a separator between encoded bytes.
163    ///
164    /// With a non-empty separator, the two hexadecimal digits of each byte
165    /// must be contiguous. Ignored ASCII whitespace may surround complete
166    /// byte tokens and configured separators, but does not replace a required
167    /// prefix or separator. Without a non-empty separator, ignored whitespace
168    /// may also occur between the two digits of a byte. A complete structural
169    /// token is matched before skipping additional whitespace. Non-whitespace
170    /// separators are matched exactly; a whitespace-only separator still
171    /// requires whitespace between bytes, and ignored ASCII whitespace may
172    /// consume additional adjacent whitespace. Prefix case folding does not
173    /// apply to separators.
174    ///
175    /// # Parameters
176    /// - `separator`: Text written between adjacent encoded bytes.
177    ///
178    /// # Returns
179    /// The updated configuration.
180    #[inline]
181    pub fn with_separator(mut self, separator: impl Into<String>) -> Self {
182        self.separator = Some(separator.into());
183        self
184    }
185
186    /// Sets whether ASCII whitespace is ignored while decoding.
187    ///
188    /// With a non-empty separator, the two hexadecimal digits of each byte
189    /// must be contiguous. Ignored ASCII whitespace may surround complete
190    /// byte tokens and configured separators, but does not replace a required
191    /// prefix or separator. Without a non-empty separator, ignored whitespace
192    /// may also occur between the two digits of a byte. A complete structural
193    /// token is matched before skipping additional whitespace. Non-whitespace
194    /// separators are matched exactly; a whitespace-only separator still
195    /// requires whitespace between bytes, and ignored ASCII whitespace may
196    /// consume additional adjacent whitespace. Prefix case folding does not
197    /// apply to separators.
198    ///
199    /// # Parameters
200    /// - `ignore`: Whether ASCII whitespace is skipped during decoding.
201    ///
202    /// # Returns
203    /// The updated configuration.
204    #[inline]
205    pub fn with_ignored_ascii_whitespace(mut self, ignore: bool) -> Self {
206        self.ignore_ascii_whitespace = ignore;
207        self
208    }
209
210    /// Sets whether configured prefixes are matched case-insensitively.
211    ///
212    /// # Parameters
213    /// - `ignore`: Whether prefix matching ignores ASCII letter case.
214    ///
215    /// # Returns
216    /// The updated configuration.
217    #[inline]
218    pub fn with_ignore_prefix_case(mut self, ignore: bool) -> Self {
219        self.ignore_prefix_case = ignore;
220        self
221    }
222}
223
224impl Default for HexCodecConfig {
225    /// Creates the default lowercase configuration without prefixes or
226    /// separators.
227    ///
228    /// # Returns
229    /// A configuration with lowercase encoding and all optional formatting
230    /// tokens disabled.
231    #[inline]
232    fn default() -> Self {
233        Self::new()
234    }
235}