1#![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#[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 #[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 #[inline(always)]
38 pub fn to_str(&self) -> Option<&'a str> {
39 core::str::from_utf8(self.0).ok()
40 }
41
42 #[inline(always)]
44 pub fn from_bytes(bytes: &'a [u8]) -> Self {
45 Self(bytes)
46 }
47
48 #[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 #[inline(always)]
57 pub fn as_bytes(&self) -> &'a [u8] {
58 self.0
59 }
60
61 #[inline(always)]
63 pub fn is_empty(&self) -> bool {
64 self.0.is_empty()
65 }
66
67 #[inline(always)]
69 pub fn len(&self) -> usize {
70 self.0.len()
71 }
72
73 #[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 #[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}