|
@@ -1,21 +1,24 @@
|
|
|
|
|
+use std::{collections::HashMap, fmt::Display};
|
|
|
|
|
+
|
|
|
use nom::{
|
|
use nom::{
|
|
|
- bytes::complete::{tag, take_until, take_until1, take_while, take_while1},
|
|
|
|
|
|
|
+ bytes::complete::{tag, take_while, take_while1},
|
|
|
character::complete::char,
|
|
character::complete::char,
|
|
|
multi::many0,
|
|
multi::many0,
|
|
|
sequence::{preceded, separated_pair},
|
|
sequence::{preceded, separated_pair},
|
|
|
Parser,
|
|
Parser,
|
|
|
};
|
|
};
|
|
|
-use serde::Serialize;
|
|
|
|
|
|
|
+use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
-#[derive(Debug, Serialize)]
|
|
|
|
|
|
|
+#[derive(Debug, Serialize, Deserialize)]
|
|
|
pub struct RequestUrlOwned {
|
|
pub struct RequestUrlOwned {
|
|
|
pub scheme: String,
|
|
pub scheme: String,
|
|
|
pub host: String,
|
|
pub host: String,
|
|
|
pub path: Vec<SegmentOwned>,
|
|
pub path: Vec<SegmentOwned>,
|
|
|
pub query_params: Vec<(String, String)>,
|
|
pub query_params: Vec<(String, String)>,
|
|
|
|
|
+ pub has_query: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-#[derive(Debug, Serialize)]
|
|
|
|
|
|
|
+#[derive(Debug, Serialize, Deserialize)]
|
|
|
#[serde(tag = "type", content = "value")]
|
|
#[serde(tag = "type", content = "value")]
|
|
|
pub enum SegmentOwned {
|
|
pub enum SegmentOwned {
|
|
|
Static(String),
|
|
Static(String),
|
|
@@ -40,117 +43,132 @@ pub struct RequestUrl<'a> {
|
|
|
|
|
|
|
|
/// Query parameters.
|
|
/// Query parameters.
|
|
|
pub query_params: Vec<(&'a str, &'a str)>,
|
|
pub query_params: Vec<(&'a str, &'a str)>,
|
|
|
|
|
+
|
|
|
|
|
+ /// Whether or not a '?' was found during parsing. If true, indicates a
|
|
|
|
|
+ /// '?' must be set when reconstructing.
|
|
|
|
|
+ pub trailing_query: bool,
|
|
|
|
|
+
|
|
|
|
|
+ /// Whether or not a trailing '&' was found during parsing. If true, indicates a
|
|
|
|
|
+ /// '&' must be set when reconstructing.
|
|
|
|
|
+ pub trailing_query_pair: bool,
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-impl<'a> RequestUrl<'a> {
|
|
|
|
|
- pub fn parse(input: &'a str) -> Result<Self, nom::Err<nom::error::Error<&'a str>>> {
|
|
|
|
|
- let (input, scheme) = take_while1(char::is_alphabetic)(input)?;
|
|
|
|
|
|
|
+type NomError<'a> = nom::Err<(&'a str, nom::error::ErrorKind)>;
|
|
|
|
|
|
|
|
- let (input, _) = tag("://")(input)?;
|
|
|
|
|
|
|
+impl<'a> RequestUrl<'a> {
|
|
|
|
|
+ pub fn parse(input: &'a str) -> Result<Self, UrlParseError> {
|
|
|
|
|
+ fn parse_query(query: &str) -> Result<(Vec<(&str, &str)>, bool), NomError<'_>> {
|
|
|
|
|
+ if query.is_empty() {
|
|
|
|
|
+ return Ok((vec![], false));
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- let mut path_parser = many0(preceded(char('/'), take_while(|c: char| c != '/')));
|
|
|
|
|
|
|
+ // Parse query
|
|
|
|
|
+ // First char will always be a '?' since we parsed succesfully
|
|
|
|
|
+ let mut query = &query[1..];
|
|
|
|
|
+ let mut query_params = vec![];
|
|
|
|
|
|
|
|
- let mut segment_parser =
|
|
|
|
|
- preceded(tag::<_, _, nom::error::Error<_>>(":"), take_while(|_| true));
|
|
|
|
|
|
|
+ let mut trailing_pair = false;
|
|
|
|
|
|
|
|
- match take_until1::<_, _, nom::error::Error<_>>("?")(input) {
|
|
|
|
|
- // URL has query parameters
|
|
|
|
|
- Ok((query, path)) => {
|
|
|
|
|
- // Parse query
|
|
|
|
|
- // First char will always be a '?' since we parsed succesfully
|
|
|
|
|
- let mut query = &query[1..];
|
|
|
|
|
- let mut query_params = vec![];
|
|
|
|
|
|
|
+ loop {
|
|
|
|
|
+ if query.is_empty() {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- loop {
|
|
|
|
|
- if query.is_empty() {
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ let (i, params) = separated_pair(
|
|
|
|
|
+ take_while(|c: char| c != '='),
|
|
|
|
|
+ char('='),
|
|
|
|
|
+ take_while(|c: char| c != '&'),
|
|
|
|
|
+ )
|
|
|
|
|
+ .parse(query)?;
|
|
|
|
|
|
|
|
- let (i, params) = separated_pair(
|
|
|
|
|
- take_while(|c: char| c != '='),
|
|
|
|
|
- char('='),
|
|
|
|
|
- take_while(|c: char| c != '&'),
|
|
|
|
|
- )
|
|
|
|
|
- .parse(query)?;
|
|
|
|
|
|
|
+ query = i;
|
|
|
|
|
+ query_params.push((params.0, params.1));
|
|
|
|
|
|
|
|
|
|
+ if let Ok((i, _)) = char::<_, nom::error::Error<_>>('&').parse(query) {
|
|
|
|
|
+ trailing_pair = i.is_empty();
|
|
|
query = i;
|
|
query = i;
|
|
|
- query_params.push((params.0, params.1));
|
|
|
|
|
-
|
|
|
|
|
- if let Ok((i, _)) = char::<_, nom::error::Error<_>>('&').parse(query) {
|
|
|
|
|
- query = i;
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- debug_assert!(query.is_empty());
|
|
|
|
|
-
|
|
|
|
|
- // Check path segments
|
|
|
|
|
-
|
|
|
|
|
- match take_until::<_, _, nom::error::Error<_>>("/")(path) {
|
|
|
|
|
- // Path exists
|
|
|
|
|
- Ok((path, host)) => {
|
|
|
|
|
- let (input, segments) = path_parser.parse(path)?;
|
|
|
|
|
- debug_assert!(input.is_empty());
|
|
|
|
|
- Ok(RequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host,
|
|
|
|
|
- path: segments
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|segment| {
|
|
|
|
|
- segment_parser.parse(segment).ok().map_or(
|
|
|
|
|
- Segment::Static(segment),
|
|
|
|
|
- |(r, s)| {
|
|
|
|
|
- debug_assert_eq!("", r);
|
|
|
|
|
- Segment::Dynamic(s)
|
|
|
|
|
- },
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
- .collect(),
|
|
|
|
|
- query_params,
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ debug_assert!(query.is_empty());
|
|
|
|
|
|
|
|
- // No path segments
|
|
|
|
|
- Err(_) => Ok(RequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host: path,
|
|
|
|
|
- path: vec![],
|
|
|
|
|
- query_params,
|
|
|
|
|
- }),
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- // No query params
|
|
|
|
|
- Err(_) => {
|
|
|
|
|
- match take_until::<_, _, nom::error::Error<_>>("/")(input) {
|
|
|
|
|
- // Path exists
|
|
|
|
|
- Ok((path, host)) => {
|
|
|
|
|
- let (input, segments) = path_parser.parse(path)?;
|
|
|
|
|
- debug_assert!(input.is_empty());
|
|
|
|
|
- Ok(RequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host,
|
|
|
|
|
- path: segments
|
|
|
|
|
- .into_iter()
|
|
|
|
|
- .map(|segment| {
|
|
|
|
|
- segment_parser.parse(segment).ok().map_or(
|
|
|
|
|
- Segment::Static(segment),
|
|
|
|
|
- |(r, s)| {
|
|
|
|
|
- debug_assert!(r.is_empty());
|
|
|
|
|
- Segment::Dynamic(s)
|
|
|
|
|
- },
|
|
|
|
|
- )
|
|
|
|
|
- })
|
|
|
|
|
- .collect(),
|
|
|
|
|
- query_params: vec![],
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- // No path segments
|
|
|
|
|
- Err(_) => Ok(RequestUrl {
|
|
|
|
|
- scheme,
|
|
|
|
|
- host: input,
|
|
|
|
|
- path: vec![],
|
|
|
|
|
- query_params: vec![],
|
|
|
|
|
- }),
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ Ok((query_params, trailing_pair))
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ let (input, scheme) = match take_while1(char::is_alphabetic)(input) {
|
|
|
|
|
+ Ok((i, s)) => (i, s),
|
|
|
|
|
+ Err(e) => return Err(map_nom_err(input, None, e)),
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ let (input, _) = tag("://")(input).map_err(|e| map_nom_err(input, Some("://"), e))?;
|
|
|
|
|
+
|
|
|
|
|
+ // Parse until first /
|
|
|
|
|
+
|
|
|
|
|
+ let (path, host) =
|
|
|
|
|
+ take_while(|c| c != '/')(input).map_err(|e| map_nom_err(input, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ // We've fully parsed the string, no path
|
|
|
|
|
+
|
|
|
|
|
+ if path.is_empty() {
|
|
|
|
|
+ let (query, host) =
|
|
|
|
|
+ take_while(|c| c != '?')(host).map_err(|e| map_nom_err(host, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ let (query_params, trailing) =
|
|
|
|
|
+ parse_query(query).map_err(|e| map_nom_err(query, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ return Ok(RequestUrl {
|
|
|
|
|
+ scheme,
|
|
|
|
|
+ host,
|
|
|
|
|
+ path: vec![],
|
|
|
|
|
+ query_params,
|
|
|
|
|
+ trailing_query: query == "?",
|
|
|
|
|
+ trailing_query_pair: trailing,
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Parse until query
|
|
|
|
|
+
|
|
|
|
|
+ let (query, path) =
|
|
|
|
|
+ take_while(|c| c != '?')(path).map_err(|e| map_nom_err(path, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ let (query_params, trailing) =
|
|
|
|
|
+ parse_query(query).map_err(|e| map_nom_err(query, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ let (remainder, segments) = many0(preceded(char('/'), take_while(|c| c != '/')))
|
|
|
|
|
+ .parse(path)
|
|
|
|
|
+ .map_err(|e| map_nom_err(path, None, e))?;
|
|
|
|
|
+
|
|
|
|
|
+ debug_assert!(remainder.is_empty());
|
|
|
|
|
+
|
|
|
|
|
+ Ok(RequestUrl {
|
|
|
|
|
+ scheme,
|
|
|
|
|
+ host,
|
|
|
|
|
+ path: segments
|
|
|
|
|
+ .into_iter()
|
|
|
|
|
+ .map(|segment| {
|
|
|
|
|
+ preceded(
|
|
|
|
|
+ tag::<_, _, nom::error::Error<_>>(":"),
|
|
|
|
|
+ nom::combinator::rest,
|
|
|
|
|
+ )
|
|
|
|
|
+ .parse(segment)
|
|
|
|
|
+ .ok()
|
|
|
|
|
+ .map_or(Segment::Static(segment), |(r, s)| {
|
|
|
|
|
+ debug_assert_eq!("", r);
|
|
|
|
|
+ Segment::Dynamic(s)
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+ .collect(),
|
|
|
|
|
+ query_params,
|
|
|
|
|
+ trailing_query: query == "?",
|
|
|
|
|
+ trailing_query_pair: trailing,
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ pub fn populate(&mut self, path_params: HashMap<&'a str, &'a str>) {
|
|
|
|
|
+ for path in self.path.iter_mut() {
|
|
|
|
|
+ if let Segment::Dynamic(value) = path {
|
|
|
|
|
+ let val = path_params.get(value).unwrap_or(&"");
|
|
|
|
|
+ std::mem::swap(path, &mut Segment::Static(val));
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -167,10 +185,59 @@ impl<'a> From<RequestUrl<'a>> for RequestUrlOwned {
|
|
|
.into_iter()
|
|
.into_iter()
|
|
|
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
|
.map(|(k, v)| (k.to_owned(), v.to_owned()))
|
|
|
.collect(),
|
|
.collect(),
|
|
|
|
|
+ has_query: value.trailing_query,
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+impl<'a> Display for RequestUrl<'a> {
|
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
+ let RequestUrl {
|
|
|
|
|
+ scheme,
|
|
|
|
|
+ host,
|
|
|
|
|
+ path,
|
|
|
|
|
+ query_params,
|
|
|
|
|
+ trailing_query,
|
|
|
|
|
+ trailing_query_pair,
|
|
|
|
|
+ } = self;
|
|
|
|
|
+
|
|
|
|
|
+ let path = path.iter().fold(String::new(), |mut acc, el| {
|
|
|
|
|
+ acc.push('/');
|
|
|
|
|
+ acc.push_str(&el.to_string());
|
|
|
|
|
+ acc
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ let query = if query_params.is_empty() {
|
|
|
|
|
+ if *trailing_query {
|
|
|
|
|
+ String::from("?")
|
|
|
|
|
+ } else {
|
|
|
|
|
+ String::new()
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ let mut params = query_params.iter().enumerate().fold(
|
|
|
|
|
+ String::from("?"),
|
|
|
|
|
+ |mut acc, (i, (key, val))| {
|
|
|
|
|
+ acc.push_str(key);
|
|
|
|
|
+ acc.push('=');
|
|
|
|
|
+ acc.push_str(val);
|
|
|
|
|
+ if i < query_params.len() - 1 {
|
|
|
|
|
+ acc.push('&')
|
|
|
|
|
+ }
|
|
|
|
|
+ acc
|
|
|
|
|
+ },
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ if *trailing_query_pair {
|
|
|
|
|
+ params.push('&');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ params
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ write!(f, "{scheme}://{host}{path}{query}")
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
pub enum Segment<'a> {
|
|
pub enum Segment<'a> {
|
|
|
/// Path segments that do not change.
|
|
/// Path segments that do not change.
|
|
@@ -183,6 +250,15 @@ pub enum Segment<'a> {
|
|
|
Dynamic(&'a str),
|
|
Dynamic(&'a str),
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+impl<'a> Segment<'a> {
|
|
|
|
|
+ pub fn value(&self) -> &'a str {
|
|
|
|
|
+ match self {
|
|
|
|
|
+ Segment::Static(s) => s,
|
|
|
|
|
+ Segment::Dynamic(s) => s,
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
impl<'a> From<Segment<'a>> for SegmentOwned {
|
|
impl<'a> From<Segment<'a>> for SegmentOwned {
|
|
|
fn from(value: Segment<'a>) -> Self {
|
|
fn from(value: Segment<'a>) -> Self {
|
|
|
match value {
|
|
match value {
|
|
@@ -192,6 +268,80 @@ impl<'a> From<Segment<'a>> for SegmentOwned {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+impl<'a> Display for Segment<'a> {
|
|
|
|
|
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
+ match self {
|
|
|
|
|
+ Segment::Static(p) => write!(f, "{p}"),
|
|
|
|
|
+ Segment::Dynamic(p) => write!(f, ":{p}"),
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Debug, Serialize)]
|
|
|
|
|
+#[serde(tag = "type", content = "error")]
|
|
|
|
|
+pub enum UrlError {
|
|
|
|
|
+ /// Contains the duplicate identifier
|
|
|
|
|
+ DuplicatePath(String),
|
|
|
|
|
+
|
|
|
|
|
+ /// Contains info about the parsing error
|
|
|
|
|
+ Parse(UrlParseError),
|
|
|
|
|
+
|
|
|
|
|
+ Var(String),
|
|
|
|
|
+
|
|
|
|
|
+ Db(String),
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+#[derive(Debug, Serialize)]
|
|
|
|
|
+pub struct UrlParseError {
|
|
|
|
|
+ #[serde(serialize_with = "serialize_kind")]
|
|
|
|
|
+ kind: Option<nom::error::ErrorKind>,
|
|
|
|
|
+ token: Option<String>,
|
|
|
|
|
+ input: String,
|
|
|
|
|
+ incomplete: bool,
|
|
|
|
|
+ recoverable: bool,
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn map_nom_err<'a>(
|
|
|
|
|
+ input: &'a str,
|
|
|
|
|
+ token: Option<&'a str>,
|
|
|
|
|
+ e: nom::Err<(&'a str, nom::error::ErrorKind)>,
|
|
|
|
|
+) -> UrlParseError {
|
|
|
|
|
+ let token = token.map(|t| t.to_string());
|
|
|
|
|
+ match e {
|
|
|
|
|
+ nom::Err::Incomplete(_) => UrlParseError {
|
|
|
|
|
+ kind: None,
|
|
|
|
|
+ token,
|
|
|
|
|
+ input: input.to_string(),
|
|
|
|
|
+ incomplete: true,
|
|
|
|
|
+ recoverable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ nom::Err::Error((i, e)) => UrlParseError {
|
|
|
|
|
+ kind: Some(e),
|
|
|
|
|
+ token,
|
|
|
|
|
+ input: i.to_string(),
|
|
|
|
|
+ incomplete: false,
|
|
|
|
|
+ recoverable: true,
|
|
|
|
|
+ },
|
|
|
|
|
+ nom::Err::Failure((i, e)) => UrlParseError {
|
|
|
|
|
+ kind: Some(e),
|
|
|
|
|
+ token,
|
|
|
|
|
+ input: i.to_string(),
|
|
|
|
|
+ incomplete: false,
|
|
|
|
|
+ recoverable: false,
|
|
|
|
|
+ },
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+fn serialize_kind<S>(kind: &Option<nom::error::ErrorKind>, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
|
+where
|
|
|
|
|
+ S: serde::Serializer,
|
|
|
|
|
+{
|
|
|
|
|
+ match kind {
|
|
|
|
|
+ Some(kind) => serializer.serialize_str(kind.description()),
|
|
|
|
|
+ None => serializer.serialize_none(),
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
#[cfg(test)]
|
|
#[cfg(test)]
|
|
|
mod tests {
|
|
mod tests {
|
|
|
use super::{RequestUrl, Segment};
|
|
use super::{RequestUrl, Segment};
|
|
@@ -245,6 +395,30 @@ mod tests {
|
|
|
assert!(url.query_params.is_empty());
|
|
assert!(url.query_params.is_empty());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn parses_sequential_empty_path_segments() {
|
|
|
|
|
+ let input = "http://localhost:4000//";
|
|
|
|
|
+
|
|
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ assert_eq!("http", url.scheme);
|
|
|
|
|
+ assert_eq!("localhost:4000", url.host);
|
|
|
|
|
+ assert_eq!(vec![Segment::Static(""), Segment::Static("")], url.path);
|
|
|
|
|
+ assert!(url.query_params.is_empty());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn parses_sequential_empty_dyn_path_segments() {
|
|
|
|
|
+ let input = "http://localhost:4000/:/:";
|
|
|
|
|
+
|
|
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ assert_eq!("http", url.scheme);
|
|
|
|
|
+ assert_eq!("localhost:4000", url.host);
|
|
|
|
|
+ assert_eq!(vec![Segment::Dynamic(""), Segment::Dynamic("")], url.path);
|
|
|
|
|
+ assert!(url.query_params.is_empty());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
#[test]
|
|
#[test]
|
|
|
fn parse_no_path_segments_trailing_slash() {
|
|
fn parse_no_path_segments_trailing_slash() {
|
|
|
let input = "http://localhost:4000/";
|
|
let input = "http://localhost:4000/";
|
|
@@ -287,4 +461,24 @@ mod tests {
|
|
|
);
|
|
);
|
|
|
assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ #[test]
|
|
|
|
|
+ fn parse_query_params_with_path_trailing_slash() {
|
|
|
|
|
+ let input = "http://localhost:4000/foo/:bar/:qux/?foo=bar&baz=bax";
|
|
|
|
|
+
|
|
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
|
|
+
|
|
|
|
|
+ assert_eq!("http", url.scheme);
|
|
|
|
|
+ assert_eq!("localhost:4000", url.host);
|
|
|
|
|
+ assert_eq!(
|
|
|
|
|
+ vec![
|
|
|
|
|
+ Segment::Static("foo"),
|
|
|
|
|
+ Segment::Dynamic("bar"),
|
|
|
|
|
+ Segment::Dynamic("qux"),
|
|
|
|
|
+ Segment::Static("")
|
|
|
|
|
+ ],
|
|
|
|
|
+ url.path
|
|
|
|
|
+ );
|
|
|
|
|
+ assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|