aws_smithy_types/
str_bytes.rs

1/*
2 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 * SPDX-License-Identifier: Apache-2.0
4 */
5
6//! UTF-8 string byte buffer representation with validation amortization.
7
8use bytes::Bytes;
9use std::str::Utf8Error;
10
11/// UTF-8 string byte buffer representation with validation amortization.
12/// When `StrBytes` is constructed from a `&str` or `String`, its underlying bytes are assumed
13/// to be valid UTF-8. Otherwise, if constructed from a byte source, the construction will
14/// be fallible.
15///
16/// Example construction from a `&str`:
17/// ```rust
18/// use aws_smithy_types::str_bytes::StrBytes;
19///
20/// let value: StrBytes = "example".into();
21/// assert_eq!("example", value.as_str());
22/// assert_eq!(b"example", &value.as_bytes()[..]);
23/// ```
24///
25/// Example construction from `Bytes`:
26/// ```rust
27/// use bytes::Bytes;
28/// use aws_smithy_types::str_bytes::StrBytes;
29///
30/// let bytes = Bytes::from_static(b"example");
31/// let value: StrBytes = bytes.try_into().expect("valid utf-8");
32/// assert_eq!("example", value.as_str());
33/// assert_eq!(b"example", &value.as_bytes()[..]);
34/// ```
35#[non_exhaustive]
36#[derive(Clone, Debug, PartialEq, Eq)]
37pub struct StrBytes {
38    bytes: Bytes,
39}
40
41impl StrBytes {
42    fn new(bytes: Bytes) -> Self {
43        StrBytes { bytes }
44    }
45
46    /// Returns the underlying `Bytes` representation.
47    pub fn as_bytes(&self) -> &Bytes {
48        &self.bytes
49    }
50
51    /// Returns the `StrBytes` value as a `&str`.
52    pub fn as_str(&self) -> &str {
53        // Safety: StrBytes can only be constructed from a valid UTF-8 string
54        unsafe { std::str::from_utf8_unchecked(&self.bytes[..]) }
55    }
56
57    /// Tries to create a `StrBytes` from a slice, or returns a `Utf8Error` if the slice
58    /// is not valid UTF-8.
59    pub fn try_copy_from_slice(slice: &[u8]) -> Result<Self, Utf8Error> {
60        match std::str::from_utf8(slice) {
61            Ok(_) => Ok(StrBytes::new(Bytes::copy_from_slice(slice))),
62            Err(err) => Err(err),
63        }
64    }
65
66    /// Creates a `StrBytes` from a `&str`.
67    pub fn copy_from_str(string: &str) -> Self {
68        StrBytes::new(Bytes::copy_from_slice(string.as_bytes()))
69    }
70}
71
72impl From<String> for StrBytes {
73    fn from(value: String) -> Self {
74        StrBytes::new(Bytes::from(value))
75    }
76}
77
78impl From<&'static str> for StrBytes {
79    fn from(value: &'static str) -> Self {
80        StrBytes::new(Bytes::from(value))
81    }
82}
83
84impl TryFrom<&'static [u8]> for StrBytes {
85    type Error = Utf8Error;
86
87    fn try_from(value: &'static [u8]) -> Result<Self, Self::Error> {
88        match std::str::from_utf8(value) {
89            Ok(_) => Ok(StrBytes::new(Bytes::from(value))),
90            Err(err) => Err(err),
91        }
92    }
93}
94
95impl TryFrom<Vec<u8>> for StrBytes {
96    type Error = Utf8Error;
97
98    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
99        match std::str::from_utf8(&value[..]) {
100            Ok(_) => Ok(StrBytes::new(Bytes::from(value))),
101            Err(err) => Err(err),
102        }
103    }
104}
105
106impl TryFrom<Bytes> for StrBytes {
107    type Error = Utf8Error;
108
109    fn try_from(bytes: Bytes) -> Result<Self, Self::Error> {
110        match std::str::from_utf8(&bytes[..]) {
111            Ok(_) => Ok(StrBytes::new(bytes)),
112            Err(err) => Err(err),
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use crate::str_bytes::StrBytes;
120    use bytes::Bytes;
121    use std::str::Utf8Error;
122
123    #[test]
124    fn invalid_utf8_correctly_errors() {
125        let invalid_utf8 = &[0xC3, 0x28][..];
126        assert!(std::str::from_utf8(invalid_utf8).is_err());
127
128        let result: Result<StrBytes, Utf8Error> = invalid_utf8.try_into();
129        assert!(result.is_err());
130
131        let result: Result<StrBytes, Utf8Error> = invalid_utf8.to_vec().try_into();
132        assert!(result.is_err());
133
134        let result: Result<StrBytes, Utf8Error> = Bytes::from_static(invalid_utf8).try_into();
135        assert!(result.is_err());
136    }
137
138    #[test]
139    fn valid_utf8() {
140        let valid_utf8 = "hello";
141        let str_bytes: StrBytes = valid_utf8.into();
142        assert_eq!(valid_utf8.as_bytes(), str_bytes.as_bytes());
143        assert_eq!(valid_utf8, str_bytes.as_str());
144        assert_eq!(valid_utf8, str_bytes.as_str());
145    }
146
147    #[test]
148    fn equals() {
149        let str_bytes: StrBytes = "test".into();
150        assert_eq!(str_bytes, str_bytes);
151        let other_bytes: StrBytes = "foo".into();
152        assert_ne!(str_bytes, other_bytes);
153    }
154}