|
|
@@ -1,9 +1,8 @@
|
|
|
use nom::{
|
|
|
bytes::complete::{tag, take_while},
|
|
|
character::complete::char,
|
|
|
- combinator::opt,
|
|
|
- multi::many0,
|
|
|
- sequence::{preceded, separated_pair, terminated},
|
|
|
+ multi::{many0, separated_list0},
|
|
|
+ sequence::{preceded, separated_pair},
|
|
|
Parser,
|
|
|
};
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
@@ -16,8 +15,8 @@ pub struct RequestUrlOwned {
|
|
|
// pub scheme: String,
|
|
|
// pub host: String,
|
|
|
pub path: Vec<SegmentOwned>,
|
|
|
- pub query_params: Vec<(String, String)>,
|
|
|
- pub has_query: bool,
|
|
|
+ pub query_params: Vec<QueryParamOwned>,
|
|
|
+ pub trail: String,
|
|
|
}
|
|
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
|
@@ -27,15 +26,20 @@ pub enum SegmentOwned {
|
|
|
Dynamic(String, usize),
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, Serialize, Deserialize)]
|
|
|
+pub struct QueryParamOwned {
|
|
|
+ pub key: String,
|
|
|
+ pub value: String,
|
|
|
+ pub pos: usize,
|
|
|
+}
|
|
|
+
|
|
|
/// A fully deconstructed URL from a workspace request.
|
|
|
/// Used as an intermediate step for populating the final URL with variables.
|
|
|
#[derive(Debug)]
|
|
|
pub struct RequestUrl<'a> {
|
|
|
- /// The URL scheme, e.g. `http`.
|
|
|
- // pub scheme: &'a str,
|
|
|
-
|
|
|
- /// The URL host, includes the port if specified.
|
|
|
- // pub host: &'a str,
|
|
|
+ /// The URL pre-string. We do not parse schemes nor
|
|
|
+ /// hosts since those are ususally kept in variables, and we
|
|
|
+ /// do not need it that much for reqwest.
|
|
|
pub pre: &'a str,
|
|
|
|
|
|
/// The URL path segments.
|
|
|
@@ -45,118 +49,91 @@ pub struct RequestUrl<'a> {
|
|
|
pub path: Vec<Segment<'a>>,
|
|
|
|
|
|
/// Query parameters.
|
|
|
- pub query_params: Vec<(&'a str, &'a str)>,
|
|
|
+ pub query_params: Vec<QueryParam<'a>>,
|
|
|
|
|
|
- /// 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,
|
|
|
+ /// URL trail that did not match any path or query parameters.
|
|
|
+ pub trail: &'a str,
|
|
|
}
|
|
|
|
|
|
-type NomError<'a> = nom::Err<(&'a str, nom::error::ErrorKind)>;
|
|
|
-
|
|
|
impl<'a> RequestUrl<'a> {
|
|
|
- pub fn parse(original_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));
|
|
|
- }
|
|
|
-
|
|
|
- // Parse query
|
|
|
- // First char will always be a '?' since we parsed succesfully
|
|
|
- let mut query = &query[1..];
|
|
|
- let mut query_params = vec![];
|
|
|
-
|
|
|
- let mut trailing_pair = false;
|
|
|
-
|
|
|
- loop {
|
|
|
- if query.is_empty() {
|
|
|
- break;
|
|
|
- }
|
|
|
+ pub fn parse(input: &'a str) -> Result<Self, UrlParseError> {
|
|
|
+ let mut offset = 0;
|
|
|
|
|
|
- let (i, params) = separated_pair(
|
|
|
- take_while(|c: char| c != '='),
|
|
|
- char('='),
|
|
|
- take_while(|c: char| c != '&'),
|
|
|
- )
|
|
|
- .parse(query)?;
|
|
|
+ // Attempt to match ://
|
|
|
|
|
|
- query = i;
|
|
|
- query_params.push((params.0, params.1));
|
|
|
+ let (path, pre) = match (
|
|
|
+ take_while(|c| c != ':'),
|
|
|
+ tag::<_, _, nom::error::Error<_>>("://"),
|
|
|
+ )
|
|
|
+ .parse(input)
|
|
|
+ {
|
|
|
+ Ok((rest, (scheme, tag))) => {
|
|
|
+ offset += scheme.len() + tag.len();
|
|
|
|
|
|
- if let Ok((i, _)) = char::<_, nom::error::Error<_>>('&').parse(query) {
|
|
|
- trailing_pair = i.is_empty();
|
|
|
- query = i;
|
|
|
- }
|
|
|
- }
|
|
|
+ // If no path segments, host contains query params if any,
|
|
|
+ // otherwise path contains it.
|
|
|
+ let (path, host) =
|
|
|
+ take_while(|c| c != '/')(rest).map_err(|e| map_nom_err(input, None, e))?;
|
|
|
|
|
|
- debug_assert!(query.is_empty());
|
|
|
+ if path.is_empty() {
|
|
|
+ let (query, query_pre) =
|
|
|
+ take_while(|c| c != '?')(host).map_err(|e| map_nom_err(host, None, e))?;
|
|
|
|
|
|
- Ok((query_params, trailing_pair))
|
|
|
- }
|
|
|
+ offset += query_pre.len();
|
|
|
|
|
|
- let mut offset = 0;
|
|
|
+ let (query_params, trail) = QueryParam::parse(query, offset);
|
|
|
|
|
|
- // Parse the tag if it exists
|
|
|
+ return Ok(RequestUrl {
|
|
|
+ pre: &input[..offset],
|
|
|
+ path: vec![],
|
|
|
+ query_params,
|
|
|
+ trail,
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- let input =
|
|
|
- match opt(terminated(take_while(|c| c != ':'), tag("://"))).parse(original_input) {
|
|
|
- Ok((input, scheme)) => match scheme {
|
|
|
- Some(scheme) => {
|
|
|
- offset += scheme.len() + 3;
|
|
|
- input
|
|
|
- }
|
|
|
- None => input,
|
|
|
- },
|
|
|
- Err(e) => return Err(map_nom_err(original_input, None, e)),
|
|
|
- };
|
|
|
+ offset += host.len();
|
|
|
|
|
|
- // Parse until first /
|
|
|
+ (path, &input[..offset])
|
|
|
+ }
|
|
|
+ Err(_) => {
|
|
|
+ let (path, pre) =
|
|
|
+ take_while(|c| c != '/')(input).map_err(|e| map_nom_err(input, None, e))?;
|
|
|
|
|
|
- let (path, host) =
|
|
|
- take_while(|c| c != '/')(input).map_err(|e| map_nom_err(input, None, e))?;
|
|
|
+ if path.is_empty() {
|
|
|
+ let (query, pre) =
|
|
|
+ take_while(|c| c != '?')(pre).map_err(|e| map_nom_err(pre, None, e))?;
|
|
|
|
|
|
- // We've fully parsed the string, no path
|
|
|
+ offset += pre.len();
|
|
|
|
|
|
- if path.is_empty() {
|
|
|
- let (query, remainder) =
|
|
|
- take_while(|c| c != '?')(host).map_err(|e| map_nom_err(host, None, e))?;
|
|
|
+ let (query_params, trail) = QueryParam::parse(query, offset);
|
|
|
|
|
|
- offset += remainder.len();
|
|
|
+ return Ok(RequestUrl {
|
|
|
+ pre,
|
|
|
+ path: vec![],
|
|
|
+ query_params,
|
|
|
+ trail,
|
|
|
+ });
|
|
|
+ }
|
|
|
|
|
|
- let (query_params, trailing_query_pair) =
|
|
|
- parse_query(query).map_err(|e| map_nom_err(query, None, e))?;
|
|
|
+ offset += pre.len();
|
|
|
|
|
|
- return Ok(RequestUrl {
|
|
|
- pre: &original_input[..offset],
|
|
|
- path: vec![],
|
|
|
- query_params,
|
|
|
- trailing_query: query == "?",
|
|
|
- trailing_query_pair,
|
|
|
- });
|
|
|
- }
|
|
|
+ #[cfg(debug_assertions)]
|
|
|
+ debug_assert_eq!(&input[..offset], pre);
|
|
|
|
|
|
- offset += host.len();
|
|
|
+ (path, &input[..offset])
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
// Parse until query
|
|
|
|
|
|
let (query, path) =
|
|
|
take_while(|c| c != '?')(path).map_err(|e| map_nom_err(path, None, e))?;
|
|
|
|
|
|
- #[cfg(debug_assertions)]
|
|
|
- debug_assert_eq!(&original_input[offset..], path.to_owned() + query);
|
|
|
-
|
|
|
- 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 != '/')))
|
|
|
+ let (_rest, segments) = many0(preceded(char('/'), take_while(|c| c != '/')))
|
|
|
.parse(path)
|
|
|
.map_err(|e| map_nom_err(path, None, e))?;
|
|
|
|
|
|
- let mut segment_offset = offset;
|
|
|
+ debug_assert!(_rest.is_empty());
|
|
|
|
|
|
let mut path = vec![];
|
|
|
|
|
|
@@ -169,31 +146,32 @@ impl<'a> RequestUrl<'a> {
|
|
|
{
|
|
|
Ok((remainder, segment)) => {
|
|
|
debug_assert_eq!("", remainder);
|
|
|
- path.push(Segment::Dynamic(segment, segment_offset));
|
|
|
+ path.push(Segment::Dynamic(segment, offset));
|
|
|
// account for :
|
|
|
- segment_offset += segment.len() + 1;
|
|
|
+ offset += segment.len() + 1;
|
|
|
}
|
|
|
Err(_) => {
|
|
|
- path.push(Segment::Static(segment, segment_offset));
|
|
|
- segment_offset += segment.len();
|
|
|
+ path.push(Segment::Static(segment, offset));
|
|
|
+ offset += segment.len();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// account for the parsed /
|
|
|
- segment_offset += 1;
|
|
|
+ offset += 1;
|
|
|
}
|
|
|
|
|
|
- debug_assert!(remainder.is_empty());
|
|
|
+ let (query_params, trail) = QueryParam::parse(query, offset);
|
|
|
|
|
|
Ok(RequestUrl {
|
|
|
- pre: &original_input[..offset],
|
|
|
+ pre: pre,
|
|
|
path,
|
|
|
query_params,
|
|
|
- trailing_query: query == "?",
|
|
|
- trailing_query_pair: trailing,
|
|
|
+ trail,
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ /// Replaces all dynamic path segments with the value from `path_params`.
|
|
|
+ /// The segment value is the key to the map.
|
|
|
pub fn populate_path(&mut self, path_params: HashMap<&'a str, &'a str>) {
|
|
|
let mut total_displaced = 0i64;
|
|
|
|
|
|
@@ -228,6 +206,7 @@ impl<'a> RequestUrl<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Swap the path segment at `new`'s position with it and adjust subsequent offsets.
|
|
|
pub fn swap_path_segment(&mut self, new: Segment<'a>) {
|
|
|
let Some((skip, segment)) = self
|
|
|
.path
|
|
|
@@ -255,19 +234,59 @@ impl<'a> RequestUrl<'a> {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ pub fn swap_query_param(&mut self, new: QueryParam<'a>) {
|
|
|
+ let Some((skip, qp)) = self
|
|
|
+ .query_params
|
|
|
+ .iter_mut()
|
|
|
+ .enumerate()
|
|
|
+ .map(|(i, qp)| (i + 1, qp))
|
|
|
+ .find(|(_, qp)| qp.pos == new.pos)
|
|
|
+ else {
|
|
|
+ log::warn!(
|
|
|
+ "Attempted to swap query param with invalid position {}",
|
|
|
+ new.pos
|
|
|
+ );
|
|
|
+ return;
|
|
|
+ };
|
|
|
+
|
|
|
+ let offset = new.total_len() as i64 - qp.total_len() as i64;
|
|
|
+
|
|
|
+ *qp = new;
|
|
|
+
|
|
|
+ for qp in self.query_params.iter_mut().skip(skip) {
|
|
|
+ if offset < 0 {
|
|
|
+ qp.pos = qp.pos - offset.abs() as usize;
|
|
|
+ } else {
|
|
|
+ qp.pos = qp.pos + offset as usize;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ pub fn add_qp_clear_trail(&mut self, key: &'a str, value: &'a str) {
|
|
|
+ if let Some(last) = self.query_params.last() {
|
|
|
+ // +1 for the =, +1 for the &
|
|
|
+ let pos = last.key.len() + 2 + last.value.len();
|
|
|
+ self.query_params.push(QueryParam { key, value, pos });
|
|
|
+ } else {
|
|
|
+ // +1 for the ?
|
|
|
+ let mut pos = self.pre.len() + 1;
|
|
|
+ for path in self.path.iter() {
|
|
|
+ pos += path.total_len();
|
|
|
+ }
|
|
|
+ self.query_params.push(QueryParam { key, value, pos });
|
|
|
+ }
|
|
|
+ self.trail = "";
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
impl<'a> From<RequestUrl<'a>> for RequestUrlOwned {
|
|
|
fn from(value: RequestUrl<'_>) -> Self {
|
|
|
Self {
|
|
|
- pre: value.pre.to_string(),
|
|
|
+ pre: value.pre.to_owned(),
|
|
|
path: value.path.into_iter().map(Into::into).collect(),
|
|
|
- query_params: value
|
|
|
- .query_params
|
|
|
- .into_iter()
|
|
|
- .map(|(k, v)| (k.to_owned(), v.to_owned()))
|
|
|
- .collect(),
|
|
|
- has_query: value.trailing_query,
|
|
|
+ query_params: value.query_params.into_iter().map(Into::into).collect(),
|
|
|
+ trail: value.trail.to_owned(),
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -280,8 +299,7 @@ impl<'a> Display for RequestUrl<'a> {
|
|
|
pre,
|
|
|
path,
|
|
|
query_params,
|
|
|
- trailing_query,
|
|
|
- trailing_query_pair,
|
|
|
+ trail,
|
|
|
} = self;
|
|
|
|
|
|
let path = path.iter().fold(String::new(), |mut acc, el| {
|
|
|
@@ -291,33 +309,23 @@ impl<'a> Display for RequestUrl<'a> {
|
|
|
});
|
|
|
|
|
|
let query = if query_params.is_empty() {
|
|
|
- if *trailing_query {
|
|
|
- String::from("?")
|
|
|
- } else {
|
|
|
- String::new()
|
|
|
- }
|
|
|
+ String::new()
|
|
|
} else {
|
|
|
- let mut params = query_params.iter().enumerate().fold(
|
|
|
- String::from("?"),
|
|
|
- |mut acc, (i, (key, val))| {
|
|
|
- acc.push_str(key);
|
|
|
+ query_params
|
|
|
+ .iter()
|
|
|
+ .enumerate()
|
|
|
+ .fold(String::from("?"), |mut acc, (i, q)| {
|
|
|
+ acc.push_str(q.key);
|
|
|
acc.push('=');
|
|
|
- acc.push_str(val);
|
|
|
+ acc.push_str(q.value);
|
|
|
if i < query_params.len() - 1 {
|
|
|
acc.push('&')
|
|
|
}
|
|
|
acc
|
|
|
- },
|
|
|
- );
|
|
|
-
|
|
|
- if *trailing_query_pair {
|
|
|
- params.push('&');
|
|
|
- }
|
|
|
-
|
|
|
- params
|
|
|
+ })
|
|
|
};
|
|
|
|
|
|
- write!(f, "{pre}{path}{query}")
|
|
|
+ write!(f, "{pre}{path}{query}{trail}")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -343,6 +351,8 @@ impl<'a> Segment<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Return the length of the segment's value, excluding the `/` and the `:` in dynamic
|
|
|
+ /// segments.
|
|
|
pub fn len(&self) -> usize {
|
|
|
match self {
|
|
|
Segment::Static(s, _) => s.len(),
|
|
|
@@ -350,6 +360,15 @@ impl<'a> Segment<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Return the full length of the path segment including its `/` and `:` in case of dynamic
|
|
|
+ /// values.
|
|
|
+ pub fn total_len(&self) -> usize {
|
|
|
+ match self {
|
|
|
+ Segment::Static(s, _) => s.len() + 1,
|
|
|
+ Segment::Dynamic(s, _) => s.len() + 2,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
pub fn set_position(&mut self, pos: usize) {
|
|
|
match self {
|
|
|
Segment::Static(_, p) => *p = pos,
|
|
|
@@ -376,6 +395,73 @@ impl<'a> Display for Segment<'a> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+#[derive(Debug, PartialEq, Eq)]
|
|
|
+pub struct QueryParam<'a> {
|
|
|
+ pub key: &'a str,
|
|
|
+ pub value: &'a str,
|
|
|
+ pub pos: usize,
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> QueryParam<'a> {
|
|
|
+ fn parse(query: &'a str, mut offset: usize) -> (Vec<Self>, &'a str) {
|
|
|
+ if query.is_empty() {
|
|
|
+ return (vec![], "");
|
|
|
+ }
|
|
|
+
|
|
|
+ if query == "?" {
|
|
|
+ return (vec![], "?");
|
|
|
+ }
|
|
|
+
|
|
|
+ offset += 1;
|
|
|
+
|
|
|
+ let query = &query[1..];
|
|
|
+
|
|
|
+ let mut query_params = vec![];
|
|
|
+
|
|
|
+ let (rest, params) = separated_list0(
|
|
|
+ char('&'),
|
|
|
+ separated_pair(
|
|
|
+ take_while::<_, _, nom::error::Error<_>>(|c: char| c != '='),
|
|
|
+ char('='),
|
|
|
+ take_while(|c: char| c != '&'),
|
|
|
+ ),
|
|
|
+ )
|
|
|
+ .parse(query)
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ for param in params {
|
|
|
+ query_params.push(QueryParam {
|
|
|
+ key: param.0,
|
|
|
+ value: param.1,
|
|
|
+ pos: offset,
|
|
|
+ });
|
|
|
+ // +1 for the &, +1 for the =
|
|
|
+ offset += param.0.len() + 2 + param.1.len();
|
|
|
+ }
|
|
|
+
|
|
|
+ (query_params, rest)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn total_len(&self) -> usize {
|
|
|
+ self.key.len() + 1 + self.value.len()
|
|
|
+ }
|
|
|
+
|
|
|
+ #[cfg(test)]
|
|
|
+ fn new(key: &'a str, value: &'a str, pos: usize) -> Self {
|
|
|
+ Self { key, value, pos }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+impl<'a> From<QueryParam<'a>> for QueryParamOwned {
|
|
|
+ fn from(value: QueryParam<'a>) -> Self {
|
|
|
+ Self {
|
|
|
+ key: value.key.to_owned(),
|
|
|
+ value: value.value.to_owned(),
|
|
|
+ pos: value.pos,
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
#[derive(Debug, Serialize)]
|
|
|
#[serde(tag = "type", content = "error")]
|
|
|
pub enum UrlError {
|
|
|
@@ -451,6 +537,8 @@ where
|
|
|
mod tests {
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
+ use crate::request::url::QueryParam;
|
|
|
+
|
|
|
use super::{RequestUrl, Segment};
|
|
|
|
|
|
#[test]
|
|
|
@@ -562,7 +650,34 @@ mod tests {
|
|
|
assert_eq!("http://localhost:4000", url.pre);
|
|
|
|
|
|
assert!(url.path.is_empty());
|
|
|
- assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("foo", "bar", "http://localhost:4000?".len()),
|
|
|
+ QueryParam::new("baz", "bax", "http://localhost:4000?foo=bar&".len()),
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn parse_query_params_dirty() {
|
|
|
+ let input = "http://localhost:4000?=bar&baz=&=";
|
|
|
+
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+
|
|
|
+ assert!(url.path.is_empty());
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("", "bar", "http://localhost:4000?".len()),
|
|
|
+ QueryParam::new("baz", "", "http://localhost:4000?=bar&".len()),
|
|
|
+ QueryParam::new("", "", "http://localhost:4000?=bar&baz=&".len()),
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
@@ -581,7 +696,17 @@ mod tests {
|
|
|
],
|
|
|
url.path
|
|
|
);
|
|
|
- assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("foo", "bar", "http://localhost:4000/foo/:bar/:qux?".len()),
|
|
|
+ QueryParam::new(
|
|
|
+ "baz",
|
|
|
+ "bax",
|
|
|
+ "http://localhost:4000/foo/:bar/:qux?foo=bar&".len()
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
@@ -601,7 +726,189 @@ mod tests {
|
|
|
],
|
|
|
url.path
|
|
|
);
|
|
|
- assert_eq!(vec![("foo", "bar"), ("baz", "bax")], url.query_params);
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("foo", "bar", "http://localhost:4000/foo/:bar/:qux/?".len()),
|
|
|
+ QueryParam::new(
|
|
|
+ "baz",
|
|
|
+ "bax",
|
|
|
+ "http://localhost:4000/foo/:bar/:qux/?foo=bar&".len()
|
|
|
+ )
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn parse_query_params_empty() {
|
|
|
+ let input = "http://localhost:4000?";
|
|
|
+
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+ assert_eq!("?", url.trail);
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn swap_query_params() {
|
|
|
+ let input = "http://localhost:4000?foo=bar&qux=420";
|
|
|
+
|
|
|
+ let mut url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+
|
|
|
+ assert!(url.path.is_empty());
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("foo", "bar", "http://localhost:4000?".len()),
|
|
|
+ QueryParam::new("qux", "420", "http://localhost:4000?foo=bar&".len()),
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+
|
|
|
+ url.swap_query_param(QueryParam::new(
|
|
|
+ "foobar",
|
|
|
+ "69",
|
|
|
+ "http://localhost:4000?".len(),
|
|
|
+ ));
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("foobar", "69", "http://localhost:4000?".len()),
|
|
|
+ QueryParam::new("qux", "420", "http://localhost:4000?foobar=69&".len()),
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000?foobar=69&qux=420", url.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn swap_query_params_dirty() {
|
|
|
+ let input = "http://localhost:4000?=bar&baz=&=";
|
|
|
+
|
|
|
+ let mut url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+
|
|
|
+ assert!(url.path.is_empty());
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![
|
|
|
+ QueryParam::new("", "bar", "http://localhost:4000?".len()),
|
|
|
+ QueryParam::new("baz", "", "http://localhost:4000?=bar&".len()),
|
|
|
+ QueryParam::new("", "", "http://localhost:4000?=bar&baz=&".len()),
|
|
|
+ ],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+
|
|
|
+ url.swap_query_param(QueryParam::new("foo", "69", "http://localhost:4000?".len()));
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ QueryParam::new("foo", "69", "http://localhost:4000?".len()),
|
|
|
+ url.query_params[0]
|
|
|
+ );
|
|
|
+
|
|
|
+ url.swap_query_param(QueryParam::new(
|
|
|
+ "bazooka",
|
|
|
+ "420",
|
|
|
+ "http://localhost:4000?foo=69&".len(),
|
|
|
+ ));
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ QueryParam::new("bazooka", "420", "http://localhost:4000?foo=69&".len()),
|
|
|
+ url.query_params[1]
|
|
|
+ );
|
|
|
+
|
|
|
+ url.swap_query_param(QueryParam::new(
|
|
|
+ "test",
|
|
|
+ "works",
|
|
|
+ "http://localhost:4000?foo=69&bazooka=420&".len(),
|
|
|
+ ));
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ QueryParam::new(
|
|
|
+ "test",
|
|
|
+ "works",
|
|
|
+ "http://localhost:4000?foo=69&bazooka=420&".len()
|
|
|
+ ),
|
|
|
+ url.query_params[2]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn add_query_params_trailing_empty() {
|
|
|
+ let input = "http://localhost:4000?";
|
|
|
+
|
|
|
+ let mut url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ url.add_qp_clear_trail("foo", "bar");
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+ assert_eq!(
|
|
|
+ vec![QueryParam::new(
|
|
|
+ "foo",
|
|
|
+ "bar",
|
|
|
+ "http://localhost:4000?".len()
|
|
|
+ )],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000?foo=bar", url.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn adds_query_parameter_with_path() {
|
|
|
+ let input = "http://foo.com/foo/:ID";
|
|
|
+
|
|
|
+ let mut url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ url.add_qp_clear_trail("foo", "bar");
|
|
|
+
|
|
|
+ assert_eq!(
|
|
|
+ vec![QueryParam::new(
|
|
|
+ "foo",
|
|
|
+ "bar",
|
|
|
+ "http://foo.com/foo/:ID?".len()
|
|
|
+ )],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+
|
|
|
+ assert_eq!("http://foo.com/foo/:ID?foo=bar", url.to_string());
|
|
|
+ }
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn parse_query_params_trailing() {
|
|
|
+ let input = "http://localhost:4000?foo=bar&";
|
|
|
+
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+ assert_eq!(
|
|
|
+ vec![QueryParam::new(
|
|
|
+ "foo",
|
|
|
+ "bar",
|
|
|
+ "http://localhost:4000?".len()
|
|
|
+ ),],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+ assert_eq!("&", url.trail);
|
|
|
+
|
|
|
+ let input = "http://localhost:4000?foo=bar&trailing";
|
|
|
+
|
|
|
+ let url = RequestUrl::parse(input).unwrap();
|
|
|
+
|
|
|
+ assert_eq!("http://localhost:4000", url.pre);
|
|
|
+ assert_eq!(
|
|
|
+ vec![QueryParam::new(
|
|
|
+ "foo",
|
|
|
+ "bar",
|
|
|
+ "http://localhost:4000?".len()
|
|
|
+ ),],
|
|
|
+ url.query_params
|
|
|
+ );
|
|
|
+ assert_eq!("&trailing", url.trail);
|
|
|
}
|
|
|
|
|
|
#[test]
|