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}