gosling/
ascii_string.rs

1#[cfg(test)]
2use anyhow::bail;
3use std::ops::Deref;
4
5#[derive(thiserror::Error, Debug)]
6pub(crate) enum Error {
7    #[error("input string is not ASCII: {0}")]
8    InvalidAscii(String),
9}
10
11/// An immutable wrapper around a String guaranteed to be ASCII encoded
12#[derive(Clone, PartialEq)]
13pub(crate) struct AsciiString {
14    value: String,
15}
16
17impl AsciiString {
18    pub fn new(value: String) -> Result<AsciiString, Error> {
19        if value.is_ascii() {
20            Ok(Self { value })
21        } else {
22            Err(Error::InvalidAscii(value))
23        }
24    }
25}
26
27impl Deref for AsciiString {
28    type Target = String;
29
30    fn deref(&self) -> &Self::Target {
31        &self.value
32    }
33}
34
35impl std::fmt::Debug for AsciiString {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        self.value.fmt(f)
38    }
39}
40
41impl std::fmt::Display for AsciiString {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        self.value.fmt(f)
44    }
45}
46
47#[test]
48fn test_ascii_string() -> anyhow::Result<()> {
49    let valid_ascii: [String; 8] = [
50        "".to_string(),
51        " !\"#$%&'()*+,-./".to_string(),
52        "0123456789".to_string(),
53        ":<=>?@".to_string(),
54        "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string(),
55        "[\\]^_`".to_string(),
56        "abcdefghijklmnopqstuvwxyz".to_string(),
57        "{|}~".to_string(),
58    ];
59
60    for string in valid_ascii {
61        match AsciiString::new(string) {
62            Ok(string) => println!("ascii: '{}'", string),
63            Err(err) => bail!("unexpected error: {}", err),
64        }
65    }
66
67    let utf8: [String; 2] = ["❤".to_string(), "heart ❤".to_string()];
68
69    for string in utf8 {
70        match AsciiString::new(string) {
71            Ok(string) => bail!("this is not ascii: {}", string),
72            Err(_) => (),
73        }
74    }
75
76    Ok(())
77}