dec_sixbit/
struct_api.rs

1//! Provides the `DecSixbit` struct for encapsulated handling of SIXBIT-encoded data.
2//!
3//! The `DecSixbit` struct offers a more feature-rich and structured API for encoding and decoding operations,
4//! leveraging the underlying encoding and decoding functions.
5//!
6//! ## Features
7//! - Encapsulates SIXBIT-encoded data and its metadata.
8//! - Implements common traits for ease of use.
9//! - Provides both encoding and decoding functionalities.
10
11use crate::{encode::encode, decode::decode_unchecked, Error};
12use std::fmt;
13
14/// The `DecSixbit` struct stores the encoded bytes and provides methods
15/// for accessing the encoded data and retrieving the original string.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
17pub struct DecSixbit {
18    /// Original string length
19    pub(crate) len: usize,
20    /// Packed bytes where every 3 bytes contain 4 characters (6 bits each)
21    pub(crate) bytes: Vec<u8>,
22}
23
24impl DecSixbit {
25    /// The marker byte for trailing spaces in the last block is added when the length is a multiple of 4, and the last 6 bits are all zero.
26    const TRAILING_SPACE_MARKER: u8 = 0b11;
27
28    /// Creates a new DecSixbit instance by encoding the input string.
29    /// Only accepts ASCII characters in the range 32-95 (space through underscore).
30    /// Creates a new `DecSixbit` instance by encoding the input string.
31    ///
32    /// # Parameters
33    /// - `str`: The input string to encode. Must contain only ASCII characters in the range 32-95.
34    ///
35    /// # Errors
36    /// Returns an [`Error::InvalidCharacter`] if the input contains invalid characters.
37    ///
38    /// # Examples
39    ///
40    /// ```rust
41    /// use dec_sixbit::DecSixbit;
42    ///
43    /// let sixbit = DecSixbit::new("HELLO").unwrap();
44    /// ```
45    #[inline(always)]
46    pub fn new(str: &str) -> Result<Self, Error> {
47        let (mut bytes, len) = encode(str)?;
48        // Check if TRAILING_SPACE_MARKER needs to be added
49        if len % 4 == 0 && len != 0 && (bytes.last().unwrap() & 0b111111) == 0 {
50            bytes.push(Self::TRAILING_SPACE_MARKER);
51        }
52        Ok(Self { bytes, len })
53    }
54
55    /// Returns a reference to the encoded SIXBIT bytes.
56    ///
57    /// # Returns
58    /// A slice of bytes containing the SIXBIT-encoded data.
59    ///
60    /// # Examples
61    ///
62    /// ```rust
63    /// use dec_sixbit::DecSixbit;
64    ///
65    /// let sixbit = DecSixbit::new("HELLO").unwrap();
66    /// let encoded = sixbit.as_bytes();
67    /// ```
68    #[inline(always)]
69    pub fn as_bytes(&self) -> &[u8] {
70        &self.bytes
71    }
72
73    /// Returns the length of the original input string.
74    ///
75    /// # Returns
76    /// The number of characters in the original string before encoding.
77    ///
78    /// # Examples
79    ///
80    /// ```rust
81    /// use dec_sixbit::DecSixbit;
82    ///
83    /// let sixbit = DecSixbit::new("HELLO").unwrap();
84    /// assert_eq!(sixbit.len(), 5);
85    /// ```
86    #[inline(always)]
87    pub fn len(&self) -> usize {
88        self.len
89    }
90
91    /// Checks if the encoded SIXBIT data is empty.
92    ///
93    /// # Returns
94    /// `true` if the original input string was empty, otherwise `false`.
95    ///
96    /// # Examples
97    ///
98    /// ```rust
99    /// use dec_sixbit::DecSixbit;
100    ///
101    /// let sixbit = DecSixbit::new("").unwrap();
102    /// assert!(sixbit.is_empty());
103    /// ```
104    #[inline(always)]
105    pub fn is_empty(&self) -> bool {
106        self.len == 0
107    }
108
109    /// Attempts to create a `DecSixbit` instance from a slice of encoded bytes.
110    ///
111    /// # Parameters
112    /// - `bytes`: A slice of bytes containing SIXBIT-encoded data.
113    ///
114    /// # Returns
115    /// - `Ok(Self)` if the slice is successfully parsed.
116    /// - `Err(Error)` if the slice has an invalid format or contains invalid data.
117    #[inline(always)]
118    pub fn try_from_slice(bytes: &[u8]) -> Result<Self, Error> {
119        let num_full_blocks = bytes.len() / 3;
120        let num_remain_bytes = bytes.len() % 3;
121
122        let len = match num_remain_bytes {
123            0 => num_full_blocks * 4,
124            1 => {
125                if bytes.last().unwrap() == &Self::TRAILING_SPACE_MARKER {
126                    num_full_blocks * 4
127                } else {
128                    num_full_blocks * 4 + 1
129                }
130            },
131            2 => num_full_blocks * 4 + 2,
132            _ => unreachable!(),
133        };
134        Ok(Self {
135            len,
136            bytes: bytes.to_vec(),
137        })
138    }
139
140    /// Creates a `DecSixbit` instance from a slice of encoded bytes.
141    ///
142    /// # Parameters
143    /// - `bytes`: A slice of bytes containing SIXBIT-encoded data.
144    ///
145    /// # Panics
146    /// - Panics if the slice has an invalid format or contains invalid data.
147    #[inline(always)]
148    pub fn from_slice(bytes: &[u8]) -> Self {
149        Self::try_from_slice(bytes).unwrap()
150    }
151
152    /// Gets the character at the specified position.
153    ///
154    /// # Parameters
155    /// - `index`: The position of the character to retrieve.
156    ///
157    /// # Returns
158    /// An `Option<char>` which is `Some(char)` if the index is valid, or `None` otherwise.
159    ///
160    /// # Examples
161    ///
162    /// ```rust
163    /// use dec_sixbit::DecSixbit;
164    ///
165    /// let sixbit = DecSixbit::new("HELLO").unwrap();
166    /// assert_eq!(sixbit.get(1), Some('E'));
167    /// assert_eq!(sixbit.get(5), None);
168    /// ```
169    pub fn get(&self, index: usize) -> Option<char> {
170        self.to_string().chars().nth(index)
171    }
172
173    /// Checks if the string starts with the given prefix.
174    ///
175    /// # Parameters
176    /// - `prefix`: The prefix string to check.
177    ///
178    /// # Returns
179    /// `true` if the string starts with the given prefix, otherwise `false`.
180    ///
181    /// # Examples
182    ///
183    /// ```rust
184    /// use dec_sixbit::DecSixbit;
185    ///
186    /// let sixbit = DecSixbit::new("HELLO").unwrap();
187    /// assert!(sixbit.starts_with("HE"));
188    /// assert!(!sixbit.starts_with("EL"));
189    /// ```
190    pub fn starts_with<P: AsRef<str>>(&self, prefix: P) -> bool {
191        self.to_string().starts_with(prefix.as_ref())
192    }
193
194    /// Checks if the string ends with the given suffix.
195    ///
196    /// # Parameters
197    /// - `suffix`: The suffix string to check.
198    ///
199    /// # Returns
200    /// `true` if the string ends with the given suffix, otherwise `false`.
201    ///
202    /// # Examples
203    ///
204    /// ```rust
205    /// use dec_sixbit::DecSixbit;
206    ///
207    /// let sixbit = DecSixbit::new("HELLO").unwrap();
208    /// assert!(sixbit.ends_with("LO"));
209    /// assert!(!sixbit.ends_with("HE"));
210    /// ```
211    pub fn ends_with<P: AsRef<str>>(&self, suffix: P) -> bool {
212        self.to_string().ends_with(suffix.as_ref())
213    }
214
215    /// Checks if the string contains the given substring.
216    ///
217    /// # Parameters
218    /// - `substring`: The substring to search for.
219    ///
220    /// # Returns
221    /// `true` if the string contains the given substring, otherwise `false`.
222    ///
223    /// # Examples
224    ///
225    /// ```rust
226    /// use dec_sixbit::DecSixbit;
227    ///
228    /// let sixbit = DecSixbit::new("HELLO").unwrap();
229    /// assert!(sixbit.contains("ELL"));
230    /// assert!(!sixbit.contains("XYZ"));
231    /// ```
232    pub fn contains<P: AsRef<str>>(&self, substring: P) -> bool {
233        self.to_string().contains(substring.as_ref())
234    }
235}
236
237impl fmt::Display for DecSixbit {
238    #[inline(always)]
239    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240        // Use decode_unchecked because the TRAILING_SPACE_MARKER byte might have been added at the end
241        let decoded = decode_unchecked(&self.bytes, self.len);
242        write!(f, "{}", decoded)
243    }
244}
245
246impl std::str::FromStr for DecSixbit {
247    type Err = Error;
248
249    fn from_str(s: &str) -> Result<Self, Self::Err> {
250        Self::new(s)
251    }
252}
253
254impl TryFrom<&str> for DecSixbit {
255    type Error = Error;
256
257    fn try_from(s: &str) -> Result<Self, Self::Error> {
258        Self::new(s)
259    }
260}
261
262impl TryFrom<&[u8]> for DecSixbit {
263    type Error = Error;
264
265    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
266        Self::try_from_slice(bytes)
267    }
268}
269
270impl TryFrom<Vec<u8>> for DecSixbit {
271    type Error = Error;
272
273    fn try_from(bytes: Vec<u8>) -> Result<Self, Self::Error> {
274        Self::try_from(bytes.as_slice())
275    }
276}
277
278impl TryFrom<&Vec<u8>> for DecSixbit {
279    type Error = Error;
280
281    fn try_from(bytes: &Vec<u8>) -> Result<Self, Self::Error> {
282        Self::try_from(bytes.as_slice())
283    }
284}
285
286impl AsRef<[u8]> for DecSixbit {
287    fn as_ref(&self) -> &[u8] {
288        self.as_bytes()
289    }
290}
291
292impl serde::Serialize for DecSixbit {
293    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
294        if serializer.is_human_readable() {
295            self.to_string().serialize(serializer)
296        } else {
297            (&self.len, &self.bytes).serialize(serializer)
298        }
299    }
300}
301
302mod deserialize {
303    use super::DecSixbit;
304
305    pub(super) struct DecSixbitVisitor;
306
307    #[allow(clippy::needless_lifetimes)]
308    impl<'de> serde::de::Visitor<'de> for DecSixbitVisitor {
309        type Value = DecSixbit;
310
311        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312            formatter.write_str("bytes or string")
313        }
314
315        fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
316        where
317            E: serde::de::Error,
318        {
319            DecSixbit::new(v).map_err(E::custom)
320        }
321    }
322}
323
324impl<'de> serde::Deserialize<'de> for DecSixbit {
325    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<DecSixbit, D::Error> {
326        use serde::de::Error;
327        if deserializer.is_human_readable() {
328            deserializer
329                .deserialize_str(deserialize::DecSixbitVisitor)
330                .map_err(D::Error::custom)
331        } else {
332            let (len, bytes) = <(usize, Vec<u8>)>::deserialize(deserializer)?;
333            Ok(DecSixbit { len, bytes })
334        }
335    }
336}
337
338#[cfg(test)]
339mod tests {
340    use super::DecSixbit;
341    use crate::Error;
342    use std::convert::TryFrom;
343
344    #[test]
345    fn test_new_valid_input() {
346        let input = "HELLO";
347        let sixbit = DecSixbit::new(input).unwrap();
348        assert_eq!(sixbit.len(), input.len());
349        assert_eq!(sixbit.to_string(), input);
350    }
351
352    #[test]
353    fn test_new_empty_string() {
354        let input = "";
355        let sixbit = DecSixbit::new(input).unwrap();
356        assert_eq!(sixbit.len(), 0);
357        assert!(sixbit.is_empty());
358        assert_eq!(sixbit.to_string(), input);
359    }
360
361    #[test]
362    fn test_new_invalid_character() {
363        let input = "HELLO😃";
364        let result = DecSixbit::new(input);
365        assert!(result.is_err());
366        match result {
367            Err(Error::InvalidCharacter { .. }) => (),
368            _ => panic!("Expected InvalidCharacter error"),
369        }
370    }
371
372    #[test]
373    fn test_as_bytes() {
374        let input = "TEST";
375        let sixbit = DecSixbit::new(input).unwrap();
376        let encoded = sixbit.as_bytes();
377        // The exact encoding depends on the `encode` implementation.
378        // Here we check that the bytes are not empty and have the expected length.
379        assert!(!encoded.is_empty());
380        assert_eq!(encoded.len(), 3); // 4 characters -> 3 bytes
381    }
382
383    #[test]
384    fn test_try_from_slice_valid() {
385        let input = "DATA";
386        let sixbit = DecSixbit::new(input).unwrap();
387        let bytes = sixbit.as_bytes();
388        let decoded = DecSixbit::try_from_slice(bytes).unwrap();
389        assert_eq!(sixbit, decoded);
390    }
391
392    #[test]
393    fn test_try_from_slice_with_trailing_marker() {
394        let input = "FOUR";
395        let sixbit = DecSixbit::new(input).unwrap();
396        let mut bytes = sixbit.as_bytes().to_vec();
397        // Manually add TRAILING_SPACE_MARKER
398        bytes.push(DecSixbit::TRAILING_SPACE_MARKER);
399        let decoded = DecSixbit::try_from_slice(&bytes).unwrap();
400        assert_eq!(decoded.len, sixbit.len);
401        assert_eq!(decoded.bytes, bytes);
402    }
403
404    #[test]
405    fn test_get_valid_index() {
406        let input = "WORLD";
407        let sixbit = DecSixbit::new(input).unwrap();
408        assert_eq!(sixbit.get(0), Some('W'));
409        assert_eq!(sixbit.get(4), Some('D'));
410    }
411
412    #[test]
413    fn test_get_invalid_index() {
414        let input = "WORLD";
415        let sixbit = DecSixbit::new(input).unwrap();
416        assert_eq!(sixbit.get(5), None);
417    }
418
419    #[test]
420    fn test_starts_with() {
421        let sixbit = DecSixbit::new("START").unwrap();
422        assert!(sixbit.starts_with("ST"));
423        assert!(!sixbit.starts_with("TA"));
424    }
425
426    #[test]
427    fn test_ends_with() {
428        let sixbit = DecSixbit::new("ENDING").unwrap();
429        assert!(sixbit.ends_with("ING"));
430        assert!(!sixbit.ends_with("END"));
431    }
432
433    #[test]
434    fn test_contains() {
435        let sixbit = DecSixbit::new("CONTAINS").unwrap();
436        assert!(sixbit.contains("TAI"));
437        assert!(!sixbit.contains("XYZ"));
438    }
439
440    #[test]
441    fn test_display_trait() {
442        let input = "DISPLAY";
443        let sixbit = DecSixbit::new(input).unwrap();
444        let displayed = format!("{}", sixbit);
445        assert_eq!(displayed, input);
446    }
447
448    #[test]
449    fn test_from_str() {
450        let input = "FROM_STR";
451        let sixbit: DecSixbit = input.parse().unwrap();
452        assert_eq!(sixbit.to_string(), input);
453    }
454
455    #[test]
456    fn test_try_from_str_valid() {
457        let input = "TRY_FROM";
458        let sixbit = DecSixbit::try_from(input).unwrap();
459        assert_eq!(sixbit.to_string(), input);
460    }
461
462    #[test]
463    fn test_try_from_str_invalid() {
464        let input = "INVALID😤";
465        let result = DecSixbit::try_from(input);
466        assert!(result.is_err());
467    }
468
469    #[test]
470    fn test_try_from_bytes_valid() {
471        let input = "BYTES";
472        let sixbit = DecSixbit::new(input).unwrap();
473        let bytes = sixbit.as_bytes();
474        let decoded = DecSixbit::try_from(bytes).unwrap();
475        assert_eq!(sixbit, decoded);
476    }
477
478    #[test]
479    fn test_try_from_vec_bytes() {
480        let input = "VEC_BYTES";
481        let sixbit = DecSixbit::new(input).unwrap();
482        let bytes = sixbit.as_bytes().to_vec();
483        let decoded = DecSixbit::try_from(bytes).unwrap();
484        assert_eq!(sixbit, decoded);
485    }
486
487    #[test]
488    fn test_serde_serialize_deserialize_human_readable() {
489        use serde_json;
490
491        let input = "SERIALIZE";
492        let sixbit = DecSixbit::new(input).unwrap();
493        let serialized = serde_json::to_string(&sixbit).unwrap();
494        assert_eq!(serialized, format!("\"{}\"", input));
495
496        let deserialized: DecSixbit = serde_json::from_str(&serialized).unwrap();
497        assert_eq!(sixbit, deserialized);
498    }
499
500    #[test]
501    fn test_serde_serialize_deserialize_binary() {
502        use bincode;
503
504        let input = "BINARY";
505        let sixbit = DecSixbit::new(input).unwrap();
506        let serialized = bincode::serialize(&sixbit).unwrap();
507        let deserialized: DecSixbit = bincode::deserialize(&serialized).unwrap();
508        assert_eq!(sixbit, deserialized);
509    }
510
511    #[test]
512    fn test_is_empty() {
513        let sixbit = DecSixbit::new("").unwrap();
514        assert!(sixbit.is_empty());
515
516        let sixbit = DecSixbit::new("NON_EMPTY").unwrap();
517        assert!(!sixbit.is_empty());
518    }
519
520    #[test]
521    fn test_len() {
522        let input = "LENGTH";
523        let sixbit = DecSixbit::new(input).unwrap();
524        assert_eq!(sixbit.len(), input.len());
525    }
526
527    #[test]
528    fn test_equality() {
529        let input1 = "EQUAL";
530        let input2 = "EQUAL";
531        let sixbit1 = DecSixbit::new(input1).unwrap();
532        let sixbit2 = DecSixbit::new(input2).unwrap();
533        assert_eq!(sixbit1, sixbit2);
534    }
535
536    #[test]
537    fn test_ordering() {
538        let sixbit_a = DecSixbit::new("AAA").unwrap();
539        let sixbit_b = DecSixbit::new("AAB").unwrap();
540        assert!(sixbit_a < sixbit_b);
541    }
542
543    #[test]
544    fn test_hash() {
545        use std::collections::HashSet;
546
547        let input1 = "HASH1";
548        let input2 = "HASH2";
549        let sixbit1 = DecSixbit::new(input1).unwrap();
550        let sixbit2 = DecSixbit::new(input2).unwrap();
551
552        let mut set = HashSet::new();
553        set.insert(sixbit1.clone());
554        set.insert(sixbit2.clone());
555
556        assert!(set.contains(&sixbit1));
557        assert!(set.contains(&sixbit2));
558    }
559}