try_ascii/
lib.rs

1// Copyright Open Logistics Foundation
2//
3// Licensed under the Open Logistics Foundation License 1.3.
4// For details on the licensing terms, see the LICENSE file.
5// SPDX-License-Identifier: OLFL-1.3
6
7#![doc = include_str!("../README.md")]
8#![no_std]
9
10/// Wrapper type that contains the byte slice and implements [Debug]
11///
12/// The [Debug] implementation prints the text parts as text and falls back to the default [Debug]
13/// implementation for `&[u8]` for the rest.
14pub struct MaybeAscii<'a> {
15    pub bytes: &'a [u8],
16}
17
18impl<'a> core::fmt::Debug for MaybeAscii<'a> {
19    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
20        let mut start = 0;
21        while start < self.bytes.len() {
22            let is_ascii = self.bytes[start] < 128;
23            let mut end = start + 1;
24            loop {
25                if end >= self.bytes.len() {
26                    break;
27                }
28                if (self.bytes[end] < 128) != is_ascii {
29                    break;
30                }
31                end += 1;
32            }
33            if is_ascii {
34                let s = core::str::from_utf8(&self.bytes[start..end]).expect("The slice should only contain ascii characters (<128) which should be convertible to a utf8 string.");
35                f.write_str(s)?;
36            } else {
37                self.bytes[start..end].fmt(f)?;
38            }
39            start = end;
40        }
41        Ok(())
42    }
43}
44
45/// Helper function to format the text parts of a byte slice as text and the non-text parts with
46/// the default [Debug] implementation for byte slices
47pub fn try_ascii(bytes: &[u8]) -> MaybeAscii<'_> {
48    MaybeAscii { bytes }
49}
50
51/// Trait that encapsulates the logic of `try_ascii`
52///
53/// The trait is automatically implemented for `[u8]`, `&[u8]` and everything that implements `AsRef<[u8]>`.
54pub trait TryAscii {
55    fn try_ascii(&self) -> MaybeAscii<'_>;
56}
57
58impl TryAscii for [u8] {
59    fn try_ascii(&self) -> MaybeAscii<'_> {
60        try_ascii(self)
61    }
62}
63
64impl TryAscii for &[u8] {
65    fn try_ascii(&self) -> MaybeAscii<'_> {
66        try_ascii(self)
67    }
68}
69
70impl TryAscii for dyn AsRef<[u8]> {
71    fn try_ascii(&self) -> MaybeAscii<'_> {
72        try_ascii(self.as_ref())
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    extern crate std;
79    use crate::try_ascii;
80    use crate::TryAscii;
81    use std::*;
82
83    #[test]
84    fn format_ascii() {
85        let res = format!("{:?}", try_ascii(b"Hello, World"));
86        assert_eq!(res, "Hello, World");
87    }
88
89    #[test]
90    fn format_non_ascii() {
91        let res = format!("{:?}", try_ascii(b"Hello, \xa0"));
92        assert_eq!(res, "Hello, [160]");
93    }
94
95    #[test]
96    fn format_slice() {
97        let res = format!("{:?}", try_ascii(b"Hello, \xa0\xa1\xa2\xa3"));
98        assert_eq!(res, "Hello, [160, 161, 162, 163]");
99    }
100
101    #[test]
102    fn format_hex() {
103        let res = format!("{:x?}", try_ascii(b"Hello, \xa0\xa1\xa2\xa3"));
104        assert_eq!(res, "Hello, [a0, a1, a2, a3]");
105    }
106
107    #[test]
108    fn format_pretty() {
109        let res = format!("{:#X?}", try_ascii(&[0xa0, 0xa1]));
110        assert_eq!(res, "[\n    0xA0,\n    0xA1,\n]");
111    }
112
113    #[test]
114    fn format_empty() {
115        let res = format!("{:?}", try_ascii(&[]));
116        assert_eq!(res, "");
117    }
118
119    #[test]
120    fn trait_for_u8() {
121        let res = format!("{:x?}", b"Hello, \xa0\xa1\xa2\xa3".try_ascii());
122        assert_eq!(res, "Hello, [a0, a1, a2, a3]");
123    }
124
125    #[test]
126    fn trait_for_ref_u8() {
127        let arr: &[u8] = b"Hello, \xa0\xa1\xa2\xa3";
128        let res = format!("{:x?}", arr.try_ascii());
129        assert_eq!(res, "Hello, [a0, a1, a2, a3]");
130    }
131
132    #[test]
133    fn trait_for_as_ref_u8() {
134        let arr: [u8; 11] = *b"Hello, \xa0\xa1\xa2\xa3";
135        let res = format!("{:x?}", arr.as_ref().try_ascii());
136        assert_eq!(res, "Hello, [a0, a1, a2, a3]");
137    }
138}