cowstr 0.11.0

Copy-on-Write shared strings
Documentation
use std::ops::Deref;

use crate::*;

/// Arbitrary immutable text as span from some shared string
#[derive(Debug, Clone)]
pub struct SubStr {
    pub(crate) string: CowStr,
    // Safety: always holds a valid range, the `CowStr` is immutable here.
    pub(crate) substr: *const str,
}

/// Tried to use a range larger than the string it should cover.
#[derive(Debug)]
pub struct RangeError;

impl SubStr {
    /// Create a `SubStr` that spans a complete `CowStr`.
    #[must_use]
    pub fn new(from: &CowStr) -> SubStr {
        let substr = &from[..] as *const str;
        SubStr {
            string: from.clone(),
            substr,
        }
    }

    /// Get a reference to the original `CowStr` from which this `SubStr` is constructed.
    #[inline]
    #[must_use]
    pub fn origin(&self) -> &CowStr {
        &self.string
    }

    #[inline]
    unsafe fn from_contained_str_unchecked(from: &CowStr, substr: &str) -> SubStr {
        SubStr {
            string: from.clone(),
            substr,
        }
    }

    /// Create and `SubStr` from a `&str` that must be contained an initial `CowStr`.  This is useful
    /// when parsing or splitting a &str that is obtained from a `CowStr`.
    ///
    /// # Panics
    ///
    /// `substr` is not within `from`
    #[inline]
    #[must_use]
    pub fn from_contained_str(from: &CowStr, substr: &str) -> SubStr {
        // We need to check only for the first character, there is no way one can safely
        // construct a &str that spans past the end of a original CowStr
        assert!(from.as_bytes().as_ptr_range().contains(&substr.as_ptr()));
        unsafe { Self::from_contained_str_unchecked(from, substr) }
    }

    /// Create and `SubStr` as span of a `CowStr`.
    pub fn new_range(from: &CowStr, start: usize, end: usize) -> Result<SubStr, RangeError> {
        if let Some(substr) = from.get(start..end) {
            let substr = substr as *const str;
            Ok(SubStr {
                string: from.clone(),
                substr,
            })
        } else {
            Err(RangeError)
        }
    }

    /// Create a `SubStr` as range from an existing `SubStr`.
    pub fn sub_range(&self, start: usize, end: usize) -> Result<SubStr, RangeError> {
        if let Some(substr) = unsafe { &*self.substr }.get(start..end) {
            Ok(SubStr {
                string: self.string.clone(),
                substr,
            })
        } else {
            Err(RangeError)
        }
    }

    /// Create a `SubStr` that spans a complete `'static str`,
    #[must_use]
    #[inline]
    pub fn from_static(from: &'static str) -> SubStr {
        CowStr::from_static(from).into_substr()
    }

    /// Returns `Ok(&'static str)` when the underlying string has static lifetime and
    /// `Err(&'self str)` when it is dynamically allocated.
    #[inline]
    pub fn deref_static(&self) -> Result<&'static str, &str> {
        match self.string.deref_static() {
            Ok(_) => Ok(unsafe { &*self.substr }),
            Err(_) => Err(unsafe { &*self.substr }),
        }
    }
}

impl Deref for SubStr {
    type Target = str;

    #[inline]
    fn deref(&self) -> &Self::Target {
        unsafe { &*self.substr }
    }
}

impl From<CowStr> for SubStr {
    #[inline]
    fn from(cow: CowStr) -> Self {
        cow.into_substr()
    }
}

impl From<&CowStr> for SubStr {
    #[inline]
    fn from(cow: &CowStr) -> Self {
        SubStr::new(cow)
    }
}

#[test]
fn from() {
    let origin = CowStr::from("test string");
    let substr = SubStr::from(origin);
    assert_eq!(&*substr, "test string");
}

#[test]
fn from_contained_str() {
    let origin = CowStr::from("test string");
    let (left, right) = origin.as_str().split_once(' ').unwrap();
    let left = SubStr::from_contained_str(&origin, left);
    let right = SubStr::from_contained_str(&origin, right);
    assert_eq!(&*left, "test");
    assert_eq!(&*right, "string");
}

#[test]
fn from_ref() {
    let origin = CowStr::from("test string");
    let substr = SubStr::from(&origin);
    assert_eq!(&*substr, "test string");
}

#[test]
fn new_range() {
    let origin = CowStr::from("test string");
    let substr = SubStr::new_range(&origin, 0, 4).unwrap();
    assert_eq!(&*substr, "test");

    let subsubstr = substr.sub_range(1, 3).unwrap();
    assert_eq!(&*subsubstr, "es");
}

#[test]
#[should_panic]
fn from_panic() {
    let origin = CowStr::from("test string");
    let _substr = SubStr::new_range(&origin, 10, 100).unwrap();
}

#[test]
fn deref_static_static() {
    let static_origin = CowStr::from_static("test string");
    let static_substr = SubStr::from(&static_origin);
    assert_eq!(static_substr.deref_static(), Ok("test string"));
}

#[test]
fn deref_static_dynamic() {
    let dynamic_origin = CowStr::from("test string");
    let dynamic_substr = SubStr::from(&dynamic_origin);
    assert_eq!(dynamic_substr.deref_static(), Err("test string"));
}