Introduce 'RawStrBuf', use in 'Origin::map_path()'.

'RawStrBuf' is the owned analog of 'RawStr'. Thus, 'Cow<RawStr>' is
either 'RawStr' or 'RawStrBuf'.
This commit is contained in:
Sergio Benitez 2021-04-07 19:29:21 -07:00
parent c0564fa8dc
commit e92b2adeaa
3 changed files with 157 additions and 10 deletions

View File

@ -53,5 +53,5 @@ pub mod private {
pub use crate::method::Method;
pub use crate::status::{Status, StatusClass};
pub use crate::raw_str::RawStr;
pub use crate::raw_str::{RawStr, RawStrBuf};
pub use crate::header::*;

View File

@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::borrow::{Borrow, Cow};
use std::convert::AsRef;
use std::cmp::Ordering;
use std::str::Utf8Error;
@ -50,6 +50,26 @@ use crate::uncased::UncasedStr;
#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStr(str);
impl ToOwned for RawStr {
type Owned = RawStrBuf;
fn to_owned(&self) -> Self::Owned {
RawStrBuf(self.to_string())
}
}
/// An owned version of [`RawStr`].
#[repr(transparent)]
#[derive(RefCast, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RawStrBuf(String);
impl RawStrBuf {
/// Cost-free conversion from `self` into a `String`.
pub fn into_string(self) -> String {
self.0
}
}
impl RawStr {
/// Constructs an `&RawStr` from a string-like type at no cost.
///
@ -68,6 +88,46 @@ impl RawStr {
RawStr::ref_cast(string.as_ref())
}
/// Constructs an `&RawStr` from a string-like type at no cost.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("Hello, world!");
///
/// // `into` can also be used; note that the type must be specified
/// let raw_str: &RawStr = "Hello, world!".into();
/// ```
pub fn from_cow_str<'a>(cow: Cow<'a, str>) -> Cow<'a, RawStr> {
match cow {
Cow::Borrowed(b) => Cow::Borrowed(b.into()),
Cow::Owned(b) => Cow::Owned(b.into()),
}
}
/// Constructs an `&RawStr` from a string-like type at no cost.
///
/// # Example
///
/// ```rust
/// # extern crate rocket;
/// use rocket::http::RawStr;
///
/// let raw_str = RawStr::new("Hello, world!");
///
/// // `into` can also be used; note that the type must be specified
/// let raw_str: &RawStr = "Hello, world!".into();
/// ```
pub fn into_cow_str<'a>(cow: Cow<'a, RawStr>) -> Cow<'a, str> {
match cow {
Cow::Borrowed(b) => Cow::Borrowed(b.as_str()),
Cow::Owned(b) => Cow::Owned(b.into_string()),
}
}
/// Performs percent decoding.
fn _percent_decode(&self) -> percent_encoding::PercentDecode<'_> {
percent_encoding::percent_decode(self.as_bytes())
@ -822,6 +882,64 @@ impl fmt::Display for RawStr {
}
}
impl AsRef<RawStr> for RawStrBuf {
#[inline(always)]
fn as_ref(&self) -> &RawStr {
RawStr::new(self.0.as_str())
}
}
impl Borrow<RawStr> for RawStrBuf {
#[inline(always)]
fn borrow(&self) -> &RawStr {
self.as_ref()
}
}
impl std::ops::Deref for RawStrBuf {
type Target = RawStr;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl fmt::Display for RawStrBuf {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Debug for RawStrBuf {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl From<String> for RawStrBuf {
#[inline(always)]
fn from(string: String) -> Self {
RawStrBuf(string)
}
}
impl From<&str> for RawStrBuf {
#[inline(always)]
fn from(string: &str) -> Self {
string.to_string().into()
}
}
impl From<&RawStr> for RawStrBuf {
#[inline(always)]
fn from(raw: &RawStr) -> Self {
raw.to_string().into()
}
}
#[cfg(test)]
mod tests {
use super::RawStr;

View File

@ -3,10 +3,10 @@ use std::convert::TryFrom;
use std::fmt::{self, Display};
use crate::ext::IntoOwned;
use crate::parse::{Indexed, Extent, IndexedStr};
use crate::parse::{Indexed, Extent, IndexedStr, uri::tables::is_pchar};
use crate::uri::{self, UriPart, Query, Path};
use crate::uri::{Error, Segments, QuerySegments, as_utf8_unchecked};
use crate::RawStr;
use crate::{RawStr, RawStrBuf};
use state::Storage;
@ -104,6 +104,19 @@ impl<'a, 'b> PartialEq<Origin<'b>> for Origin<'a> {
}
}
impl PartialEq<str> for Origin<'_> {
fn eq(&self, other: &str) -> bool {
let (path, query) = RawStr::new(other).split_at_byte(b'?');
self.path() == path && self.query().unwrap_or("".into()) == query
}
}
impl PartialEq<Origin<'_>> for str {
fn eq(&self, other: &Origin<'_>) -> bool {
other.eq(self)
}
}
impl IntoOwned for Origin<'_> {
type Owned = Origin<'static>;
@ -407,20 +420,27 @@ impl<'a> Origin<'a> {
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
///
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
/// let expected = Origin::parse("/b/c/").unwrap();
/// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), Some(expected));
///
/// let old_uri = Origin::parse("/a").unwrap();
/// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None);
///
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
/// assert_eq!(old_uri.map_path(|p| format!("hi/{}", p)), None);
/// ```
#[inline]
pub fn map_path<F: FnOnce(&RawStr) -> String>(&self, f: F) -> Option<Self> {
let path = f(self.path());
if !path.starts_with('/')
|| !path.bytes().all(|b| crate::parse::uri::tables::is_pchar(&b))
{
pub fn map_path<'s, F, P>(&'s self, f: F) -> Option<Self>
where F: FnOnce(&'s RawStr) -> P, P: Into<RawStrBuf> + 's
{
let path = f(self.path()).into();
if !path.starts_with('/') || !path.as_bytes().iter().all(|b| is_pchar(&b)) {
return None;
}
Some(Origin {
source: self.source.clone(),
path: Cow::from(path).into(),
path: Cow::from(path.into_string()).into(),
query: self.query.clone(),
decoded_path_segs: Storage::new(),
decoded_query_segs: Storage::new(),
@ -617,6 +637,15 @@ impl TryFrom<String> for Origin<'static> {
}
}
// Because inference doesn't take `&String` to `&str`.
impl<'a> TryFrom<&'a String> for Origin<'a> {
type Error = Error<'a>;
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
Origin::parse(value.as_str())
}
}
impl<'a> TryFrom<&'a str> for Origin<'a> {
type Error = Error<'a>;