raw_str/
lib.rs

1//! Defines the `RawStr` type, which represents a string that is expected to contain UTF-8 string
2//! data, but which has not yet been validated.
3//!
4#![doc = include_str!("../README.md")]
5
6#![forbid(unsafe_code)]
7#![deny(missing_docs)]
8#![cfg_attr(all(not(feature = "std"), test), no_std)]
9
10use core::fmt::{self, Debug, Display};
11#[cfg(feature = "std")]
12use std::borrow::Cow;
13
14#[cfg(test)]
15mod tests;
16
17/// A reference to a string which is believed to contain valid UTF-8 string data, but which has
18/// not yet been verified to contain UTF-8 data.
19#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Default)]
20#[repr(transparent)]
21pub struct RawStr<'a>(pub &'a [u8]);
22
23impl<'a> RawStr<'a> {
24    /// Converts this value to a string.
25    ///
26    /// If the string is valid UTF-8, then this returns a borrowed `&str` reference. If the string
27    /// does invalid UTF-8 sequences, then this will allocate a new `String` and convert the
28    /// character data to UTF-8, replacing invalid sequences with the Unicode replacement character.
29    #[cfg(feature = "std")]
30    #[inline(always)]
31    pub fn to_str_lossy(&self) -> Cow<'a, str> {
32        String::from_utf8_lossy(self.0)
33    }
34
35    /// Validates that the string contains valid UTF-8, and returns `Some(s)` if so.
36    /// If the string contains invalid UTF-8 sequences, this returns `None`.
37    #[inline(always)]
38    pub fn to_str(&self) -> Option<&'a str> {
39        core::str::from_utf8(self.0).ok()
40    }
41
42    /// Wraps a byte array as `RawStr`.
43    #[inline(always)]
44    pub fn from_bytes(bytes: &'a [u8]) -> Self {
45        Self(bytes)
46    }
47
48    /// Wraps a string slice as `RawStr`.
49    #[inline(always)]
50    #[allow(clippy::should_implement_trait)]
51    pub fn from_str(s: &'a str) -> Self {
52        Self(s.as_bytes())
53    }
54
55    /// Gets the raw bytes
56    #[inline(always)]
57    pub fn as_bytes(&self) -> &'a [u8] {
58        self.0
59    }
60
61    /// Tests if the string is empty
62    #[inline(always)]
63    pub fn is_empty(&self) -> bool {
64        self.0.is_empty()
65    }
66
67    /// Length of the string in bytes
68    #[inline(always)]
69    pub fn len(&self) -> usize {
70        self.0.len()
71    }
72
73    /// Equivalent to `str::eq_ignore_ascii_case`
74    #[inline(always)]
75    pub fn eq_ignore_ascii_case(&self, other: RawStr<'_>) -> bool {
76        self.0.eq_ignore_ascii_case(other.0)
77    }
78
79    /// Equivalent to `str::ends_with`
80    #[inline(always)]
81    pub fn ends_with(&self, other: RawStr<'_>) -> bool {
82        let other_bytes: &[u8] = other.0;
83        if other_bytes.len() <= self.0.len() {
84            self.0[self.0.len() - other_bytes.len()..self.0.len()] == *other_bytes
85        } else {
86            false
87        }
88    }
89}
90
91impl<'a> PartialEq<str> for RawStr<'a> {
92    #[inline(always)]
93    fn eq(&self, other: &str) -> bool {
94        self.0 == other.as_bytes()
95    }
96}
97
98impl<'a> PartialEq<&str> for RawStr<'a> {
99    #[inline(always)]
100    fn eq(&self, other: &&str) -> bool {
101        self.0 == other.as_bytes()
102    }
103}
104
105impl<'a> AsRef<[u8]> for RawStr<'a> {
106    #[inline(always)]
107    fn as_ref(&self) -> &[u8] {
108        self.0
109    }
110}
111
112impl<'a> Debug for RawStr<'a> {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        #[cfg(feature = "std")]
115        {
116            let s = self.to_str_lossy();
117            Debug::fmt(&*s, f)
118        }
119
120        #[cfg(not(feature = "std"))]
121        {
122            if let Ok(s) = core::str::from_utf8(self.0) {
123                Debug::fmt(s, f)
124            } else {
125                write!(f, "{:x?}", self.0)
126            }
127        }
128    }
129}
130
131impl<'a> Display for RawStr<'a> {
132    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
133        #[cfg(feature = "std")]
134        {
135            let s = self.to_str_lossy();
136            Display::fmt(&*s, f)
137        }
138
139        #[cfg(not(feature = "std"))]
140        {
141            if let Ok(s) = core::str::from_utf8(self.0) {
142                Display::fmt(s, f)
143            } else {
144                write!(f, "{:x?}", self.0)
145            }
146        }
147    }
148}
149
150impl<'a, S: 'a + AsRef<str> + ?Sized> From<&'a S> for RawStr<'a> {
151    #[inline(always)]
152    fn from(s: &'a S) -> Self {
153        Self(s.as_ref().as_bytes())
154    }
155}