|
|
|
@ -11,6 +11,49 @@ pub enum CowCappedString<'a, const N: usize> {
|
|
|
|
Owned(CappedString<N>),
|
|
|
|
Owned(CappedString<N>),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a, const N: usize> CowCappedString<'a, N> {
|
|
|
|
|
|
|
|
/// Returns the string data contained by this `CowCappedString`.
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
pub fn as_str(&self) -> &str {
|
|
|
|
|
|
|
|
match self {
|
|
|
|
|
|
|
|
CowCappedString::Borrowed(s) => s,
|
|
|
|
|
|
|
|
CowCappedString::Owned(s) => s,
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a new `CappedString` with capacity `M` containing the string converted to
|
|
|
|
|
|
|
|
/// uppercase. Returns `None` if the uppercase-converted string is longer than `M` bytes.
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
pub fn to_uppercase<const M: usize>(&self) -> Option<CappedString<M>> {
|
|
|
|
|
|
|
|
CappedString::<M>::uppercase_from_str(self)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a, const N: usize> Deref for CowCappedString<'a, N> {
|
|
|
|
|
|
|
|
type Target = str;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
|
|
|
self.as_str()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a, const N: usize> AsRef<str> for CowCappedString<'a, N> {
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
fn as_ref(&self) -> &str {
|
|
|
|
|
|
|
|
self
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'a, const N: usize> Borrow<str> for CowCappedString<'a, N> {
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
fn borrow(&self) -> &str {
|
|
|
|
|
|
|
|
self
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for CowCappedString<'de, N> {
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for CowCappedString<'de, N> {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
@ -74,16 +117,45 @@ pub struct CappedString<const N: usize> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl<const N: usize> CappedString<N> {
|
|
|
|
impl<const N: usize> CappedString<N> {
|
|
|
|
/// Returns a new `CappedString` containing a copy of the given string data. Returns an error
|
|
|
|
/// Returns a new `CappedString` containing a copy of the given string data. Returns `None` if
|
|
|
|
/// if the string data is larger than `N` bytes.
|
|
|
|
/// the string data is larger than `N` bytes.
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
#[must_use]
|
|
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
|
|
pub fn from_str(s: &str) -> Option<Self> {
|
|
|
|
unsafe { Self::from_utf8_unchecked(s.as_bytes()) }
|
|
|
|
unsafe { Self::from_utf8_unchecked(s.as_bytes()) }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a new `CappedString` containing an uppercase conversion of the given string data.
|
|
|
|
|
|
|
|
/// Returns `None` if the converted string is larger than `N` bytes.
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
|
|
|
|
#[must_use]
|
|
|
|
|
|
|
|
pub fn uppercase_from_str(s: &str) -> Option<Self> {
|
|
|
|
|
|
|
|
let mut buf = [0u8; N];
|
|
|
|
|
|
|
|
let mut cursor = 0usize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for c_orig in s.chars() {
|
|
|
|
|
|
|
|
for c_upper in c_orig.to_uppercase() {
|
|
|
|
|
|
|
|
let encode_buf = cursor
|
|
|
|
|
|
|
|
.checked_add(c_upper.len_utf8())
|
|
|
|
|
|
|
|
.and_then(|encode_buf_end| buf.get_mut(cursor..encode_buf_end))?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME: avoid the panic asm that gets generated for this encode (can never panic,
|
|
|
|
|
|
|
|
// as we always have at least `c_upper.len_utf8()` buffer space).
|
|
|
|
|
|
|
|
let encoded = c_upper.encode_utf8(encode_buf);
|
|
|
|
|
|
|
|
cursor = cursor.checked_add(encoded.len())?;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let filled_buf = buf.get(..cursor)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SAFETY:
|
|
|
|
|
|
|
|
// `filled_buf` has been filled with a sequence of bytes obtained from `char::encode_utf8`,
|
|
|
|
|
|
|
|
// so it is valid UTF-8.
|
|
|
|
|
|
|
|
unsafe { Self::from_utf8_unchecked(filled_buf) }
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a new `CappedString` containing a copy of the given UTF-8 encoded string data.
|
|
|
|
/// Returns a new `CappedString` containing a copy of the given UTF-8 encoded string data.
|
|
|
|
/// Returns an error if more than `N` bytes of data are given.
|
|
|
|
/// Returns `None` if more than `N` bytes of data are given.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
/// # Safety
|
|
|
|
/// # Safety
|
|
|
|
/// - `bs` must be valid UTF-8.
|
|
|
|
/// - `bs` must be valid UTF-8.
|
|
|
|
@ -134,32 +206,11 @@ impl<const N: usize> CappedString<N> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns a new `CappedString` with capacity `M` containing the string converted to
|
|
|
|
/// Returns a new `CappedString` with capacity `M` containing the string converted to
|
|
|
|
/// uppercase. Returns an error if the uppercase-converted string is longer than `M` bytes.
|
|
|
|
/// uppercase. Returns `None` if the uppercase-converted string is longer than `M` bytes.
|
|
|
|
#[inline]
|
|
|
|
#[inline]
|
|
|
|
#[must_use]
|
|
|
|
#[must_use]
|
|
|
|
pub fn to_uppercase<const M: usize>(&self) -> Option<CappedString<M>> {
|
|
|
|
pub fn to_uppercase<const M: usize>(&self) -> Option<CappedString<M>> {
|
|
|
|
let mut buf = [0u8; M];
|
|
|
|
CappedString::<M>::uppercase_from_str(self)
|
|
|
|
let mut cursor = 0usize;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for c_orig in self.as_str().chars() {
|
|
|
|
|
|
|
|
for c_upper in c_orig.to_uppercase() {
|
|
|
|
|
|
|
|
let encode_buf = cursor
|
|
|
|
|
|
|
|
.checked_add(c_upper.len_utf8())
|
|
|
|
|
|
|
|
.and_then(|encode_buf_end| buf.get_mut(cursor..encode_buf_end))?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// FIXME: avoid the panic asm that gets generated for this encode (can never panic,
|
|
|
|
|
|
|
|
// as we always have at least `c_upper.len_utf8()` buffer space).
|
|
|
|
|
|
|
|
let encoded = c_upper.encode_utf8(encode_buf);
|
|
|
|
|
|
|
|
cursor = cursor.checked_add(encoded.len())?;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let filled_buf = buf.get(..cursor)?;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SAFETY:
|
|
|
|
|
|
|
|
// `filled_buf` has been filled with a sequence of bytes obtained from `char::encode_utf8`,
|
|
|
|
|
|
|
|
// so it is valid UTF-8.
|
|
|
|
|
|
|
|
unsafe { CappedString::from_utf8_unchecked(filled_buf) }
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -186,6 +237,20 @@ impl<const N: usize> Borrow<str> for CappedString<N> {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<const N: usize> PartialEq for CappedString<N> {
|
|
|
|
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
|
|
|
|
|
|
|
self.as_str() == other.as_str()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<const N: usize> Eq for CappedString<N> {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<const N: usize> PartialEq<str> for CappedString<N> {
|
|
|
|
|
|
|
|
fn eq(&self, other: &str) -> bool {
|
|
|
|
|
|
|
|
self.as_str() == other
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for CappedString<N> {
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for CappedString<N> {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
@ -227,7 +292,115 @@ impl<'de, const N: usize> serde::de::Visitor<'de> for CappedStringVisitor<N> {
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
mod tests {
|
|
|
|
use super::CappedString;
|
|
|
|
use super::{CappedString, CowCappedString};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_cow_capped_string_deserialize() {
|
|
|
|
|
|
|
|
struct DeBorrowedOnly<const N: usize>(String);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for DeBorrowedOnly<N> {
|
|
|
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
|
|
|
where
|
|
|
|
|
|
|
|
D: serde::Deserializer<'de>
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
match CowCappedString::<'de, N>::deserialize(deserializer)? {
|
|
|
|
|
|
|
|
CowCappedString::Borrowed(s) => Ok(Self(s.to_owned())),
|
|
|
|
|
|
|
|
CowCappedString::Owned(_) => {
|
|
|
|
|
|
|
|
Err(serde::de::Error::custom("expected borrowed CowCappedString"))
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
struct DeOwnedOnly<const N: usize>(String);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
impl<'de, const N: usize> serde::Deserialize<'de> for DeOwnedOnly<N> {
|
|
|
|
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
|
|
|
|
|
|
where
|
|
|
|
|
|
|
|
D: serde::Deserializer<'de>
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
match CowCappedString::<'de, N>::deserialize(deserializer)? {
|
|
|
|
|
|
|
|
CowCappedString::Borrowed(_) => {
|
|
|
|
|
|
|
|
Err(serde::de::Error::custom("expected owned CowCappedString"))
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
CowCappedString::Owned(s) => Ok(Self(s.to_owned())),
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let DeBorrowedOnly(s) = serde_json::from_str::<DeBorrowedOnly<5>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s, "hello");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let DeBorrowedOnly(s) = serde_json::from_str::<DeBorrowedOnly<0>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s, "hello");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<DeOwnedOnly<5>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(s.is_err());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let DeOwnedOnly(s) = serde_json::from_str::<DeOwnedOnly<3>>(
|
|
|
|
|
|
|
|
r#""\u87f9""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s, "蟹");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<DeBorrowedOnly<3>>(
|
|
|
|
|
|
|
|
r#""\u87f9""#
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(s.is_err());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "serde")]
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
|
|
fn test_capped_string_deserialize() {
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<5>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s.as_str(), "hello");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<4>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(s.is_err());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<10>>(
|
|
|
|
|
|
|
|
r#""hello""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s.as_str(), "hello");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<12>>(
|
|
|
|
|
|
|
|
r#""hello\tworld\n""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s.as_str(), "hello\tworld\n");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<3>>(
|
|
|
|
|
|
|
|
r#""\u87f9""#
|
|
|
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
assert_eq!(s.as_str(), "蟹");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
let s = serde_json::from_str::<CappedString<2>>(
|
|
|
|
|
|
|
|
r#""\u87f9""#
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
assert!(s.is_err());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[test]
|
|
|
|
fn test_capped_string_uppercase() {
|
|
|
|
fn test_capped_string_uppercase() {
|
|
|
|
@ -247,8 +420,8 @@ mod tests {
|
|
|
|
assert_eq!(s2.as_str(), "HELLO");
|
|
|
|
assert_eq!(s2.as_str(), "HELLO");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let s1 = CappedString::<5>::from_str("hello").unwrap();
|
|
|
|
let s = CappedString::<5>::from_str("hello").unwrap();
|
|
|
|
assert!(s1.to_uppercase::<4>().is_none());
|
|
|
|
assert!(s.to_uppercase::<4>().is_none());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
|
|
{
|
|
|
|
let s1 = CappedString::<5>::from_str("groß").unwrap();
|
|
|
|
let s1 = CappedString::<5>::from_str("groß").unwrap();
|
|
|
|
|