mirror of https://github.com/rwf2/Rocket.git
Overhaul URI types, parsers, 'uri!' macro.
This commit entirely rewrites Rocket's URI parsing routines and overhauls the 'uri!' macro resolving all known issues and removing any potential limitations for compile-time URI creation. This commit: * Introduces a new 'Reference' URI variant for URI-references. * Modifies 'Redirect' to accept 'TryFrom<Reference>'. * Introduces a new 'Asterisk' URI variant for parity. * Allows creation of any URI type from a string literal via 'uri!'. * Enables dynamic/static prefixing/suffixing of route URIs in 'uri!'. * Unifies 'Segments' and 'QuerySegments' into one generic 'Segments'. * Consolidates URI formatting types/traits into a 'uri::fmt' module. * Makes APIs more symmetric across URI types. It also includes the following less-relevant changes: * Implements 'FromParam' for a single-segment 'PathBuf'. * Adds 'FileName::is_safe()'. * No longer reparses upstream request URIs. Resolves #842. Resolves #853. Resolves #998.
This commit is contained in:
parent
15b1cf59dd
commit
fa3e0334c1
|
@ -76,17 +76,17 @@
|
|||
//! `SpaceHelmet`:
|
||||
//!
|
||||
//! ```rust
|
||||
//! # extern crate rocket;
|
||||
//! # #[macro_use] extern crate rocket;
|
||||
//! # extern crate rocket_contrib;
|
||||
//! use rocket::http::uri::Uri;
|
||||
//! use rocket_contrib::helmet::{SpaceHelmet, Frame, XssFilter, Hsts, NoSniff};
|
||||
//!
|
||||
//! let site_uri = Uri::parse("https://mysite.example.com").unwrap();
|
||||
//! let report_uri = Uri::parse("https://report.example.com").unwrap();
|
||||
//! let site_uri = uri!("https://mysite.example.com");
|
||||
//! let report_uri = uri!("https://report.example.com");
|
||||
//! let helmet = SpaceHelmet::default()
|
||||
//! .enable(Hsts::default())
|
||||
//! .enable(Frame::AllowFrom(site_uri))
|
||||
//! .enable(XssFilter::EnableReport(report_uri))
|
||||
//! .enable(Frame::AllowFrom(site_uri.into()))
|
||||
//! .enable(XssFilter::EnableReport(report_uri.into()))
|
||||
//! .disable::<NoSniff>();
|
||||
//! ```
|
||||
//!
|
||||
|
|
|
@ -357,10 +357,12 @@ impl Into<Vec<Route>> for StaticFiles {
|
|||
#[rocket::async_trait]
|
||||
impl Handler for StaticFiles {
|
||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
||||
use rocket::http::uri::fmt::Path;
|
||||
|
||||
// Get the segments as a `PathBuf`, allowing dotfiles requested.
|
||||
let options = self.options;
|
||||
let allow_dotfiles = options.contains(Options::DotFiles);
|
||||
let path = req.segments::<Segments<'_>>(0..).ok()
|
||||
let path = req.segments::<Segments<'_, Path>>(0..).ok()
|
||||
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
|
||||
.map(|path| self.root.join(path));
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ extern crate rocket;
|
|||
|
||||
#[cfg(feature = "helmet")]
|
||||
mod helmet_tests {
|
||||
use rocket::http::{Status, uri::Uri};
|
||||
use rocket::http::Status;
|
||||
use rocket::local::blocking::{Client, LocalResponse};
|
||||
|
||||
use rocket_contrib::helmet::*;
|
||||
|
@ -108,24 +108,22 @@ mod helmet_tests {
|
|||
|
||||
#[test]
|
||||
fn uri_test() {
|
||||
let allow_uri = Uri::parse("https://www.google.com").unwrap();
|
||||
let report_uri = Uri::parse("https://www.google.com").unwrap();
|
||||
let enforce_uri = Uri::parse("https://www.google.com").unwrap();
|
||||
let allow_uri = uri!("https://rocket.rs");
|
||||
let report_uri = uri!("https://rocket.rs");
|
||||
let enforce_uri = uri!("https://rocket.rs");
|
||||
|
||||
let helmet = SpaceHelmet::default()
|
||||
.enable(Frame::AllowFrom(allow_uri))
|
||||
.enable(XssFilter::EnableReport(report_uri))
|
||||
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri));
|
||||
.enable(Frame::AllowFrom(allow_uri.into()))
|
||||
.enable(XssFilter::EnableReport(report_uri.into()))
|
||||
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri.into()));
|
||||
|
||||
dispatch!(helmet, |response: LocalResponse<'_>| {
|
||||
assert_header!(response, "X-Frame-Options",
|
||||
"ALLOW-FROM https://www.google.com");
|
||||
assert_header!(response, "X-Frame-Options", "ALLOW-FROM https://rocket.rs");
|
||||
|
||||
assert_header!(response, "X-XSS-Protection",
|
||||
"1; report=https://www.google.com");
|
||||
assert_header!(response, "X-XSS-Protection", "1; report=https://rocket.rs");
|
||||
|
||||
assert_header!(response, "Expect-CT",
|
||||
"max-age=30, enforce, report-uri=\"https://www.google.com\"");
|
||||
"max-age=30, enforce, report-uri=\"https://rocket.rs\"");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,8 @@ impl FromMeta for Dynamic {
|
|||
let string = StringLit::from_meta(meta)?;
|
||||
let span = string.subspan(1..string.len() + 1);
|
||||
|
||||
// We don't allow `_`. We abuse `uri::Query` to enforce this.
|
||||
Ok(Dynamic::parse::<uri::Query>(&string, span)?)
|
||||
// We don't allow `_`. We abuse `fmt::Query` to enforce this.
|
||||
Ok(Dynamic::parse::<fmt::Query>(&string, span)?)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ use unicode_xid::UnicodeXID;
|
|||
use devise::{Diagnostic, ext::SpanDiagnosticExt};
|
||||
|
||||
use crate::name::Name;
|
||||
use crate::http::uri::{self, UriPart};
|
||||
use crate::proc_macro_ext::StringLit;
|
||||
use crate::proc_macro2::Span;
|
||||
use crate::attribute::param::{Parameter, Dynamic};
|
||||
use crate::http::uri::fmt::{Part, Kind, Path};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error<'a> {
|
||||
|
@ -27,7 +27,7 @@ pub enum ErrorKind {
|
|||
}
|
||||
|
||||
impl Dynamic {
|
||||
pub fn parse<P: UriPart>(
|
||||
pub fn parse<P: Part>(
|
||||
segment: &str,
|
||||
span: Span,
|
||||
) -> Result<Self, Error<'_>> {
|
||||
|
@ -40,7 +40,7 @@ impl Dynamic {
|
|||
}
|
||||
|
||||
impl Parameter {
|
||||
pub fn parse<P: UriPart>(
|
||||
pub fn parse<P: Part>(
|
||||
segment: &str,
|
||||
source_span: Span,
|
||||
) -> Result<Self, Error<'_>> {
|
||||
|
@ -62,7 +62,7 @@ impl Parameter {
|
|||
}
|
||||
|
||||
let dynamic = Dynamic { name: Name::new(name, span), trailing, index: 0 };
|
||||
if dynamic.is_wild() && P::KIND != uri::Kind::Path {
|
||||
if dynamic.is_wild() && P::KIND != Kind::Path {
|
||||
return Err(Error::new(name, span, ErrorKind::Ignored));
|
||||
} else if dynamic.is_wild() {
|
||||
return Ok(Parameter::Ignored(dynamic));
|
||||
|
@ -84,7 +84,7 @@ impl Parameter {
|
|||
Ok(Parameter::Static(Name::new(segment, source_span)))
|
||||
}
|
||||
|
||||
pub fn parse_many<P: uri::UriPart>(
|
||||
pub fn parse_many<P: Part>(
|
||||
source: &str,
|
||||
source_span: Span,
|
||||
) -> impl Iterator<Item = Result<Self, Error<'_>>> {
|
||||
|
@ -182,7 +182,7 @@ impl devise::FromMeta for Dynamic {
|
|||
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
||||
let string = StringLit::from_meta(meta)?;
|
||||
let span = string.subspan(1..string.len() + 1);
|
||||
let param = Dynamic::parse::<uri::Path>(&string, span)?;
|
||||
let param = Dynamic::parse::<Path>(&string, span)?;
|
||||
|
||||
if param.is_wild() {
|
||||
return Err(Error::new(&string, span, ErrorKind::Ignored).into());
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::syn_ext::FnArgExt;
|
|||
use crate::name::Name;
|
||||
use crate::proc_macro2::Span;
|
||||
use crate::http::ext::IntoOwned;
|
||||
use crate::http::uri::{self, Origin};
|
||||
use crate::http::uri::{Origin, fmt};
|
||||
|
||||
/// This structure represents the parsed `route` attribute and associated items.
|
||||
#[derive(Debug)]
|
||||
|
@ -159,14 +159,14 @@ impl Route {
|
|||
|
||||
// Parse and collect the path parameters.
|
||||
let (source, span) = (attr.uri.path(), attr.uri.path_span);
|
||||
let path_params = Parameter::parse_many::<uri::Path>(source.as_str(), span)
|
||||
let path_params = Parameter::parse_many::<fmt::Path>(source.as_str(), span)
|
||||
.map(|p| Route::upgrade_param(p?, &arguments))
|
||||
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Parse and collect the query parameters.
|
||||
let query_params = match (attr.uri.query(), attr.uri.query_span) {
|
||||
(Some(r), Some(span)) => Parameter::parse_many::<uri::Query>(r.as_str(), span)
|
||||
(Some(q), Some(span)) => Parameter::parse_many::<fmt::Query>(q.as_str(), span)
|
||||
.map(|p| Route::upgrade_param(p?, &arguments))
|
||||
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
||||
.collect::<Vec<_>>(),
|
||||
|
|
|
@ -46,19 +46,19 @@ pub fn catchers_macro(input: proc_macro::TokenStream) -> TokenStream {
|
|||
pub fn uri_macro(input: proc_macro::TokenStream) -> TokenStream {
|
||||
uri::_uri_macro(input.into())
|
||||
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
|
||||
rocket::http::uri::Origin::dummy()
|
||||
rocket::http::uri::Origin::ROOT
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn uri_internal_macro(input: proc_macro::TokenStream) -> TokenStream {
|
||||
// FIXME: Ideally we would generate an `Origin::dummy()` so that we don't
|
||||
// TODO: Ideally we would generate a perfect `Origin::ROOT` so that we don't
|
||||
// assist in propoagate further errors. Alas, we can't set the span to the
|
||||
// invocation of `uri!` without access to `span.parent()`, and
|
||||
// `Span::call_site()` here points to the `#[route]`, immediate caller,
|
||||
// generate a rather confusing error message when there's a type-mismatch.
|
||||
// generating a rather confusing error message when there's a type-mismatch.
|
||||
uri::_uri_internal_macro(input.into())
|
||||
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
|
||||
rocket::http::uri::Origin::dummy()
|
||||
rocket::http::uri::Origin::ROOT
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
|
@ -3,15 +3,14 @@ use std::fmt::Display;
|
|||
use devise::{syn, Result};
|
||||
use devise::ext::{SpanDiagnosticExt, quote_respanned};
|
||||
|
||||
use crate::http::uri;
|
||||
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
||||
use crate::http::uri::fmt;
|
||||
use crate::http_codegen::Optional;
|
||||
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
||||
use crate::syn_ext::IdentExt;
|
||||
use crate::bang::uri_parsing::*;
|
||||
use crate::proc_macro2::TokenStream;
|
||||
use crate::attribute::param::Parameter;
|
||||
use crate::exports::_uri;
|
||||
|
||||
use crate::exports::*;
|
||||
use crate::URI_MACRO_PREFIX;
|
||||
|
||||
macro_rules! p {
|
||||
|
@ -31,11 +30,14 @@ pub fn prefix_last_segment(path: &mut syn::Path, prefix: &str) {
|
|||
|
||||
pub fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
||||
let input2: TokenStream = input.clone().into();
|
||||
let mut params = syn::parse2::<UriParams>(input)?;
|
||||
prefix_last_segment(&mut params.route_path, URI_MACRO_PREFIX);
|
||||
|
||||
let path = ¶ms.route_path;
|
||||
match syn::parse2::<UriMacro>(input)? {
|
||||
UriMacro::Routed(ref mut mac) => {
|
||||
prefix_last_segment(&mut mac.route.path, URI_MACRO_PREFIX);
|
||||
let path = &mac.route.path;
|
||||
Ok(quote!(#path!(#input2)))
|
||||
},
|
||||
UriMacro::Literal(uri) => Ok(quote!(#uri)),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
||||
|
@ -44,7 +46,7 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
|||
impl Iterator<Item = (&'a Ident, &'a Type)>, // types for both path || query
|
||||
)>
|
||||
{
|
||||
let route_name = &internal.uri_params.route_path;
|
||||
let route_name = &internal.uri_mac.route.path;
|
||||
match internal.validate() {
|
||||
Validation::Ok(exprs) => {
|
||||
let path_params = internal.dynamic_path_params();
|
||||
|
@ -64,7 +66,7 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
|||
let mut route_name = quote!(#route_name).to_string();
|
||||
route_name.retain(|c| !c.is_whitespace());
|
||||
|
||||
let diag = internal.uri_params.args_span()
|
||||
let diag = internal.uri_mac.args_span()
|
||||
.error("expected unnamed arguments due to ignored parameters")
|
||||
.note(format!("uri for route `{}` ignores path parameters: \"{}\"",
|
||||
route_name, internal.route_uri));
|
||||
|
@ -75,17 +77,16 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
|||
let mut route_name = quote!(#route_name).to_string();
|
||||
route_name.retain(|c| !c.is_whitespace());
|
||||
|
||||
let diag = internal.uri_params.args_span()
|
||||
.error(format!("expected {} but {} supplied",
|
||||
let diag = internal.uri_mac.args_span()
|
||||
.error(format!("route expects {} but {} supplied",
|
||||
p!(expected, "parameter"), p!(actual, "was")))
|
||||
.note(format!("route `{}` has uri \"{}\"",
|
||||
route_name, internal.route_uri));
|
||||
.note(format!("route `{}` has uri \"{}\"", route_name, internal.route_uri));
|
||||
|
||||
Err(diag)
|
||||
}
|
||||
Validation::Named(missing, extra, dup) => {
|
||||
let e = format!("invalid parameters for `{}` route uri", quote!(#route_name));
|
||||
let mut diag = internal.uri_params.args_span().error(e)
|
||||
let mut diag = internal.uri_mac.args_span().error(e)
|
||||
.note(format!("uri parameters are: {}", internal.fn_args_str()));
|
||||
|
||||
fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
|
||||
|
@ -116,11 +117,11 @@ fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
|||
}
|
||||
}
|
||||
|
||||
fn add_binding<P: uri::UriPart>(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr) {
|
||||
fn add_binding<P: fmt::Part>(to: &mut Vec<TokenStream>, ident: &Ident, ty: &Type, expr: &Expr) {
|
||||
let span = expr.span();
|
||||
let part = match P::KIND {
|
||||
uri::Kind::Path => quote_spanned!(span => #_uri::Path),
|
||||
uri::Kind::Query => quote_spanned!(span => #_uri::Query),
|
||||
fmt::Kind::Path => quote_spanned!(span => #_fmt::Path),
|
||||
fmt::Kind::Query => quote_spanned!(span => #_fmt::Query),
|
||||
};
|
||||
|
||||
let tmp_ident = ident.clone().with_span(expr.span());
|
||||
|
@ -128,7 +129,7 @@ fn add_binding<P: uri::UriPart>(to: &mut Vec<TokenStream>, ident: &Ident, ty: &T
|
|||
|
||||
to.push(quote_spanned!(span =>
|
||||
#[allow(non_snake_case)] #let_stmt;
|
||||
let #ident = <#ty as #_uri::FromUriParam<#part, _>>::from_uri_param(#tmp_ident);
|
||||
let #ident = <#ty as #_fmt::FromUriParam<#part, _>>::from_uri_param(#tmp_ident);
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -139,23 +140,11 @@ fn explode_path<'a>(
|
|||
mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
|
||||
) -> TokenStream {
|
||||
if internal.dynamic_path_params().count() == 0 {
|
||||
let route_uri = &internal.route_uri;
|
||||
if let Some(ref mount) = internal.uri_params.mount_point {
|
||||
let full_uri = route_uri.map_path(|p| format!("{}/{}", mount, p))
|
||||
.expect("origin from path")
|
||||
.into_normalized();
|
||||
|
||||
let path = full_uri.path().as_str();
|
||||
return quote!(#_uri::UriArgumentsKind::Static(#path));
|
||||
let path = internal.route_uri.path().as_str();
|
||||
quote!(#_fmt::UriArgumentsKind::Static(#path))
|
||||
} else {
|
||||
let path = route_uri.path().as_str();
|
||||
return quote!(#_uri::UriArgumentsKind::Static(#path));
|
||||
}
|
||||
}
|
||||
|
||||
let uri_display = quote!(#_uri::UriDisplay<#_uri::Path>);
|
||||
let all_path_params = internal.mount_params.iter().chain(internal.path_params.iter());
|
||||
let dyn_exprs = all_path_params.map(|param| {
|
||||
let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Path>);
|
||||
let dyn_exprs = internal.path_params.iter().map(|param| {
|
||||
match param {
|
||||
Parameter::Static(name) => {
|
||||
quote!(&#name as &dyn #uri_display)
|
||||
|
@ -163,7 +152,7 @@ fn explode_path<'a>(
|
|||
Parameter::Dynamic(_) | Parameter::Guard(_) => {
|
||||
let (ident, ty) = args.next().expect("ident/ty for non-ignored");
|
||||
let expr = exprs.next().expect("one expr per dynamic arg");
|
||||
add_binding::<uri::Path>(bindings, &ident, &ty, &expr);
|
||||
add_binding::<fmt::Path>(bindings, &ident, &ty, &expr);
|
||||
quote_spanned!(expr.span() => &#ident as &dyn #uri_display)
|
||||
}
|
||||
Parameter::Ignored(_) => {
|
||||
|
@ -173,7 +162,8 @@ fn explode_path<'a>(
|
|||
}
|
||||
});
|
||||
|
||||
quote!(#_uri::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
|
||||
quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
|
||||
}
|
||||
}
|
||||
|
||||
fn explode_query<'a>(
|
||||
|
@ -184,11 +174,11 @@ fn explode_query<'a>(
|
|||
) -> Option<TokenStream> {
|
||||
let query = internal.route_uri.query()?.as_str();
|
||||
if internal.dynamic_query_params().count() == 0 {
|
||||
return Some(quote!(#_uri::UriArgumentsKind::Static(#query)));
|
||||
return Some(quote!(#_fmt::UriArgumentsKind::Static(#query)));
|
||||
}
|
||||
|
||||
let query_arg = quote!(#_uri::UriQueryArgument);
|
||||
let uri_display = quote!(#_uri::UriDisplay<#_uri::Query>);
|
||||
let query_arg = quote!(#_fmt::UriQueryArgument);
|
||||
let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Query>);
|
||||
let dyn_exprs = internal.query_params.iter().filter_map(|param| {
|
||||
if let Parameter::Static(source) = param {
|
||||
return Some(quote!(#query_arg::Raw(#source)));
|
||||
|
@ -208,10 +198,9 @@ fn explode_query<'a>(
|
|||
let expr = match arg_expr.as_expr() {
|
||||
Some(expr) => expr,
|
||||
None => {
|
||||
// Force a typecheck for the `Ignoreable` trait. Note that write
|
||||
// out the path to `is_ignorable` to get the right span.
|
||||
// Force a typecheck for the `Ignoreable` trait.
|
||||
bindings.push(quote_respanned! { arg_expr.span() =>
|
||||
rocket::http::uri::assert_ignorable::<#_uri::Query, #ty>();
|
||||
#_fmt::assert_ignorable::<#_fmt::Query, #ty>();
|
||||
});
|
||||
|
||||
return None;
|
||||
|
@ -219,7 +208,7 @@ fn explode_query<'a>(
|
|||
};
|
||||
|
||||
let name = &dynamic.name;
|
||||
add_binding::<uri::Query>(bindings, &ident, &ty, &expr);
|
||||
add_binding::<fmt::Query>(bindings, &ident, &ty, &expr);
|
||||
Some(match dynamic.trailing {
|
||||
false => quote_spanned! { expr.span() =>
|
||||
#query_arg::NameValue(#name, &#ident as &dyn #uri_display)
|
||||
|
@ -230,7 +219,7 @@ fn explode_query<'a>(
|
|||
})
|
||||
});
|
||||
|
||||
Some(quote!(#_uri::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])))
|
||||
Some(quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*])))
|
||||
}
|
||||
|
||||
pub fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
|
||||
|
@ -239,13 +228,20 @@ pub fn _uri_internal_macro(input: TokenStream) -> Result<TokenStream> {
|
|||
let (path_exprs, query_exprs, mut fn_args) = extract_exprs(&internal)?;
|
||||
|
||||
let mut bindings = vec![];
|
||||
let uri_mod = quote!(rocket::http::uri);
|
||||
let path = explode_path(&internal, &mut bindings, path_exprs, &mut fn_args);
|
||||
let query = explode_query(&internal, &mut bindings, query_exprs, fn_args);
|
||||
let query = Optional(query);
|
||||
let query = Optional(explode_query(&internal, &mut bindings, query_exprs, fn_args));
|
||||
|
||||
Ok(quote!({
|
||||
let prefix = internal.uri_mac.prefix.as_ref()
|
||||
.map(|prefix| quote_spanned!(prefix.span() => .with_prefix(#prefix)));
|
||||
|
||||
let suffix = internal.uri_mac.suffix.as_ref()
|
||||
.map(|suffix| quote_spanned!(suffix.span() => .with_suffix(#suffix)));
|
||||
|
||||
Ok(quote_spanned!(internal.uri_mac.route.path.span() =>
|
||||
#[allow(unused_braces)] {
|
||||
#(#bindings)*
|
||||
#uri_mod::UriArguments { path: #path, query: #query, }.into_origin()
|
||||
}))
|
||||
let __builder = #_fmt::RouteUriBuilder::new(#path, #query);
|
||||
__builder #prefix #suffix .render()
|
||||
}
|
||||
))
|
||||
}
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
use std::ops::Deref;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use devise::{Spanned, ext::TypeExt};
|
||||
use quote::ToTokens;
|
||||
use quote::{ToTokens, TokenStreamExt};
|
||||
use rocket_http::uri::{Error, Reference};
|
||||
|
||||
use crate::syn::{self, Expr, Ident, LitStr, Path, Token, Type};
|
||||
use crate::syn::parse::{self, Parse, ParseStream};
|
||||
use crate::{http_codegen, syn::{self, Expr, Ident, LitStr, Path, Token, Type}};
|
||||
use crate::syn::parse::{self, Parse, ParseStream, Parser};
|
||||
use crate::syn::punctuated::Punctuated;
|
||||
|
||||
use crate::http::{uri, uri::Origin, ext::IntoOwned};
|
||||
use crate::proc_macro2::{TokenStream, Span};
|
||||
use crate::http::uri::{Uri, Origin, Absolute, fmt};
|
||||
use crate::http::ext::IntoOwned;
|
||||
use crate::proc_macro2::{TokenStream, TokenTree, Span};
|
||||
use crate::proc_macro_ext::StringLit;
|
||||
use crate::attribute::param::{Parameter, Dynamic};
|
||||
use crate::name::Name;
|
||||
|
@ -32,24 +36,54 @@ pub enum Args {
|
|||
Named(Punctuated<Arg, Token![,]>),
|
||||
}
|
||||
|
||||
// For an invocation that looks like:
|
||||
// uri!("/mount/point", this::route: e1, e2, e3);
|
||||
// ^-------------| ^----------| ^---------|
|
||||
// uri_params.mount_point | uri_params.arguments
|
||||
// uri_params.route_path
|
||||
/// A string literal parsed as a URI.
|
||||
#[derive(Debug)]
|
||||
pub struct UriParams {
|
||||
pub mount_point: Option<Origin<'static>>,
|
||||
pub route_path: Path,
|
||||
pub arguments: Args,
|
||||
pub struct UriLit(Uri<'static>, Span);
|
||||
|
||||
/// An expression in a URI slot (prefix, suffix, or literal).
|
||||
#[derive(Debug)]
|
||||
pub enum UriExpr {
|
||||
/// A string literal parsed as a URI.
|
||||
Uri(UriLit),
|
||||
/// An expression that will be typechecked to be some URI kind.
|
||||
Expr(Expr),
|
||||
}
|
||||
|
||||
/// See `UriMacro` for what each field represents.
|
||||
#[derive(Debug)]
|
||||
pub struct RouteInvocation {
|
||||
pub path: Path,
|
||||
pub args: Args,
|
||||
}
|
||||
|
||||
/// See `UriMacro` for what each field represents.
|
||||
#[derive(Debug)]
|
||||
pub struct RoutedUri {
|
||||
pub prefix: Option<UriExpr>,
|
||||
pub route: RouteInvocation,
|
||||
pub suffix: Option<UriExpr>,
|
||||
}
|
||||
|
||||
// The macro can be invoked with 1, 2, or 3 arguments.
|
||||
//
|
||||
// As a `Literal`, with a single argument:
|
||||
// uri!("/mount/point");
|
||||
// ^-------------|
|
||||
// literal.0
|
||||
//
|
||||
// As `Routed`, with 1, 2, or 3 arguments: prefix/suffix optional.
|
||||
// uri!("/mount/point", this::route(e1, e2, e3), "?some#suffix");
|
||||
// ^-------------| ^---------|^----------| |-----|------|
|
||||
// routed.prefix | | routed.suffix
|
||||
// | route.route.args
|
||||
// routed.route.path
|
||||
#[derive(Debug)]
|
||||
pub enum UriMacro {
|
||||
Literal(UriLit),
|
||||
Routed(RoutedUri),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FnArg {
|
||||
pub ident: Ident,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
pub enum Validation<'a> {
|
||||
// Parameters that were ignored in a named argument setting.
|
||||
NamedIgnored(Vec<&'a Dynamic>),
|
||||
|
@ -63,27 +97,38 @@ pub enum Validation<'a> {
|
|||
|
||||
// This is invoked by Rocket itself. The `uri!` macro expands to a call to a
|
||||
// route-specific macro which in-turn expands to a call to `internal_uri!`,
|
||||
// passing along the user's parameters (`uri_params`) from the original `uri!`
|
||||
// passing along the user's invocation (`uri_mac`) from the original `uri!`
|
||||
// call. This is necessary so that we can converge the type information in the
|
||||
// route (from the route-specific macro) with the user's parameters (by
|
||||
// forwarding them to the internal_uri! call).
|
||||
//
|
||||
// `fn_args` are the URI arguments (excluding request guards and ignored path
|
||||
// parts) from the original handler in the order they were declared in the URI
|
||||
// (`<first>/<second>`). `route_uri` is the URI itself.
|
||||
// `fn_args` are the URI arguments (excluding request guards) from the original
|
||||
// handler in the order they were declared in the URI (`<first>/<second>`).
|
||||
// `route_uri` is the full route URI itself.
|
||||
//
|
||||
// The syntax of `uri_mac` is that of `UriMacro`.
|
||||
//
|
||||
// internal_uri!("/<one>/<_>?lang=en&<two>", (one: ty, two: ty), $($tt)*);
|
||||
// ^----/----^ ^-----\-----^ ^-------/------^ ^-----|
|
||||
// path_params query_params fn_args uri_params
|
||||
// path_params query_params fn_args uri_mac
|
||||
// ^------ route_uri ------^
|
||||
#[derive(Debug)]
|
||||
pub struct InternalUriParams {
|
||||
pub route_uri: Origin<'static>,
|
||||
pub mount_params: Vec<Parameter>,
|
||||
pub path_params: Vec<Parameter>,
|
||||
pub query_params: Vec<Parameter>,
|
||||
pub fn_args: Vec<FnArg>,
|
||||
pub uri_params: UriParams,
|
||||
pub uri_mac: RoutedUri,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FnArg {
|
||||
pub ident: Ident,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
fn err<T, S: AsRef<str>>(span: Span, s: S) -> parse::Result<T> {
|
||||
Err(parse::Error::new(span.into(), s.as_ref()))
|
||||
}
|
||||
|
||||
impl Parse for ArgExpr {
|
||||
|
@ -111,77 +156,139 @@ impl Parse for Arg {
|
|||
}
|
||||
}
|
||||
|
||||
fn err<T, S: AsRef<str>>(span: Span, s: S) -> parse::Result<T> {
|
||||
Err(parse::Error::new(span.into(), s.as_ref()))
|
||||
}
|
||||
|
||||
impl Parse for UriParams {
|
||||
// Parses the mount point, if any, route identifier, and arguments.
|
||||
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
||||
if input.is_empty() {
|
||||
return Err(input.error("call to `uri!` cannot be empty"));
|
||||
}
|
||||
|
||||
// Parse the mount point and suffixing ',', if any.
|
||||
let mount_point = if input.peek(LitStr) {
|
||||
let string = input.parse::<LitStr>()?;
|
||||
let mount_point = Origin::parse_owned(string.value())
|
||||
.map(|m| m.into_normalized())
|
||||
.map_err(|_| {
|
||||
// TODO(proc_macro): use error, add example as a help
|
||||
parse::Error::new(string.span(), "invalid mount point; \
|
||||
mount points must be static, absolute URIs: `/example`")
|
||||
})?;
|
||||
|
||||
if !input.peek(Token![,]) && input.cursor().eof() {
|
||||
return err(string.span(), "unexpected end of input: \
|
||||
expected ',' followed by route path");
|
||||
}
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
Some(mount_point)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Parse the route identifier, which must always exist.
|
||||
let route_path = input.parse::<Path>()?;
|
||||
|
||||
impl Parse for Args {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
// If there are no arguments, finish early.
|
||||
if !input.peek(Token![:]) && input.cursor().eof() {
|
||||
let arguments = Args::Unnamed(Punctuated::new());
|
||||
return Ok(Self { mount_point, route_path, arguments });
|
||||
if input.cursor().eof() {
|
||||
return Ok(Args::Unnamed(Punctuated::new()));
|
||||
}
|
||||
|
||||
// Parse arguments
|
||||
let colon = input.parse::<Token![:]>()?;
|
||||
let arguments: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
||||
|
||||
// A 'colon' was used but there are no arguments.
|
||||
if arguments.is_empty() {
|
||||
return err(colon.span(), "expected argument list after `:`");
|
||||
// Parse arguments. Ensure both types of args were not used at once.
|
||||
let args: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
||||
let mut first_is_named = None;
|
||||
for arg in &args {
|
||||
if let Some(first_is_named) = first_is_named {
|
||||
if first_is_named != arg.is_named() {
|
||||
return err(args.span(), "named and unnamed parameters cannot be mixed");
|
||||
}
|
||||
|
||||
// Ensure that both types of arguments were not used at once.
|
||||
let (mut homogeneous_args, mut prev_named) = (true, None);
|
||||
for arg in &arguments {
|
||||
match prev_named {
|
||||
Some(prev_named) => homogeneous_args = prev_named == arg.is_named(),
|
||||
None => prev_named = Some(arg.is_named()),
|
||||
} else {
|
||||
first_is_named = Some(arg.is_named());
|
||||
}
|
||||
}
|
||||
|
||||
if !homogeneous_args {
|
||||
return err(arguments.span(), "named and unnamed parameters cannot be mixed");
|
||||
}
|
||||
|
||||
// Create the `Args` enum, which properly record one-kind-of-argument-ness.
|
||||
let arguments = match prev_named {
|
||||
Some(true) => Args::Named(arguments),
|
||||
_ => Args::Unnamed(arguments)
|
||||
match first_is_named {
|
||||
Some(true) => Ok(Args::Named(args)),
|
||||
_ => Ok(Args::Unnamed(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for RouteInvocation {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let path = input.parse()?;
|
||||
let args = if input.peek(syn::token::Paren) {
|
||||
let args;
|
||||
syn::parenthesized!(args in input);
|
||||
args.parse()?
|
||||
} else {
|
||||
Args::Unnamed(Punctuated::new())
|
||||
};
|
||||
|
||||
Ok(Self { mount_point, route_path, arguments })
|
||||
Ok(RouteInvocation { path, args })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for UriLit {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
let string = input.parse::<StringLit>()?;
|
||||
let uri = match Uri::parse_any(&string) {
|
||||
Ok(uri) => uri.into_owned(),
|
||||
Err(e) => {
|
||||
let span = string.subspan(e.index() + 1..(e.index() + 2));
|
||||
return err(span, format!("invalid URI: {}", e));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(UriLit(uri, string.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl UriMacro {
|
||||
fn unary(input: ParseStream<'_>) -> parse::Result<Self> {
|
||||
if input.peek(LitStr) {
|
||||
Ok(UriMacro::Literal(input.parse()?))
|
||||
} else {
|
||||
Ok(UriMacro::Routed(RoutedUri {
|
||||
prefix: None,
|
||||
route: input.parse()?,
|
||||
suffix: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
fn binary(prefix: TokenStream, middle: TokenStream) -> parse::Result<Self> {
|
||||
Ok(UriMacro::Routed(RoutedUri {
|
||||
prefix: UriExpr::parse_prefix.parse2(prefix)?,
|
||||
route: syn::parse2(middle)?,
|
||||
suffix: None,
|
||||
}))
|
||||
}
|
||||
|
||||
fn ternary(prefix: TokenStream, mid: TokenStream, suffix: TokenStream) -> parse::Result<Self> {
|
||||
Ok(UriMacro::Routed(RoutedUri {
|
||||
prefix: UriExpr::parse_prefix.parse2(prefix)?,
|
||||
route: syn::parse2(mid)?,
|
||||
suffix: UriExpr::parse_suffix.parse2(suffix)?
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for UriMacro {
|
||||
fn parse(input: ParseStream<'_>) -> parse::Result<Self> {
|
||||
use syn::buffer::Cursor;
|
||||
use parse::{StepCursor, Result};
|
||||
|
||||
fn stream<'c>(cursor: StepCursor<'c, '_>) -> Result<(Option<TokenStream>, Cursor<'c>)> {
|
||||
let mut stream = TokenStream::new();
|
||||
let mut cursor = *cursor;
|
||||
while let Some((tt, next)) = cursor.token_tree() {
|
||||
cursor = next;
|
||||
match tt {
|
||||
TokenTree::Punct(p) if p.as_char() == ',' => break,
|
||||
_ => stream.append(tt)
|
||||
}
|
||||
}
|
||||
|
||||
stream.is_empty()
|
||||
.then(|| Ok((None, cursor)))
|
||||
.unwrap_or_else(|| Ok((Some(stream), cursor)))
|
||||
}
|
||||
|
||||
let mut args = vec![];
|
||||
while let Some(tokens) = input.step(stream)? {
|
||||
args.push(tokens);
|
||||
}
|
||||
|
||||
let (arg_count, mut iter) = (args.len(), args.into_iter());
|
||||
let mut next = || iter.next().unwrap();
|
||||
match arg_count {
|
||||
0 => err(Span::call_site(), "expected at least 1 argument, found none"),
|
||||
1 => UriMacro::unary.parse2(next()),
|
||||
2 => UriMacro::binary(next(), next()),
|
||||
3 => UriMacro::ternary(next(), next(), next()),
|
||||
n => err(iter.skip(3).next().unwrap().span(),
|
||||
format!("expected 1, 2, or 3 arguments, found {}", n))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for RoutedUri {
|
||||
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||
match UriMacro::parse(input)? {
|
||||
UriMacro::Routed(route) => Ok(route),
|
||||
UriMacro::Literal(uri) => err(uri.span(), "expected route URI, found literal")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,12 +304,11 @@ impl Parse for FnArg {
|
|||
|
||||
impl Parse for InternalUriParams {
|
||||
fn parse(input: ParseStream<'_>) -> parse::Result<InternalUriParams> {
|
||||
let route_uri_str = input.parse::<LitStr>()?;
|
||||
let route_uri_str = input.parse::<StringLit>()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
// Validation should always succeed since this macro can only be called
|
||||
// if the route attribute succeeded, implying a valid route URI.
|
||||
let route_uri_str = StringLit::new(route_uri_str.value(), route_uri_str.span());
|
||||
let route_uri = Origin::parse_route(&route_uri_str)
|
||||
.map(|o| o.into_normalized().into_owned())
|
||||
.map_err(|_| input.error("internal error: invalid route URI"))?;
|
||||
|
@ -213,39 +319,28 @@ impl Parse for InternalUriParams {
|
|||
let fn_args = fn_args.into_iter().collect();
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
let uri_params = input.parse::<UriParams>()?;
|
||||
let uri_params = input.parse::<RoutedUri>()?;
|
||||
|
||||
// This span isn't right...we don't have the original span.
|
||||
let span = route_uri_str.subspan(1..route_uri.path().len() + 1);
|
||||
let mount_params = match uri_params.mount_point.as_ref() {
|
||||
Some(mount) => Parameter::parse_many::<uri::Path>(mount.path().as_str(), span)
|
||||
.map(|p| p.expect("internal error: invalid path parameter"))
|
||||
.collect::<Vec<_>>(),
|
||||
None => vec![]
|
||||
};
|
||||
|
||||
let path_params = Parameter::parse_many::<uri::Path>(route_uri.path().as_str(), span)
|
||||
let path_params = Parameter::parse_many::<fmt::Path>(route_uri.path().as_str(), span)
|
||||
.map(|p| p.expect("internal error: invalid path parameter"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let query_params = match route_uri.query() {
|
||||
Some(query) => {
|
||||
let query = route_uri.query();
|
||||
let query_params = query.map(|query| {
|
||||
let i = route_uri.path().len() + 2;
|
||||
let span = route_uri_str.subspan(i..(i + query.len()));
|
||||
Parameter::parse_many::<uri::Query>(query.as_str(), span)
|
||||
Parameter::parse_many::<fmt::Query>(query.as_str(), span)
|
||||
.map(|p| p.expect("internal error: invalid query parameter"))
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
None => vec![]
|
||||
};
|
||||
}).unwrap_or_default();
|
||||
|
||||
Ok(InternalUriParams {
|
||||
route_uri,
|
||||
mount_params,
|
||||
path_params,
|
||||
query_params,
|
||||
fn_args,
|
||||
uri_params
|
||||
uri_mac: uri_params
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -273,7 +368,7 @@ impl InternalUriParams {
|
|||
}
|
||||
|
||||
pub fn validate(&self) -> Validation<'_> {
|
||||
let args = &self.uri_params.arguments;
|
||||
let args = &self.uri_mac.route.args;
|
||||
let all_params = self.dynamic_path_params().chain(self.dynamic_query_params());
|
||||
match args {
|
||||
Args::Unnamed(args) => {
|
||||
|
@ -321,12 +416,12 @@ impl InternalUriParams {
|
|||
}
|
||||
}
|
||||
|
||||
impl UriParams {
|
||||
impl RoutedUri {
|
||||
/// The Span to use when referring to all of the arguments.
|
||||
pub fn args_span(&self) -> Span {
|
||||
match self.arguments.num() {
|
||||
0 => self.route_path.span(),
|
||||
_ => self.arguments.span()
|
||||
match self.route.args.num() {
|
||||
0 => self.route.path.span(),
|
||||
_ => self.route.args.span()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -378,6 +473,104 @@ impl ArgExpr {
|
|||
}
|
||||
}
|
||||
|
||||
fn uri_err<T>(lit: &StringLit, error: Error<'_>) -> parse::Result<T> {
|
||||
let span = lit.subspan(error.index() + 1..(error.index() + 2));
|
||||
err(span, format!("invalid URI: {}", error))
|
||||
}
|
||||
|
||||
impl UriExpr {
|
||||
fn parse_prefix(input: ParseStream<'_>) -> syn::Result<Option<Self>> {
|
||||
if let Ok(_) = input.parse::<Token![_]>() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if !input.peek(LitStr) {
|
||||
return input.parse::<Expr>().map(|e| Some(UriExpr::Expr(e)));
|
||||
}
|
||||
|
||||
let lit = input.parse::<StringLit>()?;
|
||||
let uri = Uri::parse::<Origin<'_>>(&lit)
|
||||
.or_else(|e| Uri::parse::<Absolute<'_>>(&lit).map_err(|e2| (e, e2)))
|
||||
.map_err(|(e1, e2)| lit.starts_with('/').then(|| e1).unwrap_or_else(|| e2))
|
||||
.or_else(|e| uri_err(&lit, e))?;
|
||||
|
||||
if matches!(&uri, Uri::Origin(o) if o.query().is_some())
|
||||
|| matches!(&uri, Uri::Absolute(a) if a.query().is_some())
|
||||
{
|
||||
return err(lit.span(), "URI prefix cannot contain query part");
|
||||
}
|
||||
|
||||
Ok(Some(UriExpr::Uri(UriLit(uri.into_owned(), lit.span()))))
|
||||
}
|
||||
|
||||
fn parse_suffix(input: ParseStream<'_>) -> syn::Result<Option<Self>> {
|
||||
if let Ok(_) = input.parse::<Token![_]>() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if !input.peek(LitStr) {
|
||||
return input.parse::<Expr>().map(|e| Some(UriExpr::Expr(e)));
|
||||
}
|
||||
|
||||
let lit = input.parse::<StringLit>()?;
|
||||
let uri = Reference::parse(&lit).or_else(|e| uri_err(&lit, e))?;
|
||||
if uri.scheme().is_some() || uri.authority().is_some() || !uri.path().is_empty() {
|
||||
return err(lit.span(), "URI suffix must contain only query and/or fragment");
|
||||
}
|
||||
|
||||
// This is a bit of finagling to get the types to match up how we'd
|
||||
// like. A URI like `?foo` will parse as a `Reference`, since that's
|
||||
// what it is. But if we left this as is, we'd convert Origins and
|
||||
// Absolutes to References on suffix appendage when we don't need to.
|
||||
// This is because anything + a Reference _must_ result in a Reference
|
||||
// since the resulting URI could have a fragment. Since here we know
|
||||
// that's not the case, we lie and say it's Absolute since an Absolute
|
||||
// can't contain a fragment, so an Origin + Absolute suffix is still an
|
||||
// Origin, and likewise for an Absolute.
|
||||
let uri = match uri.fragment() {
|
||||
None => {
|
||||
let query = uri.query().map(|q| q.as_str());
|
||||
Uri::Absolute(Absolute::const_new("", None, "", query))
|
||||
}
|
||||
Some(_) => Uri::Reference(uri)
|
||||
};
|
||||
|
||||
Ok(Some(UriExpr::Uri(UriLit(uri.into_owned(), lit.span()))))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for UriLit {
|
||||
type Target = Uri<'static>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for UriLit {
|
||||
fn to_tokens(&self, t: &mut TokenStream) {
|
||||
use http_codegen::*;
|
||||
|
||||
let (uri, span) = (&self.0, self.1);
|
||||
match uri {
|
||||
Uri::Origin(o) => Origin(o, span).to_tokens(t),
|
||||
Uri::Absolute(o) => Absolute(o, span).to_tokens(t),
|
||||
Uri::Authority(o) => Authority(o, span).to_tokens(t),
|
||||
Uri::Reference(r) => Reference(r, span).to_tokens(t),
|
||||
Uri::Asterisk(a) => Asterisk(*a, span).to_tokens(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for UriExpr {
|
||||
fn to_tokens(&self, t: &mut TokenStream) {
|
||||
match self {
|
||||
UriExpr::Uri(uri) => uri.to_tokens(t),
|
||||
UriExpr::Expr(e) => e.to_tokens(t),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for ArgExpr {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
match self {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
|
||||
use devise::{*, ext::SpanDiagnosticExt};
|
||||
use rocket_http::uri;
|
||||
|
||||
use crate::exports::*;
|
||||
use crate::derive::form_field::{FieldExt, VariantExt};
|
||||
use crate::proc_macro2::TokenStream;
|
||||
use crate::http::uri::fmt;
|
||||
|
||||
const NO_EMPTY_FIELDS: &str = "fieldless structs are not supported";
|
||||
const NO_NULLARY: &str = "nullary items are not supported";
|
||||
|
@ -13,10 +13,9 @@ const ONLY_ONE_UNNAMED: &str = "tuple structs or variants must have exactly one
|
|||
const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
|
||||
|
||||
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
|
||||
use crate::http::uri::Query;
|
||||
|
||||
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Query>);
|
||||
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Query>);
|
||||
const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Query>);
|
||||
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_fmt::Query>);
|
||||
|
||||
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
||||
.support(Support::Struct | Support::Enum | Support::Type | Support::Lifetime)
|
||||
|
@ -84,9 +83,9 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
|
|||
Err(diag) => return diag.emit_as_item_tokens()
|
||||
};
|
||||
|
||||
let from_self = from_uri_param::<Query>(input.clone(), quote!(Self));
|
||||
let from_ref = from_uri_param::<Query>(input.clone(), quote!(&'__r Self));
|
||||
let from_mut = from_uri_param::<Query>(input.clone(), quote!(&'__r mut Self));
|
||||
let from_self = from_uri_param::<fmt::Query>(input.clone(), quote!(Self));
|
||||
let from_ref = from_uri_param::<fmt::Query>(input.clone(), quote!(&'__r Self));
|
||||
let from_mut = from_uri_param::<fmt::Query>(input.clone(), quote!(&'__r mut Self));
|
||||
|
||||
let mut ts = TokenStream::from(uri_display);
|
||||
ts.extend(TokenStream::from(from_self));
|
||||
|
@ -97,10 +96,8 @@ pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
|
|||
|
||||
#[allow(non_snake_case)]
|
||||
pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
||||
use crate::http::uri::Path;
|
||||
|
||||
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Path>);
|
||||
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Path>);
|
||||
const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Path>);
|
||||
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_fmt::Path>);
|
||||
|
||||
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
||||
.support(Support::TupleStruct | Support::Type | Support::Lifetime)
|
||||
|
@ -130,9 +127,9 @@ pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
|||
Err(diag) => return diag.emit_as_item_tokens()
|
||||
};
|
||||
|
||||
let from_self = from_uri_param::<Path>(input.clone(), quote!(Self));
|
||||
let from_ref = from_uri_param::<Path>(input.clone(), quote!(&'__r Self));
|
||||
let from_mut = from_uri_param::<Path>(input.clone(), quote!(&'__r mut Self));
|
||||
let from_self = from_uri_param::<fmt::Path>(input.clone(), quote!(Self));
|
||||
let from_ref = from_uri_param::<fmt::Path>(input.clone(), quote!(&'__r Self));
|
||||
let from_mut = from_uri_param::<fmt::Path>(input.clone(), quote!(&'__r mut Self));
|
||||
|
||||
let mut ts = TokenStream::from(uri_display);
|
||||
ts.extend(TokenStream::from(from_self));
|
||||
|
@ -141,10 +138,10 @@ pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
|||
ts.into()
|
||||
}
|
||||
|
||||
fn from_uri_param<P: uri::UriPart>(input: proc_macro::TokenStream, ty: TokenStream) -> TokenStream {
|
||||
fn from_uri_param<P: fmt::Part>(input: proc_macro::TokenStream, ty: TokenStream) -> TokenStream {
|
||||
let part = match P::KIND {
|
||||
uri::Kind::Path => quote!(#_uri::Path),
|
||||
uri::Kind::Query => quote!(#_uri::Query),
|
||||
fmt::Kind::Path => quote!(#_fmt::Path),
|
||||
fmt::Kind::Query => quote!(#_fmt::Query),
|
||||
};
|
||||
|
||||
let ty: syn::Type = syn::parse2(ty).expect("valid type");
|
||||
|
@ -153,10 +150,10 @@ fn from_uri_param<P: uri::UriPart>(input: proc_macro::TokenStream, ty: TokenStre
|
|||
_ => None
|
||||
};
|
||||
|
||||
let param_trait = quote!(impl #gen #_uri::FromUriParam<#part, #ty>);
|
||||
let param_trait = quote!(impl #gen #_fmt::FromUriParam<#part, #ty>);
|
||||
DeriveGenerator::build_for(input, param_trait)
|
||||
.support(Support::All)
|
||||
.type_bound(quote!(#_uri::UriDisplay<#part>))
|
||||
.type_bound(quote!(#_fmt::UriDisplay<#part>))
|
||||
.inner_mapper(MapperBuild::new()
|
||||
.with_output(move |_, _| quote! {
|
||||
type Target = #ty;
|
||||
|
|
|
@ -76,6 +76,7 @@ define_exported_paths! {
|
|||
_form => ::rocket::form::prelude,
|
||||
_http => ::rocket::http,
|
||||
_uri => ::rocket::http::uri,
|
||||
_fmt => ::rocket::http::uri::fmt,
|
||||
_Option => ::std::option::Option,
|
||||
_Result => ::std::result::Result,
|
||||
_Some => ::std::option::Option::Some,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use quote::ToTokens;
|
||||
use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}};
|
||||
|
||||
use crate::proc_macro2::TokenStream;
|
||||
use crate::http;
|
||||
use crate::proc_macro2::{TokenStream, Span};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContentType(pub http::ContentType);
|
||||
|
@ -19,6 +19,21 @@ pub struct Method(pub http::Method);
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct Optional<T>(pub Option<T>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Origin<'a>(pub &'a http::uri::Origin<'a>, pub Span);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Absolute<'a>(pub &'a http::uri::Absolute<'a>, pub Span);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Authority<'a>(pub &'a http::uri::Authority<'a>, pub Span);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Reference<'a>(pub &'a http::uri::Reference<'a>, pub Span);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Asterisk(pub http::uri::Asterisk, pub Span);
|
||||
|
||||
impl FromMeta for Status {
|
||||
fn from_meta(meta: &MetaItem) -> Result<Self> {
|
||||
let num = usize::from_meta(meta)?;
|
||||
|
@ -145,3 +160,71 @@ impl<T: ToTokens> ToTokens for Optional<T> {
|
|||
tokens.extend(opt_tokens);
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Origin<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (origin, span) = (self.0, self.1);
|
||||
let origin = origin.clone().into_normalized();
|
||||
define_spanned_export!(span => _uri);
|
||||
|
||||
let path = origin.path().as_str();
|
||||
let query = Optional(origin.query().map(|q| q.as_str()));
|
||||
tokens.extend(quote_spanned! { span =>
|
||||
#_uri::Origin::const_new(#path, #query)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Absolute<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (absolute, span) = (self.0, self.1);
|
||||
define_spanned_export!(span => _uri);
|
||||
let absolute = absolute.clone().into_normalized();
|
||||
|
||||
let scheme = absolute.scheme();
|
||||
let auth = Optional(absolute.authority().map(|a| Authority(a, span)));
|
||||
let path = absolute.path().as_str();
|
||||
let query = Optional(absolute.query().map(|q| q.as_str()));
|
||||
tokens.extend(quote_spanned! { span =>
|
||||
#_uri::Absolute::const_new(#scheme, #auth, #path, #query)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Authority<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (authority, span) = (self.0, self.1);
|
||||
define_spanned_export!(span => _uri);
|
||||
|
||||
let user_info = Optional(authority.user_info());
|
||||
let host = authority.host();
|
||||
let port = Optional(authority.port());
|
||||
tokens.extend(quote_spanned! { span =>
|
||||
#_uri::Authority::const_new(#user_info, #host, #port)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Reference<'_> {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
let (reference, span) = (self.0, self.1);
|
||||
define_spanned_export!(span => _uri);
|
||||
let reference = reference.clone().into_normalized();
|
||||
|
||||
let scheme = Optional(reference.scheme());
|
||||
let auth = Optional(reference.authority().map(|a| Authority(a, span)));
|
||||
let path = reference.path().as_str();
|
||||
let query = Optional(reference.query().map(|q| q.as_str()));
|
||||
let frag = Optional(reference.fragment().map(|f| f.as_str()));
|
||||
tokens.extend(quote_spanned! { span =>
|
||||
#_uri::Reference::const_new(#scheme, #auth, #path, #query, #frag)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ToTokens for Asterisk {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||
define_spanned_export!(self.1 => _uri);
|
||||
tokens.extend(quote_spanned!(self.1 => #_uri::Asterisk));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1028,77 +1028,221 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
|||
emit!(bang::catchers_macro(input))
|
||||
}
|
||||
|
||||
/// Type-safe, URI-safe generation of an [`Origin`] URI from a route.
|
||||
/// Type-safe, encoding-safe route and non-route URI generation.
|
||||
///
|
||||
/// The `uri!` macro creates a type-safe, URL-safe URI given a route and values
|
||||
/// for the route's URI parameters. The inputs to the macro are the path to a
|
||||
/// route, a colon, and one argument for each dynamic parameter (parameters in
|
||||
/// `<>`) in the route's path and query.
|
||||
/// The `uri!` macro creates type-safe, URL-safe URIs given a route and concrete
|
||||
/// parameters for its URI or a URI string literal.
|
||||
///
|
||||
/// For example, for the following route:
|
||||
/// # String Literal Parsing
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// #[get("/person/<name>?<age>")]
|
||||
/// fn person(name: String, age: Option<u8>) -> String {
|
||||
/// # "".into() /*
|
||||
/// ...
|
||||
/// # */
|
||||
/// }
|
||||
/// Given a string literal as input, `uri!` parses the string using
|
||||
/// [`Uri::parse_any()`] and emits a `'static`, `const` value whose type is one
|
||||
/// of [`Asterisk`], [`Origin`], [`Authority`], [`Absolute`], or [`Reference`],
|
||||
/// reflecting the parsed value. If the type allows normalization, the value is
|
||||
/// normalized before being emitted. Parse errors are caught and emitted at
|
||||
/// compile-time.
|
||||
///
|
||||
/// The grammar for this variant of `uri!` is:
|
||||
///
|
||||
/// ```text
|
||||
/// uri := STRING
|
||||
///
|
||||
/// STRING := an uncooked string literal, as defined by Rust (example: `"/hi"`)
|
||||
/// ```
|
||||
///
|
||||
/// A URI can be created as follows:
|
||||
/// `STRING` is expected to be an undecoded URI of any variant.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// // Values returned from `uri!` are `const` and `'static`.
|
||||
/// const ROOT_CONST: Absolute<'static> = uri!("https://rocket.rs");
|
||||
/// static ROOT_STATIC: Absolute<'static> = uri!("https://rocket.rs?root");
|
||||
///
|
||||
/// // Any variant can be parsed, but beware of ambiguities.
|
||||
/// let asterisk = uri!("*");
|
||||
/// let origin = uri!("/foo/bar/baz");
|
||||
/// let authority = uri!("rocket.rs:443");
|
||||
/// let absolute = uri!("https://rocket.rs:443");
|
||||
/// let reference = uri!("foo?bar#baz");
|
||||
///
|
||||
/// # use rocket::http::uri::{Asterisk, Origin, Authority, Reference};
|
||||
/// # // Ensure we get the types we expect.
|
||||
/// # let asterisk: Asterisk = asterisk;
|
||||
/// # let origin: Origin<'static> = origin;
|
||||
/// # let authority: Authority<'static> = authority;
|
||||
/// # let absolute: Absolute<'static> = absolute;
|
||||
/// # let reference: Reference<'static> = reference;
|
||||
/// ```
|
||||
///
|
||||
/// # Type-Safe Route URIs
|
||||
///
|
||||
/// A URI to a route name `foo` is generated using `uri!(foo(v1, v2, v3))` or
|
||||
/// `uri!(foo(a = v1, b = v2, c = v3))`, where `v1`, `v2`, `v3` are the values
|
||||
/// to fill in for route parameters named `a`, `b`, and `c`. If the named
|
||||
/// parameter sytnax is used (`a = v1`, etc.), parameters can appear in any
|
||||
/// order.
|
||||
///
|
||||
/// More concretely, for the route `person` defined below:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #[get("/person/<name>?<age>")]
|
||||
/// fn person(name: &str, age: Option<u8>) { }
|
||||
/// ```
|
||||
///
|
||||
/// ...a URI can be created as follows:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #
|
||||
/// # #[get("/person/<name>?<age>")]
|
||||
/// # fn person(name: String, age: Option<u8>) { }
|
||||
/// #
|
||||
/// # fn person(name: &str, age: Option<u8>) { }
|
||||
/// // with unnamed parameters, in route path declaration order
|
||||
/// let mike = uri!(person: "Mike Smith", Some(28));
|
||||
/// let mike = uri!(person("Mike Smith", Some(28)));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike%20Smith?age=28");
|
||||
///
|
||||
/// // with named parameters, order irrelevant
|
||||
/// let mike = uri!(person: name = "Mike", age = Some(28));
|
||||
/// let mike = uri!(person: age = Some(28), name = "Mike");
|
||||
/// let mike = uri!(person(name = "Mike", age = Some(28)));
|
||||
/// let mike = uri!(person(age = Some(28), name = "Mike"));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike?age=28");
|
||||
///
|
||||
/// // with a specific mount-point
|
||||
/// let mike = uri!("/api", person: name = "Mike", age = Some(28));
|
||||
/// assert_eq!(mike.to_string(), "/api/person/Mike?age=28");
|
||||
///
|
||||
/// // with unnamed values ignored
|
||||
/// let mike = uri!(person: "Mike", _);
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
///
|
||||
/// // with unnamed values, explicitly `None`.
|
||||
/// let option: Option<u8> = None;
|
||||
/// let mike = uri!(person: "Mike", option);
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
///
|
||||
/// // with named values ignored
|
||||
/// let mike = uri!(person: name = "Mike", age = _);
|
||||
/// let mike = uri!(person("Mike", None::<u8>));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
///
|
||||
/// // with named values, explicitly `None`
|
||||
/// let option: Option<u8> = None;
|
||||
/// let mike = uri!(person: name = "Mike", age = option);
|
||||
/// let mike = uri!(person(name = "Mike", age = None::<u8>));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
/// ```
|
||||
///
|
||||
/// For optional query parameters, those of type `Option` or `Result`, a `_` can
|
||||
/// be used in-place of `None` or `Err`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # #[get("/person/<name>?<age>")]
|
||||
/// # fn person(name: &str, age: Option<u8>) { }
|
||||
/// // with named values ignored
|
||||
/// let mike = uri!(person(name = "Mike", age = _));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
///
|
||||
/// // with named values ignored
|
||||
/// let mike = uri!(person(age = _, name = "Mike"));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
///
|
||||
/// // with unnamed values ignored
|
||||
/// let mike = uri!(person("Mike", _));
|
||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||
/// ```
|
||||
///
|
||||
/// It is a type error to attempt to ignore query parameters that are neither
|
||||
/// `Option` or `Result`. Path parameters can never be ignored. A path parameter
|
||||
/// of type `Option<T>` or `Result<T, E>` must be filled by a value that can
|
||||
/// target a type of `T`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #[get("/person/<name>")]
|
||||
/// fn maybe(name: Option<&str>) { }
|
||||
///
|
||||
/// let bob1 = uri!(maybe(name = "Bob"));
|
||||
/// let bob2 = uri!(maybe("Bob Smith"));
|
||||
/// assert_eq!(bob1.to_string(), "/person/Bob");
|
||||
/// assert_eq!(bob2.to_string(), "/person/Bob%20Smith");
|
||||
///
|
||||
/// #[get("/person/<age>")]
|
||||
/// fn ok(age: Result<u8, &str>) { }
|
||||
///
|
||||
/// let kid1 = uri!(ok(age = 10));
|
||||
/// let kid2 = uri!(ok(12));
|
||||
/// assert_eq!(kid1.to_string(), "/person/10");
|
||||
/// assert_eq!(kid2.to_string(), "/person/12");
|
||||
/// ```
|
||||
///
|
||||
/// Values for ignored route segments can be of any type as long as the type
|
||||
/// implements [`UriDisplay`] for the appropriate URI part. If a route URI
|
||||
/// contains ignored segments, the route URI invocation cannot use named
|
||||
/// arguments.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #[get("/ignore/<_>/<other>")]
|
||||
/// fn ignore(other: &str) { }
|
||||
///
|
||||
/// let bob = uri!(ignore("Bob Hope", "hello"));
|
||||
/// let life = uri!(ignore(42, "cat&dog"));
|
||||
/// assert_eq!(bob.to_string(), "/ignore/Bob%20Hope/hello");
|
||||
/// assert_eq!(life.to_string(), "/ignore/42/cat%26dog");
|
||||
/// ```
|
||||
///
|
||||
/// ## Prefixes and Suffixes
|
||||
///
|
||||
/// A route URI can be be optionally prefixed and/or suffixed by a URI generated
|
||||
/// from a string literal or an arbitrary expression. This takes the form
|
||||
/// `uri!(prefix, foo(v1, v2, v3), suffix)`, where both `prefix` and `suffix`
|
||||
/// are optional, and either `prefix` or `suffix` may be `_` to specify the
|
||||
/// value as empty.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// #[get("/person/<name>?<age>")]
|
||||
/// fn person(name: &str, age: Option<u8>) { }
|
||||
///
|
||||
/// // with a specific mount-point of `/api`.
|
||||
/// let bob = uri!("/api", person("Bob", Some(28)));
|
||||
/// assert_eq!(bob.to_string(), "/api/person/Bob?age=28");
|
||||
///
|
||||
/// // with an absolute URI as a prefix
|
||||
/// let bob = uri!("https://rocket.rs", person("Bob", Some(28)));
|
||||
/// assert_eq!(bob.to_string(), "https://rocket.rs/person/Bob?age=28");
|
||||
///
|
||||
/// // with another absolute URI as a prefix
|
||||
/// let bob = uri!("https://rocket.rs/foo", person("Bob", Some(28)));
|
||||
/// assert_eq!(bob.to_string(), "https://rocket.rs/foo/person/Bob?age=28");
|
||||
///
|
||||
/// // with an expression as a prefix
|
||||
/// let host = uri!("http://bob.me");
|
||||
/// let bob = uri!(host, person("Bob", Some(28)));
|
||||
/// assert_eq!(bob.to_string(), "http://bob.me/person/Bob?age=28");
|
||||
///
|
||||
/// // with a suffix but no prefix
|
||||
/// let bob = uri!(_, person("Bob", Some(28)), "#baz");
|
||||
/// assert_eq!(bob.to_string(), "/person/Bob?age=28#baz");
|
||||
///
|
||||
/// // with both a prefix and suffix
|
||||
/// let bob = uri!("https://rocket.rs/", person("Bob", Some(28)), "#woo");
|
||||
/// assert_eq!(bob.to_string(), "https://rocket.rs/person/Bob?age=28#woo");
|
||||
///
|
||||
/// // with an expression suffix. if the route URI already has a query, the
|
||||
/// // query part is ignored. otherwise it is added.
|
||||
/// let suffix = uri!("?woo#bam");
|
||||
/// let bob = uri!(_, person("Bob", Some(28)), suffix.clone());
|
||||
/// assert_eq!(bob.to_string(), "/person/Bob?age=28#bam");
|
||||
///
|
||||
/// let bob = uri!(_, person("Bob", None::<u8>), suffix.clone());
|
||||
/// assert_eq!(bob.to_string(), "/person/Bob?woo#bam");
|
||||
/// ```
|
||||
///
|
||||
/// ## Grammar
|
||||
///
|
||||
/// The grammar for the `uri!` macro is:
|
||||
/// The grammar for this variant of the `uri!` macro is:
|
||||
///
|
||||
/// ```text
|
||||
/// uri := (mount ',')? PATH (':' params)?
|
||||
/// uri := (prefix ',')? route
|
||||
/// | prefix ',' route ',' suffix
|
||||
///
|
||||
/// prefix := STRING | expr ; `Origin` or `Absolute`
|
||||
/// suffix := STRING | expr ; `Reference` or `Absolute`
|
||||
///
|
||||
/// route := PATH '(' (named | unnamed) ')'
|
||||
///
|
||||
/// named := IDENT = expr (',' named)? ','?
|
||||
/// unnamed := expr (',' unnamed)? ','?
|
||||
///
|
||||
/// mount = STRING
|
||||
/// params := unnamed | named
|
||||
/// unnamed := expr (',' expr)*
|
||||
/// named := IDENT = expr (',' named)?
|
||||
/// expr := EXPR | '_'
|
||||
///
|
||||
/// EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`)
|
||||
|
@ -1107,37 +1251,55 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
|||
/// PATH := a path, as defined by Rust (examples: `route`, `my_mod::route`)
|
||||
/// ```
|
||||
///
|
||||
/// ## Semantics
|
||||
/// ## Dynamic Semantics
|
||||
///
|
||||
/// The `uri!` macro returns an [`Origin`] structure with the URI of the
|
||||
/// supplied route interpolated with the given values. Note that `Origin`
|
||||
/// implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so it can be
|
||||
/// converted into a [`Uri`] using `.into()` as needed.
|
||||
/// The returned value is that of the prefix (minus any query part) concatenated
|
||||
/// with the route URI concatenated with the query (if the route has no query
|
||||
/// part) and fragment parts of the suffix. The route URI is generated by
|
||||
/// interpolating the declared route URI with the URL-safe version of the route
|
||||
/// values in `uri!()`. The generated URI is guaranteed to be URI-safe.
|
||||
///
|
||||
/// A `uri!` invocation only typechecks if the type of every value in the
|
||||
/// invocation matches the type declared for the parameter in the given route,
|
||||
/// after conversion with [`FromUriParam`], or if a value is ignored using `_`
|
||||
/// and the corresponding route type implements [`Ignorable`].
|
||||
/// Each route value is rendered in its appropriate place in the URI using the
|
||||
/// [`UriDisplay`] implementation for the value's type. The `UriDisplay`
|
||||
/// implementation ensures that the rendered value is URL-safe.
|
||||
///
|
||||
/// Each value passed into `uri!` is rendered in its appropriate place in the
|
||||
/// URI using the [`UriDisplay`] implementation for the value's type. The
|
||||
/// `UriDisplay` implementation ensures that the rendered value is URI-safe.
|
||||
/// A `uri!()` invocation allocated at-most once.
|
||||
///
|
||||
/// If a mount-point is provided, the mount-point is prepended to the route's
|
||||
/// URI.
|
||||
/// ## Static Semantics
|
||||
///
|
||||
/// The `uri!` macro returns one of [`Origin`], [`Absolute`], or [`Reference`],
|
||||
/// depending on the types of the prefix and suffix, if any. The table below
|
||||
/// specifies all combinations:
|
||||
///
|
||||
/// | Prefix | Suffix | Output |
|
||||
/// |------------|-------------|-------------|
|
||||
/// | None | None | `Origin` |
|
||||
/// | None | `Absolute` | `Origin` |
|
||||
/// | None | `Reference` | `Reference` |
|
||||
/// | `Origin` | None | `Origin` |
|
||||
/// | `Origin` | `Absolute` | `Origin` |
|
||||
/// | `Origin` | `Reference` | `Reference` |
|
||||
/// | `Absolute` | None | `Absolute` |
|
||||
/// | `Absolute` | `Absolute` | `Absolute` |
|
||||
/// | `Absolute` | `Reference` | `Reference` |
|
||||
///
|
||||
/// A `uri!` invocation only typechecks if the type of every route URI value in
|
||||
/// the invocation matches the type declared for the parameter in the given
|
||||
/// route, after conversion with [`FromUriParam`], or if a value is ignored
|
||||
/// using `_` and the corresponding route type implements [`Ignorable`].
|
||||
///
|
||||
/// ### Conversion
|
||||
///
|
||||
/// The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
||||
/// each value passed to `uri!`. If a `FromUriParam<P, S>` implementation exists
|
||||
/// for a type `T` for part URI part `P`, then a value of type `S` can be used
|
||||
/// in `uri!` macro for a route URI parameter declared with a type of `T` in
|
||||
/// part `P`. For example, the following implementation, provided by Rocket,
|
||||
/// each value passed to `uri!`. If a `FromUriParam<P, S> for T` implementation
|
||||
/// exists for a type `T` for part URI part `P`, then a value of type `S` can be
|
||||
/// used in `uri!` macro for a route URI parameter declared with a type of `T`
|
||||
/// in part `P`. For example, the following implementation, provided by Rocket,
|
||||
/// allows an `&str` to be used in a `uri!` invocation for route URI parameters
|
||||
/// declared as `String`:
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// impl<P: UriPart, 'a> FromUriParam<P, &'a str> for String { .. }
|
||||
/// impl<P: Part, 'a> FromUriParam<P, &'a str> for String { .. }
|
||||
/// ```
|
||||
///
|
||||
/// ### Ignorables
|
||||
|
@ -1149,6 +1311,10 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
|||
///
|
||||
/// [`Uri`]: ../rocket/http/uri/enum.Uri.html
|
||||
/// [`Origin`]: ../rocket/http/uri/struct.Origin.html
|
||||
/// [`Asterisk`]: ../rocket/http/uri/struct.Asterisk.html
|
||||
/// [`Authority`]: ../rocket/http/uri/struct.Authority.html
|
||||
/// [`Absolute`]: ../rocket/http/uri/struct.Absolute.html
|
||||
/// [`Reference`]: ../rocket/http/uri/struct.Reference.html
|
||||
/// [`FromUriParam`]: ../rocket/http/uri/trait.FromUriParam.html
|
||||
/// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html
|
||||
/// [`Ignorable`]: ../rocket/http/uri/trait.Ignorable.html
|
||||
|
|
|
@ -70,6 +70,13 @@ impl StringLit {
|
|||
}
|
||||
}
|
||||
|
||||
impl crate::syn::parse::Parse for StringLit {
|
||||
fn parse(input: devise::syn::parse::ParseStream<'_>) -> devise::syn::Result<Self> {
|
||||
let lit = input.parse::<crate::syn::LitStr>()?;
|
||||
Ok(StringLit::new(lit.value(), lit.span()))
|
||||
}
|
||||
}
|
||||
|
||||
impl devise::FromMeta for StringLit {
|
||||
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
||||
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
|
||||
|
|
|
@ -30,14 +30,14 @@ fn test_raw_ident() {
|
|||
let response = client.get("/example?type=1").dispatch();
|
||||
assert_eq!(response.into_string().unwrap(), "example is 1");
|
||||
|
||||
let uri_named = uri!(get: r#enum = "test_named", r#type = 1);
|
||||
let uri_named = uri!(get(r#enum = "test_named", r#type = 1));
|
||||
assert_eq!(uri_named.to_string(), "/test_named?type=1");
|
||||
|
||||
let uri_unnamed = uri!(get: "test_unnamed", 2);
|
||||
let uri_unnamed = uri!(get("test_unnamed", 2));
|
||||
assert_eq!(uri_unnamed.to_string(), "/test_unnamed?type=2");
|
||||
|
||||
let uri_raws = uri!(swap: r#raw = "1", r#bare = "2");
|
||||
let uri_raws = uri!(swap(r#raw = "1", r#bare = "2"));
|
||||
assert_eq!(uri_raws.to_string(), "/swap/1/2");
|
||||
let uri_bare = uri!(swap: raw = "1", bare = "2");
|
||||
let uri_bare = uri!(swap(raw = "1", bare = "2"));
|
||||
assert_eq!(uri_bare.to_string(), "/swap/1/2");
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use rocket::request::Request;
|
|||
use rocket::http::ext::Normalize;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::data::{self, Data, FromData};
|
||||
use rocket::http::{Status, RawStr, ContentType};
|
||||
use rocket::http::{Status, RawStr, ContentType, uri::fmt::Path};
|
||||
|
||||
// Use all of the code generation available at once.
|
||||
|
||||
|
@ -48,7 +48,7 @@ fn post1(
|
|||
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||
sky, name, a, query.field, path.normalized_str(), simple.0);
|
||||
|
||||
let uri = uri!(post1: a, name, path, sky, query);
|
||||
let uri = uri!(post1(a, name, path, sky, query));
|
||||
|
||||
format!("({}) ({})", string, uri.to_string())
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ fn post2(
|
|||
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||
sky, name, a, query.field, path.normalized_str(), simple.0);
|
||||
|
||||
let uri = uri!(post2: a, name, path, sky, query);
|
||||
let uri = uri!(post2(a, name, path, sky, query));
|
||||
|
||||
format!("({}) ({})", string, uri.to_string())
|
||||
}
|
||||
|
@ -307,7 +307,7 @@ struct PathString(String);
|
|||
impl FromSegments<'_> for PathString {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
fn from_segments(segments: Segments<'_>) -> Result<Self, Self::Error> {
|
||||
fn from_segments(segments: Segments<'_, Path>) -> Result<Self, Self::Error> {
|
||||
Ok(PathString(segments.collect::<Vec<_>>().join("/")))
|
||||
}
|
||||
|
||||
|
|
|
@ -5,16 +5,17 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use rocket::http::CookieJar;
|
||||
use rocket::http::uri::{FromUriParam, Query};
|
||||
use rocket::http::uri::fmt::{FromUriParam, Query};
|
||||
use rocket::form::{Form, error::{Errors, ErrorKind}};
|
||||
|
||||
macro_rules! assert_uri_eq {
|
||||
($($uri:expr => $expected:expr,)+) => {
|
||||
$(
|
||||
let actual = $uri;
|
||||
let expected = rocket::http::uri::Origin::parse($expected).expect("valid origin URI");
|
||||
let expected = rocket::http::uri::Uri::parse_any($expected).expect("valid URI");
|
||||
if actual != expected {
|
||||
panic!("URI mismatch: got {}, expected {}", actual, expected);
|
||||
panic!("URI mismatch: got {}, expected {}\nGot) {:?}\nExpected) {:?}",
|
||||
actual, expected, actual, expected);
|
||||
}
|
||||
)+
|
||||
};
|
||||
|
@ -41,6 +42,9 @@ struct Second {
|
|||
nickname: String,
|
||||
}
|
||||
|
||||
#[post("/")]
|
||||
fn index() { }
|
||||
|
||||
#[post("/<id>")]
|
||||
fn simple(id: i32) { }
|
||||
|
||||
|
@ -96,187 +100,244 @@ fn guarded_segments(cookies: &CookieJar<'_>, path: PathBuf, id: usize) { }
|
|||
#[test]
|
||||
fn check_simple_unnamed() {
|
||||
assert_uri_eq! {
|
||||
uri!(simple: 100) => "/100",
|
||||
uri!(simple: -23) => "/-23",
|
||||
uri!(unused_param: 1, 2) => "/1/2",
|
||||
uri!(simple(100)) => "/100",
|
||||
uri!(simple(-23)) => "/-23",
|
||||
uri!(unused_param(1, 2)) => "/1/2",
|
||||
}
|
||||
|
||||
// The "flipped" test ensures that the order of parameters depends on the
|
||||
// route's URI, not on the order in the function signature.
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: 100, "hello".to_string()) => "/100/hello",
|
||||
uri!(simple2: 1349, "hey".to_string()) => "/1349/hey",
|
||||
uri!(simple2_flipped: 100, "hello".to_string()) => "/100/hello",
|
||||
uri!(simple2(100, "hello".to_string())) => "/100/hello",
|
||||
uri!(simple2(1349, "hey".to_string())) => "/1349/hey",
|
||||
uri!(simple2_flipped(100, "hello".to_string())) => "/100/hello",
|
||||
}
|
||||
|
||||
// Ensure that `.from_uri_param()` is called.
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: 100, "hello") => "/100/hello",
|
||||
uri!(simple2_flipped: 1349, "hey") => "/1349/hey",
|
||||
uri!(simple2(100, "hello")) => "/100/hello",
|
||||
uri!(simple2_flipped(1349, "hey")) => "/1349/hey",
|
||||
}
|
||||
|
||||
// Ensure that the `UriDisplay` trait is being used.
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: 100, "hello there") => "/100/hello%20there",
|
||||
uri!(simple2_flipped: 100, "hello there") => "/100/hello%20there",
|
||||
uri!(simple2(100, "hello there")) => "/100/hello%20there",
|
||||
uri!(simple2_flipped(100, "hello there")) => "/100/hello%20there",
|
||||
}
|
||||
|
||||
// Ensure that query parameters are handled properly.
|
||||
assert_uri_eq! {
|
||||
uri!(simple3: 100) => "/?id=100",
|
||||
uri!(simple3: 1349) => "/?id=1349",
|
||||
uri!(simple4: 100, "bob") => "/?id=100&name=bob",
|
||||
uri!(simple4: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
|
||||
uri!(simple4: -2, "@M+s&OU=") => "/?id=-2&name=@M%2Bs%26OU%3D",
|
||||
uri!(simple4_flipped: 100, "bob") => "/?id=100&name=bob",
|
||||
uri!(simple4_flipped: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
|
||||
uri!(simple3(100)) => "/?id=100",
|
||||
uri!(simple3(1349)) => "/?id=1349",
|
||||
uri!(simple4(100, "bob")) => "/?id=100&name=bob",
|
||||
uri!(simple4(1349, "Bob Anderson")) => "/?id=1349&name=Bob%20Anderson",
|
||||
uri!(simple4(-2, "@M+s&OU=")) => "/?id=-2&name=@M%2Bs%26OU%3D",
|
||||
uri!(simple4_flipped(100, "bob")) => "/?id=100&name=bob",
|
||||
uri!(simple4_flipped(1349, "Bob Anderson")) => "/?id=1349&name=Bob%20Anderson",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_simple_named() {
|
||||
assert_uri_eq! {
|
||||
uri!(simple: id = 100) => "/100",
|
||||
uri!(simple: id = -23) => "/-23",
|
||||
uri!(unused_param: used = 1, _unused = 2) => "/1/2",
|
||||
uri!(simple(id = 100)) => "/100",
|
||||
uri!(simple(id = -23)) => "/-23",
|
||||
uri!(unused_param(used = 1, _unused = 2)) => "/1/2",
|
||||
}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: id = 100, name = "hello".to_string()) => "/100/hello",
|
||||
uri!(simple2: name = "hi".to_string(), id = 123) => "/123/hi",
|
||||
uri!(simple2_flipped: id = 1349, name = "hey".to_string()) => "/1349/hey",
|
||||
uri!(simple2_flipped: name = "hello".to_string(), id = 100) => "/100/hello",
|
||||
uri!(simple2(id = 100, name = "hello".to_string())) => "/100/hello",
|
||||
uri!(simple2(name = "hi".to_string(), id = 123)) => "/123/hi",
|
||||
uri!(simple2_flipped(id = 1349, name = "hey".to_string())) => "/1349/hey",
|
||||
uri!(simple2_flipped(name = "hello".to_string(), id = 100)) => "/100/hello",
|
||||
}
|
||||
|
||||
// Ensure that `.from_uri_param()` is called.
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: id = 100, name = "hello") => "/100/hello",
|
||||
uri!(simple2: id = 100, name = "hi") => "/100/hi",
|
||||
uri!(simple2: id = 1349, name = "hey") => "/1349/hey",
|
||||
uri!(simple2: name = "hello", id = 100) => "/100/hello",
|
||||
uri!(simple2: name = "hi", id = 100) => "/100/hi",
|
||||
uri!(simple2_flipped: id = 1349, name = "hey") => "/1349/hey",
|
||||
uri!(simple2(id = 100, name = "hello")) => "/100/hello",
|
||||
uri!(simple2(id = 100, name = "hi")) => "/100/hi",
|
||||
uri!(simple2(id = 1349, name = "hey")) => "/1349/hey",
|
||||
uri!(simple2(name = "hello", id = 100)) => "/100/hello",
|
||||
uri!(simple2(name = "hi", id = 100)) => "/100/hi",
|
||||
uri!(simple2_flipped(id = 1349, name = "hey")) => "/1349/hey",
|
||||
}
|
||||
|
||||
// Ensure that the `UriDisplay` trait is being used.
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: id = 100, name = "hello there") => "/100/hello%20there",
|
||||
uri!(simple2: name = "hello there", id = 100) => "/100/hello%20there",
|
||||
uri!(simple2_flipped: id = 100, name = "hello there") => "/100/hello%20there",
|
||||
uri!(simple2_flipped: name = "hello there", id = 100) => "/100/hello%20there",
|
||||
uri!(simple2(id = 100, name = "hello there")) => "/100/hello%20there",
|
||||
uri!(simple2(name = "hello there", id = 100)) => "/100/hello%20there",
|
||||
uri!(simple2_flipped(id = 100, name = "hello there")) => "/100/hello%20there",
|
||||
uri!(simple2_flipped(name = "hello there", id = 100)) => "/100/hello%20there",
|
||||
}
|
||||
|
||||
// Ensure that query parameters are handled properly.
|
||||
assert_uri_eq! {
|
||||
uri!(simple3: id = 100) => "/?id=100",
|
||||
uri!(simple3: id = 1349) => "/?id=1349",
|
||||
uri!(simple4: id = 100, name = "bob") => "/?id=100&name=bob",
|
||||
uri!(simple4: id = 1349, name = "Bob A") => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4: name = "Bob A", id = 1349) => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4_flipped: id = 1349, name = "Bob A") => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4_flipped: name = "Bob A", id = 1349) => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple3(id = 100)) => "/?id=100",
|
||||
uri!(simple3(id = 1349)) => "/?id=1349",
|
||||
uri!(simple4(id = 100, name = "bob")) => "/?id=100&name=bob",
|
||||
uri!(simple4(id = 1349, name = "Bob A")) => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4(name = "Bob A", id = 1349)) => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4_flipped(id = 1349, name = "Bob A")) => "/?id=1349&name=Bob%20A",
|
||||
uri!(simple4_flipped(name = "Bob A", id = 1349)) => "/?id=1349&name=Bob%20A",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_mount_point() {
|
||||
fn check_route_prefix_suffix() {
|
||||
assert_uri_eq! {
|
||||
uri!("/mount", simple: 100) => "/mount/100",
|
||||
uri!("/mount", simple: id = 23) => "/mount/23",
|
||||
uri!("/another", simple: 100) => "/another/100",
|
||||
uri!("/another", simple: id = 23) => "/another/23",
|
||||
uri!(index) => "/",
|
||||
uri!("/", index) => "/",
|
||||
uri!("/hi", index) => "/hi",
|
||||
uri!("/", simple3(10)) => "/?id=10",
|
||||
uri!("/hi", simple3(11)) => "/hi?id=11",
|
||||
uri!("/mount", simple(100)) => "/mount/100",
|
||||
uri!("/mount", simple(id = 23)) => "/mount/23",
|
||||
uri!("/another", simple(100)) => "/another/100",
|
||||
uri!("/another", simple(id = 23)) => "/another/23",
|
||||
}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!("/a", simple2: 100, "hey") => "/a/100/hey",
|
||||
uri!("/b", simple2: id = 23, name = "hey") => "/b/23/hey",
|
||||
uri!("http://rocket.rs", index) => "http://rocket.rs",
|
||||
uri!("http://rocket.rs/", index) => "http://rocket.rs",
|
||||
uri!("http://rocket.rs", index) => "http://rocket.rs",
|
||||
uri!("http://", index) => "http://",
|
||||
uri!("ftp:", index) => "ftp:/",
|
||||
}
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!("http://rocket.rs", index, "?foo") => "http://rocket.rs?foo",
|
||||
uri!("http://rocket.rs/", index, "#bar") => "http://rocket.rs#bar",
|
||||
uri!("http://rocket.rs", index, "?bar#baz") => "http://rocket.rs?bar#baz",
|
||||
uri!("http://rocket.rs/", index, "?bar#baz") => "http://rocket.rs?bar#baz",
|
||||
uri!("http://", index, "?foo") => "http://?foo",
|
||||
uri!("http://rocket.rs", simple3(id = 100), "?foo") => "http://rocket.rs?id=100",
|
||||
uri!("http://rocket.rs", simple3(id = 100), "?foo#bar") => "http://rocket.rs?id=100#bar",
|
||||
uri!(_, simple3(id = 100), "?foo#bar") => "/?id=100#bar",
|
||||
}
|
||||
|
||||
let dyn_origin = uri!("/a/b/c");
|
||||
let dyn_origin2 = uri!("/a/b/c?foo-bar");
|
||||
assert_uri_eq! {
|
||||
uri!(dyn_origin.clone(), index) => "/a/b/c",
|
||||
uri!(dyn_origin2.clone(), index) => "/a/b/c",
|
||||
uri!(dyn_origin.clone(), simple3(10)) => "/a/b/c?id=10",
|
||||
uri!(dyn_origin2.clone(), simple3(10)) => "/a/b/c?id=10",
|
||||
uri!(dyn_origin.clone(), simple(100)) => "/a/b/c/100",
|
||||
uri!(dyn_origin2.clone(), simple(100)) => "/a/b/c/100",
|
||||
uri!(dyn_origin.clone(), simple2(100, "hey")) => "/a/b/c/100/hey",
|
||||
uri!(dyn_origin2.clone(), simple2(100, "hey")) => "/a/b/c/100/hey",
|
||||
uri!(dyn_origin.clone(), simple2(id = 23, name = "hey")) => "/a/b/c/23/hey",
|
||||
uri!(dyn_origin2.clone(), simple2(id = 23, name = "hey")) => "/a/b/c/23/hey",
|
||||
}
|
||||
|
||||
let dyn_absolute = uri!("http://rocket.rs");
|
||||
assert_uri_eq! {
|
||||
uri!(dyn_absolute.clone(), index) => "http://rocket.rs",
|
||||
uri!(uri!("http://rocket.rs/a/b"), index) => "http://rocket.rs/a/b",
|
||||
}
|
||||
|
||||
let dyn_abs = uri!("http://rocket.rs?foo");
|
||||
assert_uri_eq! {
|
||||
uri!(_, index, dyn_abs.clone()) => "/?foo",
|
||||
uri!("http://rocket.rs/", index, dyn_abs.clone()) => "http://rocket.rs?foo",
|
||||
uri!("http://rocket.rs", index, dyn_abs.clone()) => "http://rocket.rs?foo",
|
||||
uri!("http://", index, dyn_abs.clone()) => "http://?foo",
|
||||
uri!(_, simple3(id = 123), dyn_abs) => "/?id=123",
|
||||
}
|
||||
|
||||
let dyn_ref = uri!("?foo#bar");
|
||||
assert_uri_eq! {
|
||||
uri!(_, index, dyn_ref.clone()) => "/?foo#bar",
|
||||
uri!("http://rocket.rs/", index, dyn_ref.clone()) => "http://rocket.rs?foo#bar",
|
||||
uri!("http://rocket.rs", index, dyn_ref.clone()) => "http://rocket.rs?foo#bar",
|
||||
uri!("http://", index, dyn_ref.clone()) => "http://?foo#bar",
|
||||
uri!(_, simple3(id = 123), dyn_ref) => "/?id=123#bar",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_guards_ignored() {
|
||||
assert_uri_eq! {
|
||||
uri!("/mount", guard_1: 100) => "/mount/100",
|
||||
uri!("/mount", guard_2: 2938, "boo") => "/mount/2938/boo",
|
||||
uri!("/mount", guard_3: 340, "Bob") => "/mount/a/340/hi/Bob/hey",
|
||||
uri!(guard_1: 100) => "/100",
|
||||
uri!(guard_2: 2938, "boo") => "/2938/boo",
|
||||
uri!(guard_3: 340, "Bob") => "/a/340/hi/Bob/hey",
|
||||
uri!("/mount", guard_1: id = 100) => "/mount/100",
|
||||
uri!("/mount", guard_2: id = 2938, name = "boo") => "/mount/2938/boo",
|
||||
uri!("/mount", guard_3: id = 340, name = "Bob") => "/mount/a/340/hi/Bob/hey",
|
||||
uri!(guard_1: id = 100) => "/100",
|
||||
uri!(guard_2: name = "boo", id = 2938) => "/2938/boo",
|
||||
uri!(guard_3: name = "Bob", id = 340) => "/a/340/hi/Bob/hey",
|
||||
uri!("/mount", guard_1(100)) => "/mount/100",
|
||||
uri!("/mount", guard_2(2938, "boo")) => "/mount/2938/boo",
|
||||
uri!("/mount", guard_3(340, "Bob")) => "/mount/a/340/hi/Bob/hey",
|
||||
uri!(guard_1(100)) => "/100",
|
||||
uri!(guard_2(2938, "boo")) => "/2938/boo",
|
||||
uri!(guard_3(340, "Bob")) => "/a/340/hi/Bob/hey",
|
||||
uri!("/mount", guard_1(id = 100)) => "/mount/100",
|
||||
uri!("/mount", guard_2(id = 2938, name = "boo")) => "/mount/2938/boo",
|
||||
uri!("/mount", guard_3(id = 340, name = "Bob")) => "/mount/a/340/hi/Bob/hey",
|
||||
uri!(guard_1(id = 100)) => "/100",
|
||||
uri!(guard_2(name = "boo", id = 2938)) => "/2938/boo",
|
||||
uri!(guard_3(name = "Bob", id = 340)) => "/a/340/hi/Bob/hey",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_with_segments() {
|
||||
assert_uri_eq! {
|
||||
uri!(segments: PathBuf::from("one/two/three")) => "/a/one/two/three",
|
||||
uri!(segments: path = PathBuf::from("one/two/three")) => "/a/one/two/three",
|
||||
uri!("/c", segments: PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o",
|
||||
uri!("/c", segments: path = PathBuf::from("one/tw o/")) => "/c/a/one/tw%20o",
|
||||
uri!(segments: PathBuf::from("one/ tw?o/")) => "/a/one/%20tw%3Fo",
|
||||
uri!(param_and_segments: 10, PathBuf::from("a/b")) => "/a/10/then/a/b",
|
||||
uri!(param_and_segments: id = 10, path = PathBuf::from("a/b"))
|
||||
=> "/a/10/then/a/b",
|
||||
uri!(guarded_segments: 10, PathBuf::from("a/b")) => "/a/10/then/a/b",
|
||||
uri!(guarded_segments: id = 10, path = PathBuf::from("a/b"))
|
||||
=> "/a/10/then/a/b",
|
||||
uri!(segments(PathBuf::from("one/two/three"))) => "/a/one/two/three",
|
||||
uri!(segments(path = PathBuf::from("one/two/three"))) => "/a/one/two/three",
|
||||
uri!("/c", segments(PathBuf::from("one/tw o/"))) => "/c/a/one/tw%20o",
|
||||
uri!("/c", segments(path = PathBuf::from("one/tw o/"))) => "/c/a/one/tw%20o",
|
||||
uri!(segments(PathBuf::from("one/ tw?o/"))) => "/a/one/%20tw%3Fo",
|
||||
uri!(param_and_segments(10, PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||
uri!(param_and_segments(id = 10, path = PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||
uri!(guarded_segments(10, PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||
uri!(guarded_segments(id = 10, path = PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||
}
|
||||
|
||||
// Now check the `from_uri_param()` conversions for `PathBuf`.
|
||||
assert_uri_eq! {
|
||||
uri!(segments: "one/two/three") => "/a/one/two/three",
|
||||
uri!("/oh", segments: path = "one/two/three") => "/oh/a/one/two/three",
|
||||
uri!(segments: "one/ tw?o/") => "/a/one/%20tw%3Fo",
|
||||
uri!(param_and_segments: id = 10, path = "a/b") => "/a/10/then/a/b",
|
||||
uri!(guarded_segments: 10, "a/b") => "/a/10/then/a/b",
|
||||
uri!(guarded_segments: id = 10, path = "a/b") => "/a/10/then/a/b",
|
||||
uri!(segments("one/two/three")) => "/a/one/two/three",
|
||||
uri!("/", segments(path = "one/two/three")) => "/a/one/two/three",
|
||||
uri!("/oh", segments(path = "one/two/three")) => "/oh/a/one/two/three",
|
||||
uri!(segments("one/ tw?o/")) => "/a/one/%20tw%3Fo",
|
||||
uri!(param_and_segments(id = 10, path = "a/b")) => "/a/10/then/a/b",
|
||||
uri!(guarded_segments(10, "a/b")) => "/a/10/then/a/b",
|
||||
uri!(guarded_segments(id = 10, path = "a/b")) => "/a/10/then/a/b",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_complex() {
|
||||
assert_uri_eq! {
|
||||
uri!(complex: "no idea", 10, "high", ("A B C", "a c")) =>
|
||||
uri!(complex("no idea", 10, "high", ("A B C", "a c"))) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: "Bob", 248, "?", User { name: "Robert".into(), nickname: "Bob".into() }) =>
|
||||
uri!(complex("Bob", 248, "?", User { name: "Robert".into(), nickname: "Bob".into() })) =>
|
||||
"/name/Bob?foo=248&type=10&type=%3F&name=Robert&nickname=Bob",
|
||||
uri!(complex: "Bob", 248, "a a", &User { name: "Robert".into(), nickname: "B".into() }) =>
|
||||
uri!(complex("Bob", 248, "a a", &User { name: "Robert".into(), nickname: "B".into() })) =>
|
||||
"/name/Bob?foo=248&type=10&type=a%20a&name=Robert&nickname=B",
|
||||
uri!(complex: "no idea", 248, "", &User { name: "A B".into(), nickname: "A".into() }) =>
|
||||
uri!(complex("no idea", 248, "", &User { name: "A B".into(), nickname: "A".into() })) =>
|
||||
"/name/no%20idea?foo=248&type=10&type=&name=A%20B&nickname=A",
|
||||
uri!(complex: "hi", 3, "b", &User { name: "A B C".into(), nickname: "a b".into() }) =>
|
||||
uri!(complex("hi", 3, "b", &User { name: "A B C".into(), nickname: "a b".into() })) =>
|
||||
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
|
||||
uri!(complex: name = "no idea", foo = 10, r#type = "high", query = ("A B C", "a c")) =>
|
||||
uri!(complex(name = "no idea", foo = 10, r#type = "high", query = ("A B C", "a c"))) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: foo = 10, name = "no idea", r#type = "high", query = ("A B C", "a c")) =>
|
||||
uri!(complex(foo = 10, name = "no idea", r#type = "high", query = ("A B C", "a c"))) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high", ) =>
|
||||
uri!(complex(query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high", )) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high") =>
|
||||
uri!(complex(query = ("A B C", "a c"), foo = 10, name = "no idea", r#type = "high")) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: query = *&("A B C", "a c"), foo = 10, name = "no idea", r#type = "high") =>
|
||||
uri!(complex(query = *&("A B C", "a c"), foo = 10, name = "no idea", r#type = "high")) =>
|
||||
"/name/no%20idea?foo=10&type=10&type=high&name=A%20B%20C&nickname=a%20c",
|
||||
uri!(complex: foo = 3, name = "hi", r#type = "b",
|
||||
query = &User { name: "A B C".into(), nickname: "a b".into() }) =>
|
||||
uri!(complex(foo = 3, name = "hi", r#type = "b",
|
||||
query = &User { name: "A B C".into(), nickname: "a b".into() })) =>
|
||||
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
|
||||
uri!(complex: query = &User { name: "A B C".into(), nickname: "a b".into() },
|
||||
foo = 3, name = "hi", r#type = "b") =>
|
||||
uri!(complex(query = &User { name: "A B C".into(), nickname: "a b".into() },
|
||||
foo = 3, name = "hi", r#type = "b")) =>
|
||||
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
|
||||
}
|
||||
|
||||
// Ensure variables are correctly processed.
|
||||
let user = User { name: "Robert".into(), nickname: "Bob".into() };
|
||||
assert_uri_eq! {
|
||||
uri!(complex: "complex", 0, "high", &user) =>
|
||||
uri!(complex("complex", 0, "high", &user)) =>
|
||||
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
|
||||
uri!(complex: "complex", 0, "high", &user) =>
|
||||
uri!(complex("complex", 0, "high", &user)) =>
|
||||
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
|
||||
uri!(complex: "complex", 0, "high", user) =>
|
||||
uri!(complex("complex", 0, "high", user)) =>
|
||||
"/name/complex?foo=0&type=10&type=high&name=Robert&nickname=Bob",
|
||||
}
|
||||
}
|
||||
|
@ -290,42 +351,42 @@ fn check_location_promotion() {
|
|||
let s2 = S2 { name: "Bob".into() };
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: 1, &S1("A".into()).0) => "/1/A",
|
||||
uri!(simple2: 1, &mut S1("A".into()).0) => "/1/A",
|
||||
uri!(simple2: 1, S1("A".into()).0) => "/1/A",
|
||||
uri!(simple2: 1, &S2 { name: "A".into() }.name) => "/1/A",
|
||||
uri!(simple2: 1, &mut S2 { name: "A".into() }.name) => "/1/A",
|
||||
uri!(simple2: 1, S2 { name: "A".into() }.name) => "/1/A",
|
||||
uri!(simple2: 1, &s1.0) => "/1/Bob",
|
||||
uri!(simple2: 1, &s2.name) => "/1/Bob",
|
||||
uri!(simple2: 2, &s1.0) => "/2/Bob",
|
||||
uri!(simple2: 2, &s2.name) => "/2/Bob",
|
||||
uri!(simple2: 2, s1.0) => "/2/Bob",
|
||||
uri!(simple2: 2, s2.name) => "/2/Bob",
|
||||
uri!(simple2(1, &S1("A".into()).0)) => "/1/A",
|
||||
uri!(simple2(1, &mut S1("A".into()).0)) => "/1/A",
|
||||
uri!(simple2(1, S1("A".into()).0)) => "/1/A",
|
||||
uri!(simple2(1, &S2 { name: "A".into() }.name)) => "/1/A",
|
||||
uri!(simple2(1, &mut S2 { name: "A".into() }.name)) => "/1/A",
|
||||
uri!(simple2(1, S2 { name: "A".into() }.name)) => "/1/A",
|
||||
uri!(simple2(1, &s1.0)) => "/1/Bob",
|
||||
uri!(simple2(1, &s2.name)) => "/1/Bob",
|
||||
uri!(simple2(2, &s1.0)) => "/2/Bob",
|
||||
uri!(simple2(2, &s2.name)) => "/2/Bob",
|
||||
uri!(simple2(2, s1.0)) => "/2/Bob",
|
||||
uri!(simple2(2, s2.name)) => "/2/Bob",
|
||||
}
|
||||
|
||||
let mut s1 = S1("Bob".into());
|
||||
let mut s2 = S2 { name: "Bob".into() };
|
||||
assert_uri_eq! {
|
||||
uri!(simple2: 1, &mut S1("A".into()).0) => "/1/A",
|
||||
uri!(simple2: 1, S1("A".into()).0) => "/1/A",
|
||||
uri!(simple2: 1, &mut S2 { name: "A".into() }.name) => "/1/A",
|
||||
uri!(simple2: 1, S2 { name: "A".into() }.name) => "/1/A",
|
||||
uri!(simple2: 1, &mut s1.0) => "/1/Bob",
|
||||
uri!(simple2: 1, &mut s2.name) => "/1/Bob",
|
||||
uri!(simple2: 2, &mut s1.0) => "/2/Bob",
|
||||
uri!(simple2: 2, &mut s2.name) => "/2/Bob",
|
||||
uri!(simple2: 2, s1.0) => "/2/Bob",
|
||||
uri!(simple2: 2, s2.name) => "/2/Bob",
|
||||
uri!(simple2(1, &mut S1("A".into()).0)) => "/1/A",
|
||||
uri!(simple2(1, S1("A".into()).0)) => "/1/A",
|
||||
uri!(simple2(1, &mut S2 { name: "A".into() }.name)) => "/1/A",
|
||||
uri!(simple2(1, S2 { name: "A".into() }.name)) => "/1/A",
|
||||
uri!(simple2(1, &mut s1.0)) => "/1/Bob",
|
||||
uri!(simple2(1, &mut s2.name)) => "/1/Bob",
|
||||
uri!(simple2(2, &mut s1.0)) => "/2/Bob",
|
||||
uri!(simple2(2, &mut s2.name)) => "/2/Bob",
|
||||
uri!(simple2(2, s1.0)) => "/2/Bob",
|
||||
uri!(simple2(2, s2.name)) => "/2/Bob",
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_scoped() {
|
||||
assert_uri_eq!{
|
||||
uri!(typed_uris::simple: 100) => "/typed_uris/100",
|
||||
uri!(typed_uris::simple: id = 100) => "/typed_uris/100",
|
||||
uri!(typed_uris::deeper::simple: 100) => "/typed_uris/deeper/100",
|
||||
uri!(typed_uris::simple(100)) => "/typed_uris/100",
|
||||
uri!(typed_uris::simple(id = 100)) => "/typed_uris/100",
|
||||
uri!(typed_uris::deeper::simple(100)) => "/typed_uris/deeper/100",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,10 +397,10 @@ mod typed_uris {
|
|||
#[test]
|
||||
fn check_simple_scoped() {
|
||||
assert_uri_eq! {
|
||||
uri!(simple: id = 100) => "/typed_uris/100",
|
||||
uri!(crate::simple: id = 100) => "/100",
|
||||
uri!("/mount", crate::simple: id = 100) => "/mount/100",
|
||||
uri!(crate::simple2: id = 100, name = "hello") => "/100/hello",
|
||||
uri!(simple(id = 100)) => "/typed_uris/100",
|
||||
uri!(crate::simple(id = 100)) => "/100",
|
||||
uri!("/mount", crate::simple(id = 100)) => "/mount/100",
|
||||
uri!(crate::simple2(id = 100, name = "hello")) => "/100/hello",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,8 +411,8 @@ mod typed_uris {
|
|||
#[test]
|
||||
fn check_deep_scoped() {
|
||||
assert_uri_eq! {
|
||||
uri!(super::simple: id = 100) => "/typed_uris/100",
|
||||
uri!(crate::simple: id = 100) => "/100",
|
||||
uri!(super::simple(id = 100)) => "/typed_uris/100",
|
||||
uri!(crate::simple(id = 100)) => "/100",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -376,68 +437,68 @@ fn test_optional_uri_parameters() {
|
|||
let mut some_10 = Some(10);
|
||||
let mut third = Third { one: "hi there".into(), two: "a b".into() };
|
||||
assert_uri_eq! {
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = Some(10),
|
||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() })
|
||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() }),
|
||||
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = &10,
|
||||
bar = &"hi there",
|
||||
q1 = Some(&10),
|
||||
rest = Some(&third)
|
||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
rest = Some(&third),
|
||||
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = &mut 10,
|
||||
bar = &mut "hi there",
|
||||
q1 = some_10.as_mut(),
|
||||
rest = Some(&mut third)
|
||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
rest = Some(&mut third),
|
||||
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = _,
|
||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() })
|
||||
) => "/10/hi%20there?one=hi%20there&two=a%20b",
|
||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() }),
|
||||
)) => "/10/hi%20there?one=hi%20there&two=a%20b",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = Some(10),
|
||||
rest = _
|
||||
) => "/10/hi%20there?q1=10",
|
||||
rest = _,
|
||||
)) => "/10/hi%20there?q1=10",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = Err(ErrorKind::Missing.into()) as Result<usize, _>,
|
||||
rest = _
|
||||
) => "/10/hi%20there",
|
||||
rest = _,
|
||||
)) => "/10/hi%20there",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = None as Option<usize>,
|
||||
rest = _
|
||||
) => "/10/hi%20there",
|
||||
)) => "/10/hi%20there",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = _,
|
||||
rest = None as Option<Third<'_>>,
|
||||
) => "/10/hi%20there",
|
||||
)) => "/10/hi%20there",
|
||||
|
||||
uri!(optionals:
|
||||
uri!(optionals(
|
||||
foo = 10,
|
||||
bar = &"hi there",
|
||||
q1 = _,
|
||||
rest = _,
|
||||
) => "/10/hi%20there",
|
||||
)) => "/10/hi%20there",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -445,15 +506,15 @@ fn test_optional_uri_parameters() {
|
|||
fn test_simple_ignored() {
|
||||
#[get("/<_>")] fn ignore_one() { }
|
||||
assert_uri_eq! {
|
||||
uri!(ignore_one: 100) => "/100",
|
||||
uri!(ignore_one: "hello") => "/hello",
|
||||
uri!(ignore_one: "cats r us") => "/cats%20r%20us",
|
||||
uri!(ignore_one(100)) => "/100",
|
||||
uri!(ignore_one("hello")) => "/hello",
|
||||
uri!(ignore_one("cats r us")) => "/cats%20r%20us",
|
||||
}
|
||||
|
||||
#[get("/<_>/<_>")] fn ignore_two() { }
|
||||
assert_uri_eq! {
|
||||
uri!(ignore_two: 100, "boop") => "/100/boop",
|
||||
uri!(ignore_two: &"hi", "bop") => "/hi/bop",
|
||||
uri!(ignore_two(100, "boop")) => "/100/boop",
|
||||
uri!(ignore_two(&"hi", "bop")) => "/hi/bop",
|
||||
}
|
||||
|
||||
#[get("/<_>/foo/<_>")] fn ignore_inner_two() { }
|
||||
|
@ -461,9 +522,9 @@ fn test_simple_ignored() {
|
|||
#[get("/hey/hi/<_>/foo/<_>")] fn ignore_inner_two_b() { }
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!(ignore_inner_two: 100, "boop") => "/100/foo/boop",
|
||||
uri!(ignore_inner_one_a: "!?") => "/hi/!%3F/foo",
|
||||
uri!(ignore_inner_two_b: &mut 5, "boo") => "/hey/hi/5/foo/boo",
|
||||
uri!(ignore_inner_two(100, "boop")) => "/100/foo/boop",
|
||||
uri!(ignore_inner_one_a("!?")) => "/hi/!%3F/foo",
|
||||
uri!(ignore_inner_two_b(&mut 5, "boo")) => "/hey/hi/5/foo/boo",
|
||||
}
|
||||
|
||||
#[get("/<_>/foo/<_>?hi")] fn ignore_with_q() { }
|
||||
|
@ -471,8 +532,8 @@ fn test_simple_ignored() {
|
|||
#[get("/hi/<_>/foo/<_>?<hi>&<hey>")] fn ignore_with_q3(hi: &str, hey: &str) { }
|
||||
|
||||
assert_uri_eq! {
|
||||
uri!(ignore_with_q: 100, "boop") => "/100/foo/boop?hi",
|
||||
uri!(ignore_with_q2: "!?", "bop", Some(3usize)) => "/hi/!%3F/foo/bop?hi&hey=3",
|
||||
uri!(ignore_with_q3: &mut 5, "boo", "hi b", "ho") => "/hi/5/foo/boo?hi=hi%20b&hey=ho",
|
||||
uri!(ignore_with_q(100, "boop")) => "/100/foo/boop?hi",
|
||||
uri!(ignore_with_q2("!?", "bop", Some(3usize))) => "/hi/!%3F/foo/bop?hi&hey=3",
|
||||
uri!(ignore_with_q3(&mut 5, "boo", "hi b", "ho")) => "/hi/5/foo/boo?hi=hi%20b&hey=ho",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:45:23
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:45:22
|
||||
|
|
||||
45 | uri!(simple: id = "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
||||
45 | uri!(simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -10,11 +10,11 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:47:18
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:47:17
|
||||
|
|
||||
47 | uri!(simple: "hello");
|
||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
||||
47 | uri!(simple("hello"));
|
||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -22,11 +22,11 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:49:23
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:49:22
|
||||
|
|
||||
49 | uri!(simple: id = 239239i64);
|
||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
||||
49 | uri!(simple(id = 239239i64));
|
||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, i64>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -34,32 +34,32 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:51:31
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:51:30
|
||||
|
|
||||
51 | uri!(not_uri_display: 10, S);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
||||
51 | uri!(not_uri_display(10, S));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Path, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:26
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:25
|
||||
|
|
||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||
56 | uri!(optionals(id = Some(10), name = Ok("bob".into())));
|
||||
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<i32 as FromUriParam<P, &'x i32>>
|
||||
<i32 as FromUriParam<P, &'x mut i32>>
|
||||
<i32 as FromUriParam<P, i32>>
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>`
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>`
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:43
|
||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:42
|
||||
|
|
||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
| ^^^^^^^^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||
56 | uri!(optionals(id = Some(10), name = Ok("bob".into())));
|
||||
| ^^^^^^^^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<std::string::String as FromUriParam<P, &'a str>>
|
||||
|
@ -67,14 +67,14 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
|
|||
<std::string::String as FromUriParam<P, &'x mut &'a str>>
|
||||
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
||||
and 2 others
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Result<_, _>>` for `Result<std::string::String, &str>`
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` for `Result<std::string::String, &str>`
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:58:20
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:58:19
|
||||
|
|
||||
58 | uri!(simple_q: "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
||||
58 | uri!(simple_q("hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
|
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
|
|||
<isize as FromUriParam<P, isize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:60:25
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:60:24
|
||||
|
|
||||
60 | uri!(simple_q: id = "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
||||
60 | uri!(simple_q(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
|
@ -94,48 +94,120 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
|
|||
<isize as FromUriParam<P, isize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:62:24
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:62:23
|
||||
|
|
||||
62 | uri!(other_q: 100, S);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
62 | uri!(other_q(100, S));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:64:26
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:64:25
|
||||
|
|
||||
64 | uri!(other_q: rest = S, id = 100);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
64 | uri!(other_q(rest = S, id = 100));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:66:26
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:66:25
|
||||
|
|
||||
66 | uri!(other_q: rest = _, id = 100);
|
||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
||||
66 | uri!(other_q(rest = _, id = 100));
|
||||
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `S`
|
||||
|
|
||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
||||
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||
|
|
||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
||||
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||
| ------------ required by this bound in `assert_ignorable`
|
||||
|
||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:34
|
||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:33
|
||||
|
|
||||
68 | uri!(other_q: rest = S, id = _);
|
||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
||||
68 | uri!(other_q(rest = S, id = _));
|
||||
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `usize`
|
||||
|
|
||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
||||
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||
|
|
||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
||||
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||
| ------------ required by this bound in `assert_ignorable`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:26
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:25
|
||||
|
|
||||
68 | uri!(other_q: rest = S, id = _);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
68 | uri!(other_q(rest = S, id = _));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:77:40
|
||||
|
|
||||
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:77:15
|
||||
|
|
||||
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||
| ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:78:33
|
||||
|
|
||||
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:78:15
|
||||
|
|
||||
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||
| ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:81:25
|
||||
|
|
||||
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:81:37
|
||||
|
|
||||
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||
| ^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Asterisk`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:82:25
|
||||
|
|
||||
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:82:37
|
||||
|
|
||||
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||
| ^^^^^^^^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Origin<'_>`
|
||||
|
|
|
@ -1,271 +1,279 @@
|
|||
error: expected identifier
|
||||
--> $DIR/typed-uris-bad-params.rs:62:19
|
||||
--> $DIR/typed-uris-bad-params.rs:63:18
|
||||
|
|
||||
62 | uri!(ignored: _ = 10);
|
||||
63 | uri!(ignored(_ = 10));
|
||||
| ^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:68:19
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:69:18
|
||||
|
|
||||
68 | uri!(ignored: 10, "10");
|
||||
69 | uri!(ignored(10, "10"));
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: route `ignored` has uri "/<_>"
|
||||
|
||||
error: expected unnamed arguments due to ignored parameters
|
||||
--> $DIR/typed-uris-bad-params.rs:66:19
|
||||
--> $DIR/typed-uris-bad-params.rs:67:18
|
||||
|
|
||||
66 | uri!(ignored: num = 10);
|
||||
67 | uri!(ignored(num = 10));
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: uri for route `ignored` ignores path parameters: "/<_>"
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:64:19
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:65:18
|
||||
|
|
||||
64 | uri!(ignored: 10, 20);
|
||||
65 | uri!(ignored(10, 20));
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: route `ignored` has uri "/<_>"
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:60:19
|
||||
--> $DIR/typed-uris-bad-params.rs:61:18
|
||||
|
|
||||
60 | uri!(ignored: _);
|
||||
61 | uri!(ignored(_));
|
||||
| ^
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:58:37
|
||||
--> $DIR/typed-uris-bad-params.rs:59:36
|
||||
|
|
||||
58 | uri!(optionals: id = 10, name = _);
|
||||
59 | uri!(optionals(id = 10, name = _));
|
||||
| ^
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:56:26
|
||||
--> $DIR/typed-uris-bad-params.rs:57:25
|
||||
|
|
||||
56 | uri!(optionals: id = _, name = "bob".into());
|
||||
57 | uri!(optionals(id = _, name = "bob".into()));
|
||||
| ^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:54:19
|
||||
--> $DIR/typed-uris-bad-params.rs:55:18
|
||||
|
|
||||
54 | uri!(has_two: id = 100, cookies = "hi");
|
||||
55 | uri!(has_two(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32, name: String
|
||||
= help: missing parameter: `name`
|
||||
help: unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:54:29
|
||||
--> $DIR/typed-uris-bad-params.rs:55:28
|
||||
|
|
||||
54 | uri!(has_two: id = 100, cookies = "hi");
|
||||
55 | uri!(has_two(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:52:19
|
||||
--> $DIR/typed-uris-bad-params.rs:53:18
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32, name: String
|
||||
= help: missing parameter: `name`
|
||||
help: unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:52:19
|
||||
--> $DIR/typed-uris-bad-params.rs:53:18
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^^^^^^
|
||||
help: duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:52:45
|
||||
--> $DIR/typed-uris-bad-params.rs:53:44
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^ ^^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:50:19
|
||||
--> $DIR/typed-uris-bad-params.rs:51:18
|
||||
|
|
||||
50 | uri!(has_two: name = "hi");
|
||||
51 | uri!(has_two(name = "hi"));
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32, name: String
|
||||
= help: missing parameter: `id`
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:48:19
|
||||
--> $DIR/typed-uris-bad-params.rs:49:18
|
||||
|
|
||||
48 | uri!(has_two: id = 100, id = 100, );
|
||||
49 | uri!(has_two(id = 100, id = 100, ));
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32, name: String
|
||||
= help: missing parameter: `name`
|
||||
help: duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:48:29
|
||||
--> $DIR/typed-uris-bad-params.rs:49:28
|
||||
|
|
||||
48 | uri!(has_two: id = 100, id = 100, );
|
||||
49 | uri!(has_two(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one_guarded` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:46:27
|
||||
--> $DIR/typed-uris-bad-params.rs:47:26
|
||||
|
|
||||
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
|
||||
47 | uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:46:37
|
||||
--> $DIR/typed-uris-bad-params.rs:47:36
|
||||
|
|
||||
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
|
||||
47 | uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_one_guarded` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:44:27
|
||||
--> $DIR/typed-uris-bad-params.rs:45:26
|
||||
|
|
||||
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
|
||||
45 | uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:44:27
|
||||
--> $DIR/typed-uris-bad-params.rs:45:26
|
||||
|
|
||||
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
|
||||
45 | uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:42:19
|
||||
--> $DIR/typed-uris-bad-params.rs:43:18
|
||||
|
|
||||
42 | uri!(has_one: name = "hi");
|
||||
43 | uri!(has_one(name = "hi"));
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
= help: missing parameter: `id`
|
||||
help: unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:42:19
|
||||
--> $DIR/typed-uris-bad-params.rs:43:18
|
||||
|
|
||||
42 | uri!(has_one: name = "hi");
|
||||
43 | uri!(has_one(name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:40:19
|
||||
--> $DIR/typed-uris-bad-params.rs:41:18
|
||||
|
|
||||
40 | uri!(has_one: id = 100, id = 100, );
|
||||
41 | uri!(has_one(id = 100, id = 100, ));
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:40:29
|
||||
--> $DIR/typed-uris-bad-params.rs:41:28
|
||||
|
|
||||
40 | uri!(has_one: id = 100, id = 100, );
|
||||
41 | uri!(has_one(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:38:19
|
||||
--> $DIR/typed-uris-bad-params.rs:39:18
|
||||
|
|
||||
38 | uri!(has_one: id = 100, id = 100);
|
||||
39 | uri!(has_one(id = 100, id = 100));
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:38:29
|
||||
--> $DIR/typed-uris-bad-params.rs:39:28
|
||||
|
|
||||
38 | uri!(has_one: id = 100, id = 100);
|
||||
39 | uri!(has_one(id = 100, id = 100));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:36:19
|
||||
--> $DIR/typed-uris-bad-params.rs:37:18
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameters: `name`, `age`
|
||||
--> $DIR/typed-uris-bad-params.rs:36:19
|
||||
--> $DIR/typed-uris-bad-params.rs:37:18
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^^^ ^^^
|
||||
help: duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:36:51
|
||||
--> $DIR/typed-uris-bad-params.rs:37:50
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:34:19
|
||||
--> $DIR/typed-uris-bad-params.rs:35:18
|
||||
|
|
||||
34 | uri!(has_one: name = 100, age = 50, id = 100);
|
||||
35 | uri!(has_one(name = 100, age = 50, id = 100));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameters: `name`, `age`
|
||||
--> $DIR/typed-uris-bad-params.rs:34:19
|
||||
--> $DIR/typed-uris-bad-params.rs:35:18
|
||||
|
|
||||
34 | uri!(has_one: name = 100, age = 50, id = 100);
|
||||
35 | uri!(has_one(name = 100, age = 50, id = 100));
|
||||
| ^^^^ ^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:32:19
|
||||
--> $DIR/typed-uris-bad-params.rs:33:18
|
||||
|
|
||||
32 | uri!(has_one: name = 100, id = 100);
|
||||
33 | uri!(has_one(name = 100, id = 100));
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:32:19
|
||||
--> $DIR/typed-uris-bad-params.rs:33:18
|
||||
|
|
||||
32 | uri!(has_one: name = 100, id = 100);
|
||||
33 | uri!(has_one(name = 100, id = 100));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--> $DIR/typed-uris-bad-params.rs:30:19
|
||||
--> $DIR/typed-uris-bad-params.rs:31:18
|
||||
|
|
||||
30 | uri!(has_one: id = 100, name = "hi");
|
||||
31 | uri!(has_one(id = 100, name = "hi"));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: uri parameters are: id: i32
|
||||
help: unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:30:29
|
||||
--> $DIR/typed-uris-bad-params.rs:31:28
|
||||
|
|
||||
30 | uri!(has_one: id = 100, name = "hi");
|
||||
31 | uri!(has_one(id = 100, name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: expected 2 parameters but 1 was supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:28:19
|
||||
error: route expects 2 parameters but 1 was supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:29:18
|
||||
|
|
||||
28 | uri!(has_two: 10);
|
||||
29 | uri!(has_two(10));
|
||||
| ^^
|
||||
|
|
||||
= note: route `has_two` has uri "/<id>?<name>"
|
||||
|
||||
error: expected 2 parameters but 3 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:27:19
|
||||
error: route expects 2 parameters but 3 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:28:18
|
||||
|
|
||||
27 | uri!(has_two: 10, "hi", "there");
|
||||
28 | uri!(has_two(10, "hi", "there"));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: route `has_two` has uri "/<id>?<name>"
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:25:27
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:26:26
|
||||
|
|
||||
25 | uri!(has_one_guarded: "hi", 100);
|
||||
26 | uri!(has_one_guarded("hi", 100));
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: route `has_one_guarded` has uri "/<id>"
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:24:19
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:25:18
|
||||
|
|
||||
24 | uri!(has_one: "Hello", 23, );
|
||||
25 | uri!(has_one("Hello", 23, ));
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
= note: route `has_one` has uri "/<id>"
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:23:19
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:24:18
|
||||
|
|
||||
23 | uri!(has_one: 1, 23);
|
||||
24 | uri!(has_one(1, 23));
|
||||
| ^^^^^
|
||||
|
|
||||
= note: route `has_one` has uri "/<id>"
|
||||
|
||||
error: expected 1 parameter but 0 were supplied
|
||||
error: route expects 1 parameter but 0 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:22:10
|
||||
|
|
||||
22 | uri!(has_one());
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: route `has_one` has uri "/<id>"
|
||||
|
||||
error: route expects 1 parameter but 0 were supplied
|
||||
--> $DIR/typed-uris-bad-params.rs:21:10
|
||||
|
|
||||
21 | uri!(has_one);
|
||||
|
|
|
@ -1,65 +1,153 @@
|
|||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
||||
error: expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
||||
|
|
||||
7 | uri!(simple: id = 100, "Hello");
|
||||
10 | uri!(simple: id = 100, "Hello");
|
||||
| ^^^^^^^
|
||||
|
||||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:11:17
|
||||
|
|
||||
11 | uri!(simple(id = 100, "Hello"));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:8:18
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:12:17
|
||||
|
|
||||
8 | uri!(simple: "Hello", id = 100);
|
||||
12 | uri!(simple("Hello", id = 100));
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
error: expected `:`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:9:16
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
||||
|
|
||||
9 | uri!(simple,);
|
||||
14 | uri!(simple:);
|
||||
| ^
|
||||
|
||||
error: expected argument list after `:`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:10:16
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:16:16
|
||||
|
|
||||
10 | uri!(simple:);
|
||||
16 | uri!("mount", simple);
|
||||
| ^
|
||||
|
||||
error: unexpected end of input: expected ',' followed by route path
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:11:10
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:17:16
|
||||
|
|
||||
11 | uri!("/mount");
|
||||
| ^^^^^^^^
|
||||
17 | uri!("mount", simple, "http://");
|
||||
| ^
|
||||
|
||||
error: unexpected end of input, expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:12:5
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:18:28
|
||||
|
|
||||
12 | uri!("/mount",);
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
18 | uri!("/mount", simple, "http://");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||
error: expected 1, 2, or 3 arguments, found 4
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:19:36
|
||||
|
|
||||
13 | uri!("mount", simple);
|
||||
| ^^^^^^^
|
||||
19 | uri!("/mount", simple, "#foo", "?foo");
|
||||
| ^^^^^^
|
||||
|
||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:14:10
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:20:16
|
||||
|
|
||||
14 | uri!("/mount/<id>", simple);
|
||||
| ^^^^^^^^^^^^^
|
||||
20 | uri!("mount", simple(10, "hi"), "http://");
|
||||
| ^
|
||||
|
||||
error: unexpected end of input, call to `uri!` cannot be empty
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:15:5
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:21:38
|
||||
|
|
||||
15 | uri!();
|
||||
21 | uri!("/mount", simple(10, "hi"), "http://");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: URI prefix cannot contain query part
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:22:10
|
||||
|
|
||||
22 | uri!("/mount?foo", simple(10, "hi"), "foo/bar?foo#bar");
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:23:38
|
||||
|
|
||||
23 | uri!("/mount", simple(10, "hi"), "a/b");
|
||||
| ^^^^^
|
||||
|
||||
error: expected 1, 2, or 3 arguments, found 4
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:24:46
|
||||
|
|
||||
24 | uri!("/mount", simple(10, "hi"), "#foo", "?foo");
|
||||
| ^^^^^^
|
||||
|
||||
error: invalid URI: unexpected token '<' at index 7
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:25:18
|
||||
|
|
||||
25 | uri!("/mount/<id>", simple);
|
||||
| ^
|
||||
|
||||
error: expected at least 1 argument, found none
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:26:5
|
||||
|
|
||||
26 | uri!();
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:27:16
|
||||
|
|
||||
27 | uri!(simple: id = );
|
||||
| ^
|
||||
|
||||
error: unexpected end of input, expected expression
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:28:22
|
||||
|
|
||||
16 | uri!(simple: id = );
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
28 | uri!(simple(id = ));
|
||||
| ^
|
||||
|
||||
error: invalid URI: unexpected EOF: expected some token at index 0
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:29:11
|
||||
|
|
||||
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
29 | uri!("*", simple(10), "hi");
|
||||
| ^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:30:40
|
||||
|
|
||||
30 | uri!("some.host:8088", simple(10), "hi");
|
||||
| ^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:33:18
|
||||
|
|
||||
33 | uri!("/foo", "bar");
|
||||
| ^^^^^
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:34:17
|
||||
|
|
||||
34 | uri!("/foo" ("bar"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI prefix cannot contain query part
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:35:10
|
||||
|
|
||||
35 | uri!("ftp:?", index);
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:36:25
|
||||
|
|
||||
36 | uri!("ftp:", index, "foo#bar");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:37:25
|
||||
|
|
||||
37 | uri!("ftp:", index, "foo?bar");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: route expects 2 parameters but 0 were supplied
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||
|
|
||||
13 | uri!(simple,);
|
||||
| ^^^^^^
|
||||
|
|
||||
= note: route `simple` has uri "/<id>/<name>"
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:6:13
|
||||
|
|
||||
6 | struct Bar1(BadType);
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:10:5
|
||||
|
|
||||
10 | field: BadType,
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:16:5
|
||||
|
|
||||
16 | bad: BadType,
|
||||
| ^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:21:11
|
||||
|
|
||||
21 | Inner(BadType),
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:27:9
|
||||
|
|
||||
27 | field: BadType,
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:35:9
|
||||
|
|
||||
35 | other: BadType,
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Path>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Path>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:40:12
|
||||
|
|
||||
40 | struct Baz(BadType);
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Path>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Path>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Path>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Path>` for `&BadType`
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:45:23
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:45:22
|
||||
|
|
||||
45 | uri!(simple: id = "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
||||
45 | uri!(simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -10,11 +10,11 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:47:18
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:47:17
|
||||
|
|
||||
47 | uri!(simple: "hello");
|
||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
||||
47 | uri!(simple("hello"));
|
||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -22,11 +22,11 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:49:23
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:49:22
|
||||
|
|
||||
49 | uri!(simple: id = 239239i64);
|
||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
||||
49 | uri!(simple(id = 239239i64));
|
||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, i64>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
|
@ -34,32 +34,32 @@ error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>
|
|||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:51:31
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:51:30
|
||||
|
|
||||
51 | uri!(not_uri_display: 10, S);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
||||
51 | uri!(not_uri_display(10, S));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Path, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:26
|
||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:25
|
||||
|
|
||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||
56 | uri!(optionals(id = Some(10), name = Ok("bob".into())));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<i32 as FromUriParam<P, &'x i32>>
|
||||
<i32 as FromUriParam<P, &'x mut i32>>
|
||||
<i32 as FromUriParam<P, i32>>
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>`
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` for `std::option::Option<i32>`
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:43
|
||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:56:42
|
||||
|
|
||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
| ^^ the trait `FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||
56 | uri!(optionals(id = Some(10), name = Ok("bob".into())));
|
||||
| ^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<std::string::String as FromUriParam<P, &'a str>>
|
||||
|
@ -67,14 +67,14 @@ error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::u
|
|||
<std::string::String as FromUriParam<P, &'x mut &'a str>>
|
||||
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
||||
and 2 others
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::Path, Result<_, _>>` for `Result<std::string::String, &str>`
|
||||
= note: required because of the requirements on the impl of `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` for `Result<std::string::String, &str>`
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:58:20
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:58:19
|
||||
|
|
||||
58 | uri!(simple_q: "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
||||
58 | uri!(simple_q("hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
|
@ -82,11 +82,11 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
|
|||
<isize as FromUriParam<P, isize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:60:25
|
||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:60:24
|
||||
|
|
||||
60 | uri!(simple_q: id = "hi");
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
||||
60 | uri!(simple_q(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<isize as FromUriParam<P, &'x isize>>
|
||||
|
@ -94,48 +94,120 @@ error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &st
|
|||
<isize as FromUriParam<P, isize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:62:24
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:62:23
|
||||
|
|
||||
62 | uri!(other_q: 100, S);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
62 | uri!(other_q(100, S));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:64:26
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:64:25
|
||||
|
|
||||
64 | uri!(other_q: rest = S, id = 100);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
64 | uri!(other_q(rest = S, id = 100));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:66:26
|
||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:66:25
|
||||
|
|
||||
66 | uri!(other_q: rest = _, id = 100);
|
||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
||||
66 | uri!(other_q(rest = _, id = 100));
|
||||
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `S`
|
||||
|
|
||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
||||
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||
|
|
||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
||||
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||
| ------------ required by this bound in `assert_ignorable`
|
||||
|
||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:34
|
||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:33
|
||||
|
|
||||
68 | uri!(other_q: rest = S, id = _);
|
||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
||||
68 | uri!(other_q(rest = S, id = _));
|
||||
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `usize`
|
||||
|
|
||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
||||
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||
|
|
||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
||||
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||
| ------------ required by this bound in `assert_ignorable`
|
||||
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:26
|
||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:68:25
|
||||
|
|
||||
68 | uri!(other_q: rest = S, id = _);
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
||||
68 | uri!(other_q(rest = S, id = _));
|
||||
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||
|
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:77:40
|
||||
|
|
||||
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:77:15
|
||||
|
|
||||
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||
| ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:78:33
|
||||
|
|
||||
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:78:15
|
||||
|
|
||||
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||
| ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:81:25
|
||||
|
|
||||
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:81:37
|
||||
|
|
||||
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||
| ^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Asterisk`
|
||||
|
||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:82:25
|
||||
|
|
||||
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||
|
|
||||
= help: the following implementations were found:
|
||||
<usize as FromUriParam<P, &'x mut usize>>
|
||||
<usize as FromUriParam<P, &'x usize>>
|
||||
<usize as FromUriParam<P, usize>>
|
||||
= note: required by `from_uri_param`
|
||||
|
||||
error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||
--> $DIR/typed-uri-bad-type.rs:82:37
|
||||
|
|
||||
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||
| ^^^^^^^^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Origin<'_>`
|
||||
|
|
|
@ -1,264 +1,271 @@
|
|||
error: expected identifier
|
||||
--> $DIR/typed-uris-bad-params.rs:62:19
|
||||
--> $DIR/typed-uris-bad-params.rs:63:18
|
||||
|
|
||||
62 | uri!(ignored: _ = 10);
|
||||
63 | uri!(ignored(_ = 10));
|
||||
| ^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--- note: route `ignored` has uri "/<_>"
|
||||
--> $DIR/typed-uris-bad-params.rs:68:19
|
||||
--> $DIR/typed-uris-bad-params.rs:69:18
|
||||
|
|
||||
68 | uri!(ignored: 10, "10");
|
||||
69 | uri!(ignored(10, "10"));
|
||||
| ^^
|
||||
|
||||
error: expected unnamed arguments due to ignored parameters
|
||||
--- note: uri for route `ignored` ignores path parameters: "/<_>"
|
||||
--> $DIR/typed-uris-bad-params.rs:66:19
|
||||
--> $DIR/typed-uris-bad-params.rs:67:18
|
||||
|
|
||||
66 | uri!(ignored: num = 10);
|
||||
67 | uri!(ignored(num = 10));
|
||||
| ^^^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--- note: route `ignored` has uri "/<_>"
|
||||
--> $DIR/typed-uris-bad-params.rs:64:19
|
||||
--> $DIR/typed-uris-bad-params.rs:65:18
|
||||
|
|
||||
64 | uri!(ignored: 10, 20);
|
||||
65 | uri!(ignored(10, 20));
|
||||
| ^^
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:60:19
|
||||
--> $DIR/typed-uris-bad-params.rs:61:18
|
||||
|
|
||||
60 | uri!(ignored: _);
|
||||
61 | uri!(ignored(_));
|
||||
| ^
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:58:37
|
||||
--> $DIR/typed-uris-bad-params.rs:59:36
|
||||
|
|
||||
58 | uri!(optionals: id = 10, name = _);
|
||||
59 | uri!(optionals(id = 10, name = _));
|
||||
| ^
|
||||
|
||||
error: path parameters cannot be ignored
|
||||
--> $DIR/typed-uris-bad-params.rs:56:26
|
||||
--> $DIR/typed-uris-bad-params.rs:57:25
|
||||
|
|
||||
56 | uri!(optionals: id = _, name = "bob".into());
|
||||
57 | uri!(optionals(id = _, name = "bob".into()));
|
||||
| ^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--- note: uri parameters are: id: i32, name: String
|
||||
--- help: missing parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:54:19
|
||||
--> $DIR/typed-uris-bad-params.rs:55:18
|
||||
|
|
||||
54 | uri!(has_two: id = 100, cookies = "hi");
|
||||
55 | uri!(has_two(id = 100, cookies = "hi"));
|
||||
| ^^
|
||||
|
||||
error: [help] unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:54:29
|
||||
--> $DIR/typed-uris-bad-params.rs:55:28
|
||||
|
|
||||
54 | uri!(has_two: id = 100, cookies = "hi");
|
||||
55 | uri!(has_two(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--- note: uri parameters are: id: i32, name: String
|
||||
--- help: missing parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:52:19
|
||||
--> $DIR/typed-uris-bad-params.rs:53:18
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^^^^^^
|
||||
|
||||
error: [help] unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:52:19
|
||||
--> $DIR/typed-uris-bad-params.rs:53:18
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^^^^^^
|
||||
|
||||
error: [help] duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:52:45
|
||||
--> $DIR/typed-uris-bad-params.rs:53:44
|
||||
|
|
||||
52 | uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
53 | uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--- note: uri parameters are: id: i32, name: String
|
||||
--- help: missing parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:50:19
|
||||
--> $DIR/typed-uris-bad-params.rs:51:18
|
||||
|
|
||||
50 | uri!(has_two: name = "hi");
|
||||
51 | uri!(has_two(name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_two` route uri
|
||||
--- note: uri parameters are: id: i32, name: String
|
||||
--- help: missing parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:48:19
|
||||
--> $DIR/typed-uris-bad-params.rs:49:18
|
||||
|
|
||||
48 | uri!(has_two: id = 100, id = 100, );
|
||||
49 | uri!(has_two(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: [help] duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:48:29
|
||||
--> $DIR/typed-uris-bad-params.rs:49:28
|
||||
|
|
||||
48 | uri!(has_two: id = 100, id = 100, );
|
||||
49 | uri!(has_two(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one_guarded` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:46:27
|
||||
--> $DIR/typed-uris-bad-params.rs:47:26
|
||||
|
|
||||
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
|
||||
47 | uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||
| ^^
|
||||
|
||||
error: [help] unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:46:37
|
||||
--> $DIR/typed-uris-bad-params.rs:47:36
|
||||
|
|
||||
46 | uri!(has_one_guarded: id = 100, cookies = "hi");
|
||||
47 | uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_one_guarded` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:44:27
|
||||
--> $DIR/typed-uris-bad-params.rs:45:26
|
||||
|
|
||||
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
|
||||
45 | uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||
| ^^^^^^^
|
||||
|
||||
error: [help] unknown parameter: `cookies`
|
||||
--> $DIR/typed-uris-bad-params.rs:44:27
|
||||
--> $DIR/typed-uris-bad-params.rs:45:26
|
||||
|
|
||||
44 | uri!(has_one_guarded: cookies = "hi", id = 100);
|
||||
45 | uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--- help: missing parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:42:19
|
||||
--> $DIR/typed-uris-bad-params.rs:43:18
|
||||
|
|
||||
42 | uri!(has_one: name = "hi");
|
||||
43 | uri!(has_one(name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: [help] unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:42:19
|
||||
--> $DIR/typed-uris-bad-params.rs:43:18
|
||||
|
|
||||
42 | uri!(has_one: name = "hi");
|
||||
43 | uri!(has_one(name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:40:19
|
||||
--> $DIR/typed-uris-bad-params.rs:41:18
|
||||
|
|
||||
40 | uri!(has_one: id = 100, id = 100, );
|
||||
41 | uri!(has_one(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: [help] duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:40:29
|
||||
--> $DIR/typed-uris-bad-params.rs:41:28
|
||||
|
|
||||
40 | uri!(has_one: id = 100, id = 100, );
|
||||
41 | uri!(has_one(id = 100, id = 100, ));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:38:19
|
||||
--> $DIR/typed-uris-bad-params.rs:39:18
|
||||
|
|
||||
38 | uri!(has_one: id = 100, id = 100);
|
||||
39 | uri!(has_one(id = 100, id = 100));
|
||||
| ^^
|
||||
|
||||
error: [help] duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:38:29
|
||||
--> $DIR/typed-uris-bad-params.rs:39:28
|
||||
|
|
||||
38 | uri!(has_one: id = 100, id = 100);
|
||||
39 | uri!(has_one(id = 100, id = 100));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:36:19
|
||||
--> $DIR/typed-uris-bad-params.rs:37:18
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^^^
|
||||
|
||||
error: [help] unknown parameters: `name`, `age`
|
||||
--> $DIR/typed-uris-bad-params.rs:36:19
|
||||
--> $DIR/typed-uris-bad-params.rs:37:18
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^^^
|
||||
|
||||
error: [help] duplicate parameter: `id`
|
||||
--> $DIR/typed-uris-bad-params.rs:36:51
|
||||
--> $DIR/typed-uris-bad-params.rs:37:50
|
||||
|
|
||||
36 | uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
37 | uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
| ^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:34:19
|
||||
--> $DIR/typed-uris-bad-params.rs:35:18
|
||||
|
|
||||
34 | uri!(has_one: name = 100, age = 50, id = 100);
|
||||
35 | uri!(has_one(name = 100, age = 50, id = 100));
|
||||
| ^^^^
|
||||
|
||||
error: [help] unknown parameters: `name`, `age`
|
||||
--> $DIR/typed-uris-bad-params.rs:34:19
|
||||
--> $DIR/typed-uris-bad-params.rs:35:18
|
||||
|
|
||||
34 | uri!(has_one: name = 100, age = 50, id = 100);
|
||||
35 | uri!(has_one(name = 100, age = 50, id = 100));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:32:19
|
||||
--> $DIR/typed-uris-bad-params.rs:33:18
|
||||
|
|
||||
32 | uri!(has_one: name = 100, id = 100);
|
||||
33 | uri!(has_one(name = 100, id = 100));
|
||||
| ^^^^
|
||||
|
||||
error: [help] unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:32:19
|
||||
--> $DIR/typed-uris-bad-params.rs:33:18
|
||||
|
|
||||
32 | uri!(has_one: name = 100, id = 100);
|
||||
33 | uri!(has_one(name = 100, id = 100));
|
||||
| ^^^^
|
||||
|
||||
error: invalid parameters for `has_one` route uri
|
||||
--- note: uri parameters are: id: i32
|
||||
--> $DIR/typed-uris-bad-params.rs:30:19
|
||||
--> $DIR/typed-uris-bad-params.rs:31:18
|
||||
|
|
||||
30 | uri!(has_one: id = 100, name = "hi");
|
||||
31 | uri!(has_one(id = 100, name = "hi"));
|
||||
| ^^
|
||||
|
||||
error: [help] unknown parameter: `name`
|
||||
--> $DIR/typed-uris-bad-params.rs:30:29
|
||||
--> $DIR/typed-uris-bad-params.rs:31:28
|
||||
|
|
||||
30 | uri!(has_one: id = 100, name = "hi");
|
||||
31 | uri!(has_one(id = 100, name = "hi"));
|
||||
| ^^^^
|
||||
|
||||
error: expected 2 parameters but 1 was supplied
|
||||
error: route expects 2 parameters but 1 was supplied
|
||||
--- note: route `has_two` has uri "/<id>?<name>"
|
||||
--> $DIR/typed-uris-bad-params.rs:28:19
|
||||
--> $DIR/typed-uris-bad-params.rs:29:18
|
||||
|
|
||||
28 | uri!(has_two: 10);
|
||||
29 | uri!(has_two(10));
|
||||
| ^^
|
||||
|
||||
error: expected 2 parameters but 3 were supplied
|
||||
error: route expects 2 parameters but 3 were supplied
|
||||
--- note: route `has_two` has uri "/<id>?<name>"
|
||||
--> $DIR/typed-uris-bad-params.rs:27:19
|
||||
--> $DIR/typed-uris-bad-params.rs:28:18
|
||||
|
|
||||
27 | uri!(has_two: 10, "hi", "there");
|
||||
28 | uri!(has_two(10, "hi", "there"));
|
||||
| ^^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--- note: route `has_one_guarded` has uri "/<id>"
|
||||
--> $DIR/typed-uris-bad-params.rs:25:27
|
||||
--> $DIR/typed-uris-bad-params.rs:26:26
|
||||
|
|
||||
25 | uri!(has_one_guarded: "hi", 100);
|
||||
26 | uri!(has_one_guarded("hi", 100));
|
||||
| ^^^^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--- note: route `has_one` has uri "/<id>"
|
||||
--> $DIR/typed-uris-bad-params.rs:24:19
|
||||
--> $DIR/typed-uris-bad-params.rs:25:18
|
||||
|
|
||||
24 | uri!(has_one: "Hello", 23, );
|
||||
25 | uri!(has_one("Hello", 23, ));
|
||||
| ^^^^^^^
|
||||
|
||||
error: expected 1 parameter but 2 were supplied
|
||||
error: route expects 1 parameter but 2 were supplied
|
||||
--- note: route `has_one` has uri "/<id>"
|
||||
--> $DIR/typed-uris-bad-params.rs:23:19
|
||||
--> $DIR/typed-uris-bad-params.rs:24:18
|
||||
|
|
||||
23 | uri!(has_one: 1, 23);
|
||||
24 | uri!(has_one(1, 23));
|
||||
| ^
|
||||
|
||||
error: expected 1 parameter but 0 were supplied
|
||||
error: route expects 1 parameter but 0 were supplied
|
||||
--- note: route `has_one` has uri "/<id>"
|
||||
--> $DIR/typed-uris-bad-params.rs:22:10
|
||||
|
|
||||
22 | uri!(has_one());
|
||||
| ^^^^^^^
|
||||
|
||||
error: route expects 1 parameter but 0 were supplied
|
||||
--- note: route `has_one` has uri "/<id>"
|
||||
--> $DIR/typed-uris-bad-params.rs:21:10
|
||||
|
|
||||
|
|
|
@ -1,65 +1,152 @@
|
|||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
||||
error: expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
||||
|
|
||||
7 | uri!(simple: id = 100, "Hello");
|
||||
10 | uri!(simple: id = 100, "Hello");
|
||||
| ^^^^^^^
|
||||
|
||||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:11:17
|
||||
|
|
||||
11 | uri!(simple(id = 100, "Hello"));
|
||||
| ^^
|
||||
|
||||
error: named and unnamed parameters cannot be mixed
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:8:18
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:12:17
|
||||
|
|
||||
8 | uri!(simple: "Hello", id = 100);
|
||||
12 | uri!(simple("Hello", id = 100));
|
||||
| ^^^^^^^
|
||||
|
||||
error: expected `:`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:9:16
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
||||
|
|
||||
9 | uri!(simple,);
|
||||
14 | uri!(simple:);
|
||||
| ^
|
||||
|
||||
error: expected argument list after `:`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:10:16
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:16:10
|
||||
|
|
||||
10 | uri!(simple:);
|
||||
| ^
|
||||
|
||||
error: unexpected end of input: expected ',' followed by route path
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:11:10
|
||||
|
|
||||
11 | uri!("/mount");
|
||||
| ^^^^^^^^
|
||||
|
||||
error: unexpected end of input, expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:12:5
|
||||
|
|
||||
12 | uri!("/mount",);
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||
|
|
||||
13 | uri!("mount", simple);
|
||||
16 | uri!("mount", simple);
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:14:10
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:17:10
|
||||
|
|
||||
14 | uri!("/mount/<id>", simple);
|
||||
17 | uri!("mount", simple, "http://");
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:18:28
|
||||
|
|
||||
18 | uri!("/mount", simple, "http://");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: expected 1, 2, or 3 arguments, found 4
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:19:36
|
||||
|
|
||||
19 | uri!("/mount", simple, "#foo", "?foo");
|
||||
| ^^^^^^
|
||||
|
||||
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:20:10
|
||||
|
|
||||
20 | uri!("mount", simple(10, "hi"), "http://");
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:21:38
|
||||
|
|
||||
21 | uri!("/mount", simple(10, "hi"), "http://");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: URI prefix cannot contain query part
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:22:10
|
||||
|
|
||||
22 | uri!("/mount?foo", simple(10, "hi"), "foo/bar?foo#bar");
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:23:38
|
||||
|
|
||||
23 | uri!("/mount", simple(10, "hi"), "a/b");
|
||||
| ^^^^^
|
||||
|
||||
error: expected 1, 2, or 3 arguments, found 4
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:24:46
|
||||
|
|
||||
24 | uri!("/mount", simple(10, "hi"), "#foo", "?foo");
|
||||
| ^^^^^^
|
||||
|
||||
error: invalid URI: unexpected token '<' at index 7
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:25:10
|
||||
|
|
||||
25 | uri!("/mount/<id>", simple);
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: unexpected end of input, call to `uri!` cannot be empty
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:15:5
|
||||
error: expected at least 1 argument, found none
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:26:5
|
||||
|
|
||||
15 | uri!();
|
||||
26 | uri!();
|
||||
| ^^^^^^^
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:27:16
|
||||
|
|
||||
27 | uri!(simple: id = );
|
||||
| ^
|
||||
|
||||
error: unexpected end of input, expected expression
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:28:16
|
||||
|
|
||||
16 | uri!(simple: id = );
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
28 | uri!(simple(id = ));
|
||||
| ^^^^^^^
|
||||
|
||||
error: invalid URI: unexpected EOF: expected some token at index 0
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:29:10
|
||||
|
|
||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
29 | uri!("*", simple(10), "hi");
|
||||
| ^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:30:40
|
||||
|
|
||||
30 | uri!("some.host:8088", simple(10), "hi");
|
||||
| ^^^^
|
||||
|
||||
error: expected identifier
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:33:18
|
||||
|
|
||||
33 | uri!("/foo", "bar");
|
||||
| ^^^^^
|
||||
|
||||
error: unexpected token
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:34:17
|
||||
|
|
||||
34 | uri!("/foo" ("bar"));
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI prefix cannot contain query part
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:35:10
|
||||
|
|
||||
35 | uri!("ftp:?", index);
|
||||
| ^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:36:25
|
||||
|
|
||||
36 | uri!("ftp:", index, "foo#bar");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: URI suffix must contain only query and/or fragment
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:37:25
|
||||
|
|
||||
37 | uri!("ftp:", index, "foo?bar");
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: route expects 2 parameters but 0 were supplied
|
||||
--- note: route `simple` has uri "/<id>/<name>"
|
||||
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||
|
|
||||
13 | uri!(simple,);
|
||||
| ^^^^^^
|
||||
|
|
|
@ -1,61 +1,61 @@
|
|||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:6:13
|
||||
|
|
||||
6 | struct Bar1(BadType);
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:10:5
|
||||
|
|
||||
10 | field: BadType,
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:16:5
|
||||
|
|
||||
16 | bad: BadType,
|
||||
| ^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:21:11
|
||||
|
|
||||
21 | Inner(BadType),
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:27:9
|
||||
|
|
||||
27 | field: BadType,
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:35:9
|
||||
|
|
||||
35 | other: BadType,
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::Query>` is not implemented for `BadType`
|
||||
| ^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Query>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&BadType`
|
||||
= note: 1 redundant requirements hidden
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Query>` for `&&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Query>` for `&&BadType`
|
||||
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Path>` is not satisfied
|
||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Path>` is not satisfied
|
||||
--> $DIR/uri_display_type_errors.rs:40:12
|
||||
|
|
||||
40 | struct Baz(BadType);
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Path>` is not implemented for `BadType`
|
||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Path>` is not implemented for `BadType`
|
||||
|
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Path>` for `&BadType`
|
||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Path>` for `&BadType`
|
||||
|
|
|
@ -42,34 +42,42 @@ fn other_q(id: usize, rest: S) { }
|
|||
fn optionals_q(id: Option<i32>, name: Result<String, Errors<'_>>) { }
|
||||
|
||||
fn main() {
|
||||
uri!(simple: id = "hi");
|
||||
uri!(simple(id = "hi"));
|
||||
|
||||
uri!(simple: "hello");
|
||||
uri!(simple("hello"));
|
||||
|
||||
uri!(simple: id = 239239i64);
|
||||
uri!(simple(id = 239239i64));
|
||||
|
||||
uri!(not_uri_display: 10, S);
|
||||
uri!(not_uri_display(10, S));
|
||||
|
||||
// This one is okay. In paths, a value _must_ be supplied.
|
||||
uri!(optionals: id = 10, name = "bob".to_string());
|
||||
uri!(optionals(id = 10, name = "bob".to_string()));
|
||||
|
||||
uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
||||
uri!(optionals(id = Some(10), name = Ok("bob".into())));
|
||||
|
||||
uri!(simple_q: "hi");
|
||||
uri!(simple_q("hi"));
|
||||
|
||||
uri!(simple_q: id = "hi");
|
||||
uri!(simple_q(id = "hi"));
|
||||
|
||||
uri!(other_q: 100, S);
|
||||
uri!(other_q(100, S));
|
||||
|
||||
uri!(other_q: rest = S, id = 100);
|
||||
uri!(other_q(rest = S, id = 100));
|
||||
|
||||
uri!(other_q: rest = _, id = 100);
|
||||
uri!(other_q(rest = _, id = 100));
|
||||
|
||||
uri!(other_q: rest = S, id = _);
|
||||
uri!(other_q(rest = S, id = _));
|
||||
|
||||
// These are all okay.
|
||||
uri!(optionals_q: _, _);
|
||||
uri!(optionals_q: id = Some(10), name = Some("Bob".to_string()));
|
||||
uri!(optionals_q: _, Some("Bob".into()));
|
||||
uri!(optionals_q: id = _, name = _);
|
||||
uri!(optionals_q(_, _));
|
||||
uri!(optionals_q(id = Some(10), name = Some("Bob".to_string())));
|
||||
uri!(optionals_q(_, Some("Bob".into())));
|
||||
uri!(optionals_q(id = _, name = _));
|
||||
|
||||
// Invalid prefixes.
|
||||
uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||
uri!(uri!("*"), simple(id = "hi"));
|
||||
|
||||
// Invalid suffix.
|
||||
uri!(_, simple(id = "hi"), uri!("*"));
|
||||
uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||
}
|
||||
|
|
|
@ -19,51 +19,52 @@ fn ignored() { }
|
|||
|
||||
fn main() {
|
||||
uri!(has_one);
|
||||
uri!(has_one());
|
||||
|
||||
uri!(has_one: 1, 23);
|
||||
uri!(has_one: "Hello", 23, );
|
||||
uri!(has_one_guarded: "hi", 100);
|
||||
uri!(has_one(1, 23));
|
||||
uri!(has_one("Hello", 23, ));
|
||||
uri!(has_one_guarded("hi", 100));
|
||||
|
||||
uri!(has_two: 10, "hi", "there");
|
||||
uri!(has_two: 10);
|
||||
uri!(has_two(10, "hi", "there"));
|
||||
uri!(has_two(10));
|
||||
|
||||
uri!(has_one: id = 100, name = "hi");
|
||||
uri!(has_one(id = 100, name = "hi"));
|
||||
|
||||
uri!(has_one: name = 100, id = 100);
|
||||
uri!(has_one(name = 100, id = 100));
|
||||
|
||||
uri!(has_one: name = 100, age = 50, id = 100);
|
||||
uri!(has_one(name = 100, age = 50, id = 100));
|
||||
|
||||
uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
||||
uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||
|
||||
uri!(has_one: id = 100, id = 100);
|
||||
uri!(has_one(id = 100, id = 100));
|
||||
|
||||
uri!(has_one: id = 100, id = 100, );
|
||||
uri!(has_one(id = 100, id = 100, ));
|
||||
|
||||
uri!(has_one: name = "hi");
|
||||
uri!(has_one(name = "hi"));
|
||||
|
||||
uri!(has_one_guarded: cookies = "hi", id = 100);
|
||||
uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||
|
||||
uri!(has_one_guarded: id = 100, cookies = "hi");
|
||||
uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||
|
||||
uri!(has_two: id = 100, id = 100, );
|
||||
uri!(has_two(id = 100, id = 100, ));
|
||||
|
||||
uri!(has_two: name = "hi");
|
||||
uri!(has_two(name = "hi"));
|
||||
|
||||
uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
||||
uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||
|
||||
uri!(has_two: id = 100, cookies = "hi");
|
||||
uri!(has_two(id = 100, cookies = "hi"));
|
||||
|
||||
uri!(optionals: id = _, name = "bob".into());
|
||||
uri!(optionals(id = _, name = "bob".into()));
|
||||
|
||||
uri!(optionals: id = 10, name = _);
|
||||
uri!(optionals(id = 10, name = _));
|
||||
|
||||
uri!(ignored: _);
|
||||
uri!(ignored(_));
|
||||
|
||||
uri!(ignored: _ = 10);
|
||||
uri!(ignored(_ = 10));
|
||||
|
||||
uri!(ignored: 10, 20);
|
||||
uri!(ignored(10, 20));
|
||||
|
||||
uri!(ignored: num = 10);
|
||||
uri!(ignored(num = 10));
|
||||
|
||||
uri!(ignored: 10, "10");
|
||||
uri!(ignored(10, "10"));
|
||||
}
|
||||
|
|
|
@ -1,17 +1,38 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
#[get("/")]
|
||||
fn index() { }
|
||||
|
||||
#[post("/<id>/<name>")]
|
||||
fn simple(id: i32, name: String) -> &'static str { "" }
|
||||
|
||||
fn main() {
|
||||
uri!(simple: id = 100, "Hello");
|
||||
uri!(simple: "Hello", id = 100);
|
||||
uri!(simple(id = 100, "Hello"));
|
||||
uri!(simple("Hello", id = 100));
|
||||
uri!(simple,);
|
||||
uri!(simple:);
|
||||
uri!("/mount");
|
||||
uri!("/mount",);
|
||||
uri!("mount", simple);
|
||||
uri!("mount", simple, "http://");
|
||||
uri!("/mount", simple, "http://");
|
||||
uri!("/mount", simple, "#foo", "?foo");
|
||||
uri!("mount", simple(10, "hi"), "http://");
|
||||
uri!("/mount", simple(10, "hi"), "http://");
|
||||
uri!("/mount?foo", simple(10, "hi"), "foo/bar?foo#bar");
|
||||
uri!("/mount", simple(10, "hi"), "a/b");
|
||||
uri!("/mount", simple(10, "hi"), "#foo", "?foo");
|
||||
uri!("/mount/<id>", simple);
|
||||
uri!();
|
||||
uri!(simple: id = );
|
||||
uri!(simple(id = ));
|
||||
uri!("*", simple(10), "hi");
|
||||
uri!("some.host:8088", simple(10), "hi");
|
||||
uri!("?foo");
|
||||
uri!("");
|
||||
uri!("/foo", "bar");
|
||||
uri!("/foo" ("bar"));
|
||||
uri!("ftp:?", index);
|
||||
uri!("ftp:", index, "foo#bar");
|
||||
uri!("ftp:", index, "foo?bar");
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::http::uri::{UriDisplay, Query, Path};
|
||||
use rocket::http::uri::fmt::{UriDisplay, Query, Path};
|
||||
|
||||
macro_rules! assert_uri_display_query {
|
||||
($v:expr, $s:expr) => (
|
||||
|
|
|
@ -34,7 +34,7 @@ ref-cast = "1.0"
|
|||
uncased = "0.9.6"
|
||||
parking_lot = "0.11"
|
||||
either = "1"
|
||||
pear = "0.2.1"
|
||||
pear = "0.2.3"
|
||||
pin-project-lite = "0.2"
|
||||
memchr = "2"
|
||||
stable-pattern = "0.1"
|
||||
|
|
|
@ -164,17 +164,32 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T>
|
|||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Make `self` concrete by allocating if indexed.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is an indexed string and `source` is None.
|
||||
pub fn into_concrete(self, source: &Option<Cow<'_, T>>) -> Cow<'a, T> {
|
||||
if self.is_indexed() && source.is_none() {
|
||||
panic!("cannot concretize indexed str to str without base string!")
|
||||
}
|
||||
|
||||
match self {
|
||||
Indexed::Indexed(i, j) => Cow::Owned(source.as_ref().unwrap()[i..j].to_owned()),
|
||||
Indexed::Concrete(string) => string,
|
||||
}
|
||||
}
|
||||
|
||||
/// Retrieves the string `self` corresponds to. If `self` is derived from
|
||||
/// indexes, the corresponding subslice of `source` is returned. Otherwise,
|
||||
/// the concrete string is returned.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` is an indexed string and `string` is None.
|
||||
// pub fn to_source(&self, source: Option<&'a T>) -> &T {
|
||||
/// Panics if `self` is an indexed string and `source` is None.
|
||||
pub fn from_cow_source<'s>(&'s self, source: &'s Option<Cow<'_, T>>) -> &'s T {
|
||||
if self.is_indexed() && source.is_none() {
|
||||
panic!("Cannot convert indexed str to str without base string!")
|
||||
panic!("cannot convert indexed str to str without base string!")
|
||||
}
|
||||
|
||||
match *self {
|
||||
|
|
|
@ -18,6 +18,7 @@ pub struct Error<'a> {
|
|||
pub(crate) index: usize,
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<'a> From<ParseError<RawInput<'a>>> for Error<'a> {
|
||||
fn from(inner: ParseError<RawInput<'a>>) -> Self {
|
||||
let expected = inner.error.map(|t| t.into(), |v| v.values.into());
|
||||
|
|
|
@ -4,9 +4,9 @@ pub(crate) mod tables;
|
|||
|
||||
#[cfg(test)] mod tests;
|
||||
|
||||
use crate::uri::{Uri, Origin, Absolute, Authority};
|
||||
use crate::uri::{Uri, Origin, Absolute, Authority, Reference};
|
||||
|
||||
use self::parser::{uri, origin, authority_only, absolute_only};
|
||||
use self::parser::*;
|
||||
|
||||
pub use self::error::Error;
|
||||
|
||||
|
@ -24,10 +24,15 @@ pub fn origin_from_str(s: &str) -> Result<Origin<'_>, Error<'_>> {
|
|||
|
||||
#[inline]
|
||||
pub fn authority_from_str(s: &str) -> Result<Authority<'_>, Error<'_>> {
|
||||
Ok(parse!(authority_only: RawInput::new(s.as_bytes()))?)
|
||||
Ok(parse!(authority: RawInput::new(s.as_bytes()))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn absolute_from_str(s: &str) -> Result<Absolute<'_>, Error<'_>> {
|
||||
Ok(parse!(absolute_only: RawInput::new(s.as_bytes()))?)
|
||||
Ok(parse!(absolute: RawInput::new(s.as_bytes()))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reference_from_str(s: &str) -> Result<Reference<'_>, Error<'_>> {
|
||||
Ok(parse!(reference: RawInput::new(s.as_bytes()))?)
|
||||
}
|
||||
|
|
|
@ -1,67 +1,168 @@
|
|||
use pear::parsers::*;
|
||||
use pear::input::{Extent, Rewind};
|
||||
use pear::macros::{parser, switch, parse_current_marker, parse_error, parse_try};
|
||||
use pear::combinators::*;
|
||||
use pear::input::{self, Pear, Extent, Rewind, Input};
|
||||
use pear::macros::{parser, switch, parse_error, parse_try};
|
||||
|
||||
use crate::uri::{Uri, Origin, Authority, Absolute, Host};
|
||||
use crate::parse::uri::tables::{is_reg_name_char, is_pchar, is_qchar};
|
||||
use crate::uri::{Uri, Origin, Authority, Absolute, Reference, Asterisk};
|
||||
use crate::parse::uri::tables::*;
|
||||
use crate::parse::uri::RawInput;
|
||||
|
||||
type Result<'a, T> = pear::input::Result<T, RawInput<'a>>;
|
||||
|
||||
// SAFETY: Every `unsafe` here comes from bytes -> &str conversions. Since all
|
||||
// bytes are checked against tables in `tables.rs`, all of which allow only
|
||||
// ASCII characters, these are all safe.
|
||||
|
||||
// TODO: We should cap the source we pass into `raw` to the bytes we've actually
|
||||
// checked. Otherwise, we could have good bytes followed by unchecked bad bytes
|
||||
// since eof() may not called. However, note that we only actually expose these
|
||||
// parsers via `parse!()`, which _does_ call `eof()`, so we're externally okay.
|
||||
|
||||
#[parser(rewind)]
|
||||
pub fn complete<I, P, O>(input: &mut Pear<I>, p: P) -> input::Result<O, I>
|
||||
where I: Input + Rewind, P: FnOnce(&mut Pear<I>) -> input::Result<O, I>
|
||||
{
|
||||
(p()?, eof()?).0
|
||||
}
|
||||
|
||||
/// TODO: Have a way to ask for for preference in ambiguity resolution.
|
||||
/// * An ordered [Preference] is probably best.
|
||||
/// * Need to filter/uniqueify. See `uri-pref`.
|
||||
/// Once we have this, we should probably set the default so that `authority` is
|
||||
/// preferred over `absolute`, otherwise something like `foo:3122` is absolute.
|
||||
#[parser]
|
||||
pub fn uri<'a>(input: &mut RawInput<'a>) -> Result<'a, Uri<'a>> {
|
||||
match input.items.len() {
|
||||
0 => parse_error!("empty URI")?,
|
||||
1 => switch! {
|
||||
eat(b'*') => Uri::Asterisk,
|
||||
eat(b'/') => Uri::Origin(Origin::new::<_, &str>("/", None)),
|
||||
eat(b'%') => parse_error!("'%' is not a valid URI")?,
|
||||
_ => unsafe {
|
||||
// the `is_reg_name_char` guarantees ASCII
|
||||
let host = Host::Raw(take_n_if(1, is_reg_name_char)?);
|
||||
Uri::Authority(Authority::raw(input.start.into(), None, host, None))
|
||||
}
|
||||
},
|
||||
// NOTE: We accept '%' even when it isn't followed by two hex digits.
|
||||
_ => switch! {
|
||||
peek(b'/') => Uri::Origin(origin()?),
|
||||
_ => absolute_or_authority()?
|
||||
}
|
||||
// To resolve all ambiguities with preference, we might need to look at the
|
||||
// complete string twice: origin/ref, asterisk/ref, authority/absolute.
|
||||
switch! {
|
||||
complete(|i| eat(i, b'*')) => Uri::Asterisk(Asterisk),
|
||||
origin@complete(origin) => Uri::Origin(origin),
|
||||
authority@complete(authority) => Uri::Authority(authority),
|
||||
absolute@complete(absolute) => Uri::Absolute(absolute),
|
||||
_ => Uri::Reference(reference()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
|
||||
(peek(b'/')?, path_and_query(is_pchar, is_qchar)?).1
|
||||
let (_, path, query) = (peek(b'/')?, path()?, query()?);
|
||||
unsafe { Origin::raw(input.start.into(), path.into(), query) }
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn path_and_query<'a, F, Q>(
|
||||
input: &mut RawInput<'a>,
|
||||
is_path_char: F,
|
||||
is_query_char: Q
|
||||
) -> Result<'a, Origin<'a>>
|
||||
where F: Fn(&u8) -> bool + Copy, Q: Fn(&u8) -> bool + Copy
|
||||
{
|
||||
let path = take_while(is_path_char)?;
|
||||
let query = parse_try!(eat(b'?') => take_while(is_query_char)?);
|
||||
pub fn authority<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
||||
let prefix = take_while(is_reg_name_char)?;
|
||||
let (user_info, host, port) = switch! {
|
||||
peek(b'[') if prefix.is_empty() => (None, host()?, port()?),
|
||||
eat(b':') => {
|
||||
let suffix = take_while(is_reg_name_char)?;
|
||||
switch! {
|
||||
peek(b':') => {
|
||||
let end = (take_while(is_user_info_char)?, eat(b'@')?).0;
|
||||
(input.span(prefix, end), host()?, port()?)
|
||||
},
|
||||
eat(b'@') => (input.span(prefix, suffix), host()?, port()?),
|
||||
// FIXME: Rewind to just after prefix to get the right context
|
||||
// to be able to call `port()`. Then remove `maybe_port()`.
|
||||
_ => (None, prefix, maybe_port(&suffix)?)
|
||||
}
|
||||
},
|
||||
eat(b'@') => (Some(prefix), host()?, port()?),
|
||||
_ => (None, prefix, None),
|
||||
};
|
||||
|
||||
if path.is_empty() && query.is_none() {
|
||||
parse_error!("expected path or query, found neither")?
|
||||
} else {
|
||||
// We know the string is ASCII because of the `is_char` checks above.
|
||||
Ok(unsafe { Origin::raw(input.start.into(), path.into(), query.map(|q| q.into())) })
|
||||
unsafe { Authority::raw(input.start.into(), user_info, host, port) }
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn absolute<'a>(input: &mut RawInput<'a>) -> Result<'a, Absolute<'a>> {
|
||||
let scheme = take_some_while(is_scheme_char)?;
|
||||
if !scheme.get(0).map_or(false, |b| b.is_ascii_alphabetic()) {
|
||||
parse_error!("invalid scheme")?;
|
||||
}
|
||||
|
||||
let (_, (authority, path), query) = (eat(b':')?, hier_part()?, query()?);
|
||||
unsafe { Absolute::raw(input.start.into(), scheme, authority, path, query) }
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn reference<'a>(
|
||||
input: &mut RawInput<'a>,
|
||||
) -> Result<'a, Reference<'a>> {
|
||||
let prefix = take_while(is_scheme_char)?;
|
||||
let (scheme, authority, path) = switch! {
|
||||
peek(b':') if prefix.is_empty() => parse_error!("missing scheme")?,
|
||||
eat(b':') => {
|
||||
if !prefix.get(0).map_or(false, |b| b.is_ascii_alphabetic()) {
|
||||
parse_error!("invalid scheme")?;
|
||||
}
|
||||
|
||||
let (authority, path) = hier_part()?;
|
||||
(Some(prefix), authority, path)
|
||||
},
|
||||
peek_slice(b"//") if prefix.is_empty() => {
|
||||
let (authority, path) = hier_part()?;
|
||||
(None, authority, path)
|
||||
},
|
||||
_ => {
|
||||
let path = path()?;
|
||||
let full_path = input.span(prefix, path).unwrap_or(none()?);
|
||||
(None, None, full_path)
|
||||
},
|
||||
};
|
||||
|
||||
let (source, query, fragment) = (input.start.into(), query()?, fragment()?);
|
||||
unsafe { Reference::raw(source, scheme, authority, path, query, fragment) }
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn hier_part<'a>(
|
||||
input: &mut RawInput<'a>
|
||||
) -> Result<'a, (Option<Authority<'a>>, Extent<&'a [u8]>)> {
|
||||
switch! {
|
||||
eat_slice(b"//") => {
|
||||
let authority = authority()?;
|
||||
let path = parse_try!(peek(b'/') => path()? => || none()?);
|
||||
(Some(authority), path)
|
||||
},
|
||||
_ => (None, path()?)
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn port_from<'a>(input: &mut RawInput<'a>, bytes: &[u8]) -> Result<'a, u16> {
|
||||
fn host<'a>(
|
||||
input: &mut RawInput<'a>,
|
||||
) -> Result<'a, Extent<&'a [u8]>> {
|
||||
switch! {
|
||||
peek(b'[') => enclosed(b'[', is_pchar, b']')?,
|
||||
_ => take_while(is_reg_name_char)?
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn port<'a>(
|
||||
input: &mut RawInput<'a>,
|
||||
) -> Result<'a, Option<u16>> {
|
||||
if !succeeds(input, |i| eat(i, b':')) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let bytes = take_n_while(5, |c| c.is_ascii_digit())?;
|
||||
maybe_port(&bytes)?
|
||||
}
|
||||
|
||||
// FIXME: The context here is wrong since it's empty. We should reset to
|
||||
// current - bytes.len(). Or something like that.
|
||||
#[parser]
|
||||
fn maybe_port<'a>(input: &mut RawInput<'a>, bytes: &[u8]) -> Result<'a, Option<u16>> {
|
||||
if bytes.len() > 5 {
|
||||
parse_error!("port len is out of range")?;
|
||||
} else if !bytes.iter().all(|b| b.is_ascii_digit()) {
|
||||
parse_error!("invalid port bytes")?;
|
||||
}
|
||||
|
||||
let mut port_num: u32 = 0;
|
||||
for (b, i) in bytes.iter().rev().zip(&[1, 10, 100, 1000, 10000]) {
|
||||
if !b.is_ascii_digit() {
|
||||
parse_error!("port byte is out of range")?;
|
||||
}
|
||||
|
||||
port_num += (b - b'0') as u32 * i;
|
||||
}
|
||||
|
||||
|
@ -69,123 +170,20 @@ fn port_from<'a>(input: &mut RawInput<'a>, bytes: &[u8]) -> Result<'a, u16> {
|
|||
parse_error!("port out of range: {}", port_num)?;
|
||||
}
|
||||
|
||||
Ok(port_num as u16)
|
||||
Some(port_num as u16)
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn port<'a>(input: &mut RawInput<'a>) -> Result<'a, u16> {
|
||||
let port = take_n_while(5, |c| c.is_ascii_digit())?;
|
||||
port_from(&port)?
|
||||
fn path<'a>(input: &mut RawInput<'a>) -> Result<'a, Extent<&'a [u8]>> {
|
||||
take_while(is_pchar)?
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn authority<'a>(
|
||||
input: &mut RawInput<'a>,
|
||||
user_info: Option<Extent<&'a [u8]>>
|
||||
) -> Result<'a, Authority<'a>> {
|
||||
let host = switch! {
|
||||
peek(b'[') => Host::Bracketed(delimited(b'[', is_pchar, b']')?),
|
||||
_ => Host::Raw(take_while(is_reg_name_char)?)
|
||||
};
|
||||
|
||||
// The `is_pchar`,`is_reg_name_char`, and `port()` functions ensure ASCII.
|
||||
let port = parse_try!(eat(b':') => port()?);
|
||||
unsafe { Authority::raw(input.start.into(), user_info, host, port) }
|
||||
}
|
||||
|
||||
// Callers must ensure that `scheme` is actually ASCII.
|
||||
#[parser]
|
||||
fn absolute<'a>(
|
||||
input: &mut RawInput<'a>,
|
||||
scheme: Extent<&'a [u8]>
|
||||
) -> Result<'a, Absolute<'a>> {
|
||||
let (authority, path_and_query) = switch! {
|
||||
eat_slice(b"://") => {
|
||||
let before_auth = parse_current_marker!();
|
||||
let left = take_while(|c| is_reg_name_char(c) || *c == b':')?;
|
||||
let authority = switch! {
|
||||
eat(b'@') => authority(Some(left))?,
|
||||
_ => {
|
||||
input.rewind_to(before_auth);
|
||||
authority(None)?
|
||||
}
|
||||
};
|
||||
|
||||
let path_and_query = parse_try!(path_and_query(is_pchar, is_qchar));
|
||||
(Some(authority), path_and_query)
|
||||
},
|
||||
eat(b':') => (None, Some(path_and_query(is_pchar, is_qchar)?)),
|
||||
_ => parse_error!("expected ':' but none was found")?
|
||||
};
|
||||
|
||||
// `authority` and `path_and_query` parsers ensure ASCII.
|
||||
unsafe { Absolute::raw(input.start.into(), scheme, authority, path_and_query) }
|
||||
fn query<'a>(input: &mut RawInput<'a>) -> Result<'a, Option<Extent<&'a [u8]>>> {
|
||||
parse_try!(eat(b'?') => take_while(is_qchar)?)
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn authority_only<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
||||
if let Uri::Authority(authority) = absolute_or_authority()? {
|
||||
Ok(authority)
|
||||
} else {
|
||||
parse_error!("expected authority URI but found absolute URI")?
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn absolute_only<'a>(input: &mut RawInput<'a>) -> Result<'a, Absolute<'a>> {
|
||||
if let Uri::Absolute(absolute) = absolute_or_authority()? {
|
||||
Ok(absolute)
|
||||
} else {
|
||||
parse_error!("expected absolute URI but found authority URI")?
|
||||
}
|
||||
}
|
||||
|
||||
#[parser]
|
||||
fn absolute_or_authority<'a>(input: &mut RawInput<'a>) -> Result<'a, Uri<'a>> {
|
||||
// If the URI begins with `:`, it must follow with a `port`.
|
||||
switch! {
|
||||
peek(b':') => return Ok(Uri::Authority(authority(None)?)),
|
||||
}
|
||||
|
||||
let start = parse_current_marker!();
|
||||
let left = take_while(is_reg_name_char)?;
|
||||
let mark_at_left = parse_current_marker!();
|
||||
switch! {
|
||||
peek_slice(b":/") => Uri::Absolute(absolute(left)?),
|
||||
eat(b'@') => Uri::Authority(authority(Some(left))?),
|
||||
take_n_if(1, |b| *b == b':') => {
|
||||
// could be authority or an IP with ':' in it
|
||||
let rest = take_while(|c| is_reg_name_char(c) || *c == b':')?;
|
||||
let authority_here = parse_context!();
|
||||
switch! {
|
||||
eat(b'@') => Uri::Authority(authority(Some(authority_here))?),
|
||||
peek(b'/') => {
|
||||
input.rewind_to(mark_at_left);
|
||||
Uri::Absolute(absolute(left)?)
|
||||
},
|
||||
_ => unsafe {
|
||||
// Here we hit an ambiguity: `rest` could be a port in
|
||||
// host:port or a host in scheme:host. Both are correct
|
||||
// parses. To settle the ambiguity, we assume that if it
|
||||
// looks like a port, it's a port. Otherwise a host. Unless
|
||||
// we have a query, in which case it's definitely a host.
|
||||
let query = parse_try!(eat(b'?') => take_while(is_pchar)?);
|
||||
if query.is_some() || rest.is_empty() || rest.len() > 5 {
|
||||
Uri::raw_absolute(input.start.into(), left, rest, query)
|
||||
} else if let Ok(port) = port_from(input, &rest) {
|
||||
let host = Host::Raw(left);
|
||||
let source = input.start.into();
|
||||
let port = Some(port);
|
||||
Uri::Authority(Authority::raw(source, None, host, port))
|
||||
} else {
|
||||
Uri::raw_absolute(input.start.into(), left, rest, query)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
input.rewind_to(start);
|
||||
Uri::Authority(authority(None)?)
|
||||
}
|
||||
}
|
||||
fn fragment<'a>(input: &mut RawInput<'a>) -> Result<'a, Option<Extent<&'a [u8]>>> {
|
||||
parse_try!(eat(b'#') => take_while(is_qchar)?)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,26 @@
|
|||
RFC 7230 URI Grammar
|
||||
RFC 7230 Uri-Reference Grammar
|
||||
|
||||
URI-reference = URI / relative-ref
|
||||
|
||||
URI = absolute-URI [ "#" fragment ]
|
||||
|
||||
relative-ref = relative-part [ "?" query ] [ "#" fragment ]
|
||||
|
||||
; like hier-part - rootless + noscheme
|
||||
relative-part = "//" authority path-abempty
|
||||
/ path-absolute
|
||||
/ path-noscheme ; like rootless but first seg can't have ':'
|
||||
/ path-empty
|
||||
|
||||
fragment = *( pchar / "/" / "?" ) ;; = query
|
||||
|
||||
# unrolled form of `URI-reference` above, for comparison
|
||||
URI-reference = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
|
||||
/ relative-part [ "?" query ] [ "#" fragment ]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
RFC 7230 Target Grammar
|
||||
|
||||
request-target = origin-form / absolute-form / authority-form / asterisk-form
|
||||
|
||||
|
@ -18,19 +40,6 @@ authority-form = authority
|
|||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
1. look for ':', '@', '?'
|
||||
2. if neither is found, you have an authority, text is `host`
|
||||
3. if ':' is found, have either 'host', 'scheme', or 'userinfo'
|
||||
* can only be host if: next four characters are port
|
||||
* must be host if: text before ':' is empty, requires port
|
||||
* if next (at most) four characters are numbers, then we have a host/port.
|
||||
* if next character is '/' or there is none, then scheme
|
||||
* otherwise try as scheme, fallback to userinfo if find '@'
|
||||
4. if '?' is found, have either 'host', 'scheme', or 'userinfo'
|
||||
5. if '@' is found, have 'userinfo'
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
absolute-form = absolute-URI
|
||||
|
||||
absolute-URI = scheme ":" hier-part [ "?" query ]
|
||||
|
@ -60,6 +69,8 @@ path-empty = 0<pchar>
|
|||
|
||||
segment = *pchar
|
||||
segment-nz = 1*pchar
|
||||
segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
|
||||
; non-zero-length segment without any colon ":"
|
||||
|
||||
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||
|
||||
|
|
|
@ -20,13 +20,16 @@ const fn char_table(sets: &[&[u8]]) -> [u8; 256] {
|
|||
table
|
||||
}
|
||||
|
||||
const UNRESERVED: &[u8] = &[
|
||||
const ALPHA: &[u8] = &[
|
||||
b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L',
|
||||
b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X',
|
||||
b'Y', b'Z', b'a', b'b', b'c', b'd', b'e', b'f', b'g', b'h', b'i', b'j',
|
||||
b'k', b'l', b'm', b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v',
|
||||
b'w', b'x', b'y', b'z', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7',
|
||||
b'8', b'9', b'-', b'.', b'_', b'~',
|
||||
b'w', b'x', b'y', b'z'
|
||||
];
|
||||
|
||||
const DIGIT: &[u8] = &[
|
||||
b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9'
|
||||
];
|
||||
|
||||
const PCT_ENCODED: &[u8] = &[
|
||||
|
@ -38,8 +41,24 @@ const SUB_DELIMS: &[u8] = &[
|
|||
b'!', b'$', b'&', b'\'', b'(', b')', b'*', b'+', b',', b';', b'='
|
||||
];
|
||||
|
||||
const SCHEME_CHAR: [u8; 256] = char_table(&[
|
||||
ALPHA, DIGIT, &[b'+', b'-', b'.']
|
||||
]);
|
||||
|
||||
const UNRESERVED: [u8; 256] = char_table(&[
|
||||
ALPHA, DIGIT, &[b'-', b'.', b'_', b'~']
|
||||
]);
|
||||
|
||||
const REG_NAME_CHARS: [u8; 256] = char_table(&[
|
||||
&UNRESERVED, PCT_ENCODED, SUB_DELIMS
|
||||
]);
|
||||
|
||||
const USER_INFO_CHARS: [u8; 256] = char_table(&[
|
||||
®_NAME_CHARS, &[b':']
|
||||
]);
|
||||
|
||||
pub const PATH_CHARS: [u8; 256] = char_table(&[
|
||||
UNRESERVED, PCT_ENCODED, SUB_DELIMS, &[b':', b'@', b'/']
|
||||
®_NAME_CHARS, &[b':', b'@', b'/']
|
||||
]);
|
||||
|
||||
const QUERY_CHARS: [u8; 256] = char_table(&[
|
||||
|
@ -50,13 +69,15 @@ const QUERY_CHARS: [u8; 256] = char_table(&[
|
|||
&[b'{', b'}', b'[', b']', b'\\', b'^', b'`', b'|'],
|
||||
]);
|
||||
|
||||
const REG_NAME_CHARS: [u8; 256] = char_table(&[
|
||||
UNRESERVED, PCT_ENCODED, SUB_DELIMS
|
||||
]);
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_pchar(&c: &u8) -> bool { PATH_CHARS[c as usize] != 0 }
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_scheme_char(&c: &u8) -> bool { SCHEME_CHAR[c as usize] != 0 }
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_user_info_char(&c: &u8) -> bool { USER_INFO_CHARS[c as usize] != 0 }
|
||||
|
||||
#[inline(always)]
|
||||
pub const fn is_qchar(&c: &u8) -> bool { QUERY_CHARS[c as usize] != 0 }
|
||||
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
use crate::uri::{Uri, Origin, Authority, Absolute};
|
||||
use crate::uri::{Origin, Authority, Absolute, Asterisk};
|
||||
use crate::parse::uri::*;
|
||||
use crate::uri::Host::*;
|
||||
|
||||
macro_rules! assert_parse_eq {
|
||||
($($from:expr => $to:expr),+) => (
|
||||
($($from:expr => $to:expr),+ $(,)?) => (
|
||||
$(
|
||||
let expected = $to.into();
|
||||
let expected = $to;
|
||||
match from_str($from) {
|
||||
Ok(output) => {
|
||||
if output != expected {
|
||||
println!("Failure on: {:?}", $from);
|
||||
assert_eq!(output, expected);
|
||||
assert_eq!(output, expected, "{} != {}", output, expected);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
|
@ -20,12 +19,10 @@ macro_rules! assert_parse_eq {
|
|||
}
|
||||
)+
|
||||
);
|
||||
|
||||
($($from:expr => $to:expr),+,) => (assert_parse_eq!($($from => $to),+))
|
||||
}
|
||||
|
||||
macro_rules! assert_no_parse {
|
||||
($($from:expr),+) => (
|
||||
($($from:expr),+ $(,)?) => (
|
||||
$(
|
||||
if let Ok(uri) = from_str($from) {
|
||||
println!("{:?} parsed unexpectedly!", $from);
|
||||
|
@ -38,7 +35,7 @@ macro_rules! assert_no_parse {
|
|||
}
|
||||
|
||||
macro_rules! assert_parse {
|
||||
($($from:expr),+) => (
|
||||
($($from:expr),+ $(,)?) => (
|
||||
$(
|
||||
if let Err(e) = from_str($from) {
|
||||
println!("{:?} failed to parse", $from);
|
||||
|
@ -46,12 +43,10 @@ macro_rules! assert_parse {
|
|||
}
|
||||
)+
|
||||
);
|
||||
|
||||
($($from:expr),+,) => (assert_parse!($($from),+))
|
||||
}
|
||||
|
||||
macro_rules! assert_displays_eq {
|
||||
($($string:expr),+) => (
|
||||
($($string:expr),+ $(,)?) => (
|
||||
$(
|
||||
let string = $string.into();
|
||||
match from_str(string) {
|
||||
|
@ -75,20 +70,19 @@ macro_rules! assert_displays_eq {
|
|||
($($string:expr),+,) => (assert_parse_eq!($($string),+))
|
||||
}
|
||||
|
||||
fn uri_origin<'a>(path: &'a str, query: Option<&'a str>) -> Uri<'a> {
|
||||
Uri::Origin(Origin::new(path, query))
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_assert_parse_eq() {
|
||||
assert_parse_eq!("*" => uri_origin("*", None));
|
||||
assert_parse_eq!("*" => Origin::path_only("*"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_assert_parse_eq_consecutive() {
|
||||
assert_parse_eq!("/" => uri_origin("/", None), "/" => Uri::Asterisk);
|
||||
assert_parse_eq! {
|
||||
"/" => Origin::ROOT,
|
||||
"/" => Asterisk
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -103,13 +97,14 @@ fn bad_parses() {
|
|||
"://z7:77777777777777777777777777777`77777777777",
|
||||
|
||||
// from #1621
|
||||
"://@example.com/test",
|
||||
"://example.com:/test",
|
||||
"://@example.com:/test",
|
||||
"://example.com/test?",
|
||||
"://example.com:/test?",
|
||||
"://@example.com/test?",
|
||||
"://@example.com:/test?"
|
||||
":/",
|
||||
|
||||
// almost URIs
|
||||
":/",
|
||||
"://",
|
||||
"::",
|
||||
":::",
|
||||
"a://a::",
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -129,144 +124,153 @@ fn test_parse_issue_924_samples() {
|
|||
"/rocket/?query=%3E5",
|
||||
);
|
||||
|
||||
assert_no_parse!("/rocket/?query=>5", "/?#foo");
|
||||
assert_no_parse!("/rocket/?query=>5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_byte() {
|
||||
assert_parse_eq!(
|
||||
"*" => Uri::Asterisk,
|
||||
"/" => uri_origin("/", None),
|
||||
"." => Authority::new(None, Raw("."), None),
|
||||
"_" => Authority::new(None, Raw("_"), None),
|
||||
"1" => Authority::new(None, Raw("1"), None),
|
||||
"b" => Authority::new(None, Raw("b"), None),
|
||||
"*" => Asterisk,
|
||||
"/" => Origin::ROOT,
|
||||
"." => Authority::new(None, ".", None),
|
||||
"_" => Authority::new(None, "_", None),
|
||||
"1" => Authority::new(None, "1", None),
|
||||
"b" => Authority::new(None, "b", None),
|
||||
"%" => Authority::new(None, "%", None),
|
||||
"?" => Reference::new(None, None, "", "", None),
|
||||
"#" => Reference::new(None, None, "", None, ""),
|
||||
":" => Authority::new(None, "", 0),
|
||||
"@" => Authority::new("", "", None),
|
||||
);
|
||||
|
||||
assert_no_parse!("?", "#", "%");
|
||||
assert_no_parse!("[", "]");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn origin() {
|
||||
assert_parse_eq!(
|
||||
"/a/b/c" => uri_origin("/a/b/c", None),
|
||||
"/a/b/c?" => uri_origin("/a/b/c", Some("")),
|
||||
"/a/b/c?abc" => uri_origin("/a/b/c", Some("abc")),
|
||||
"/a/b/c???" => uri_origin("/a/b/c", Some("??")),
|
||||
"/a/b/c?a?b?" => uri_origin("/a/b/c", Some("a?b?")),
|
||||
"/a/b/c?a?b?/c" => uri_origin("/a/b/c", Some("a?b?/c")),
|
||||
"/?abc" => uri_origin("/", Some("abc")),
|
||||
"/hi%20there?a=b&c=d" => uri_origin("/hi%20there", Some("a=b&c=d")),
|
||||
"/c/d/fa/b/c?abc" => uri_origin("/c/d/fa/b/c", Some("abc")),
|
||||
"/xn--ls8h?emoji=poop" => uri_origin("/xn--ls8h", Some("emoji=poop")),
|
||||
"/?t=[rocket|is\\here^`]&{ok}" => uri_origin("/", Some("t=[rocket|is\\here^`]&{ok}")),
|
||||
"/a/b/c" => Origin::path_only("/a/b/c"),
|
||||
"//" => Origin::path_only("//"),
|
||||
"///" => Origin::path_only("///"),
|
||||
"////" => Origin::path_only("////"),
|
||||
"/a/b/c?" => Origin::new("/a/b/c", Some("")),
|
||||
"/a/b/c?abc" => Origin::new("/a/b/c", Some("abc")),
|
||||
"/a/b/c???" => Origin::new("/a/b/c", Some("??")),
|
||||
"/a/b/c?a?b?" => Origin::new("/a/b/c", Some("a?b?")),
|
||||
"/a/b/c?a?b?/c" => Origin::new("/a/b/c", Some("a?b?/c")),
|
||||
"/?abc" => Origin::new("/", Some("abc")),
|
||||
"/hi%20there?a=b&c=d" => Origin::new("/hi%20there", Some("a=b&c=d")),
|
||||
"/c/d/fa/b/c?abc" => Origin::new("/c/d/fa/b/c", Some("abc")),
|
||||
"/xn--ls8h?emoji=poop" => Origin::new("/xn--ls8h", Some("emoji=poop")),
|
||||
"/?t=[rocket|is\\here^`]&{ok}" => Origin::new("/", Some("t=[rocket|is\\here^`]&{ok}")),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn authority() {
|
||||
assert_parse_eq!(
|
||||
"abc" => Authority::new(None, Raw("abc"), None),
|
||||
"@abc" => Authority::new(Some(""), Raw("abc"), None),
|
||||
"sergio:benitez@spark" => Authority::new(Some("sergio:benitez"), Raw("spark"), None),
|
||||
"a:b:c@1.2.3:12121" => Authority::new(Some("a:b:c"), Raw("1.2.3"), Some(12121)),
|
||||
"sergio@spark" => Authority::new(Some("sergio"), Raw("spark"), None),
|
||||
"sergio@spark:230" => Authority::new(Some("sergio"), Raw("spark"), Some(230)),
|
||||
"sergio@[1::]:230" => Authority::new(Some("sergio"), Bracketed("1::"), Some(230)),
|
||||
"google.com:8000" => Authority::new(None, Raw("google.com"), Some(8000)),
|
||||
"[1::2::3]:80" => Authority::new(None, Bracketed("1::2::3"), Some(80)),
|
||||
"@:" => Authority::new("", "", 0),
|
||||
"abc" => Authority::new(None, "abc", None),
|
||||
"@abc" => Authority::new("", "abc", None),
|
||||
"a@b" => Authority::new("a", "b", None),
|
||||
"a@" => Authority::new("a", "", None),
|
||||
":@" => Authority::new(":", "", None),
|
||||
":@:" => Authority::new(":", "", 0),
|
||||
"sergio:benitez@spark" => Authority::new("sergio:benitez", "spark", None),
|
||||
"a:b:c@1.2.3:12121" => Authority::new("a:b:c", "1.2.3", 12121),
|
||||
"sergio@spark" => Authority::new("sergio", "spark", None),
|
||||
"sergio@spark:230" => Authority::new("sergio", "spark", 230),
|
||||
"sergio@[1::]:230" => Authority::new("sergio", "[1::]", 230),
|
||||
"rocket.rs:8000" => Authority::new(None, "rocket.rs", 8000),
|
||||
"[1::2::3]:80" => Authority::new(None, "[1::2::3]", 80),
|
||||
"bar:" => Authority::new(None, "bar", 0), // could be absolute too
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn absolute() {
|
||||
assert_parse_eq! {
|
||||
"http://foo.com:8000" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("foo.com"), Some(8000))),
|
||||
None
|
||||
),
|
||||
"http://foo:8000" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("foo"), Some(8000))),
|
||||
None,
|
||||
),
|
||||
"foo:bar" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new::<_, &str>("bar", None)),
|
||||
),
|
||||
"http://sergio:pass@foo.com:8000" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(Some("sergio:pass"), Raw("foo.com"), Some(8000))),
|
||||
None,
|
||||
),
|
||||
"foo:/sergio/pass?hi" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new("/sergio/pass", Some("hi"))),
|
||||
),
|
||||
"bar:" => Absolute::new(
|
||||
"bar",
|
||||
None,
|
||||
Some(Origin::new::<_, &str>("", None)),
|
||||
),
|
||||
"foo:?hi" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new("", Some("hi"))),
|
||||
),
|
||||
"foo:a/b?hi" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new("a/b", Some("hi"))),
|
||||
),
|
||||
"foo:a/b" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new::<_, &str>("a/b", None)),
|
||||
),
|
||||
"foo:/a/b" => Absolute::new(
|
||||
"foo",
|
||||
None,
|
||||
Some(Origin::new::<_, &str>("/a/b", None))
|
||||
),
|
||||
"abc://u:p@foo.com:123/a/b?key=value&key2=value2" => Absolute::new(
|
||||
"abc",
|
||||
Some(Authority::new(Some("u:p"), Raw("foo.com"), Some(123))),
|
||||
Some(Origin::new("/a/b", Some("key=value&key2=value2"))),
|
||||
),
|
||||
"ftp://foo.com:21/abc" => Absolute::new(
|
||||
"ftp",
|
||||
Some(Authority::new(None, Raw("foo.com"), Some(21))),
|
||||
Some(Origin::new::<_, &str>("/abc", None)),
|
||||
),
|
||||
"http://google.com/abc" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("google.com"), None)),
|
||||
Some(Origin::new::<_, &str>("/abc", None)),
|
||||
),
|
||||
"http://google.com" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("google.com"), None)),
|
||||
None
|
||||
),
|
||||
"http://foo.com?test" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("foo.com"), None,)),
|
||||
Some(Origin::new("", Some("test"))),
|
||||
),
|
||||
"http://google.com/abc?hi" => Absolute::new(
|
||||
"http",
|
||||
Some(Authority::new(None, Raw("google.com"), None,)),
|
||||
Some(Origin::new("/abc", Some("hi"))),
|
||||
),
|
||||
"http:/" => Absolute::new("http", None, "/", None),
|
||||
"http://" => Absolute::new("http", Authority::new(None, "", None), "", None),
|
||||
"http:///" => Absolute::new("http", Authority::new(None, "", None), "/", None),
|
||||
"http://a.com:8000" => Absolute::new("http", Authority::new(None, "a.com", 8000), "", None),
|
||||
"http://foo:8000" => Absolute::new("http", Authority::new(None, "foo", 8000), "", None),
|
||||
"foo:bar" => Absolute::new("foo", None, "bar", None),
|
||||
"ftp:::" => Absolute::new("ftp", None, "::", None),
|
||||
"ftp:::?bar" => Absolute::new("ftp", None, "::", "bar"),
|
||||
"http://:::@a.b.c.:8000" =>
|
||||
Absolute::new("http", Authority::new(":::", "a.b.c.", 8000), "", None),
|
||||
"http://sergio:pass@foo.com:8000" =>
|
||||
Absolute::new("http", Authority::new("sergio:pass", "foo.com", 8000), "", None),
|
||||
"foo:/sergio/pass?hi" => Absolute::new("foo", None, "/sergio/pass", "hi"),
|
||||
"foo:?hi" => Absolute::new("foo", None, "", "hi"),
|
||||
"foo:a/b" => Absolute::new("foo", None, "a/b", None),
|
||||
"foo:a/b?" => Absolute::new("foo", None, "a/b", ""),
|
||||
"foo:a/b?hi" => Absolute::new("foo", None, "a/b", "hi"),
|
||||
"foo:/a/b" => Absolute::new("foo", None, "/a/b", None),
|
||||
"abc://u:p@foo.com:123/a/b?key=value&key2=value2" =>
|
||||
Absolute::new("abc",
|
||||
Authority::new("u:p", "foo.com", 123),
|
||||
"/a/b", "key=value&key2=value2"),
|
||||
"ftp://foo.com:21/abc" =>
|
||||
Absolute::new("ftp", Authority::new(None, "foo.com", 21), "/abc", None),
|
||||
"http://rocket.rs/abc" =>
|
||||
Absolute::new("http", Authority::new(None, "rocket.rs", None), "/abc", None),
|
||||
"http://s:b@rocket.rs/abc" =>
|
||||
Absolute::new("http", Authority::new("s:b", "rocket.rs", None), "/abc", None),
|
||||
"http://rocket.rs/abc?q" =>
|
||||
Absolute::new("http", Authority::new(None, "rocket.rs", None), "/abc", "q"),
|
||||
"http://rocket.rs" =>
|
||||
Absolute::new("http", Authority::new(None, "rocket.rs", None), "", None),
|
||||
"git://s::@rocket.rs:443/abc?q" =>
|
||||
Absolute::new("git", Authority::new("s::", "rocket.rs", 443), "/abc", "q"),
|
||||
"git://:@rocket.rs:443/abc?q" =>
|
||||
Absolute::new("git", Authority::new(":", "rocket.rs", 443), "/abc", "q"),
|
||||
"a://b?test" => Absolute::new("a", Authority::new(None, "b", None), "", "test"),
|
||||
"a://b:?test" => Absolute::new("a", Authority::new(None, "b", 0), "", "test"),
|
||||
"a://b:1?test" => Absolute::new("a", Authority::new(None, "b", 1), "", "test"),
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference() {
|
||||
assert_parse_eq!(
|
||||
"*#" => Reference::new(None, None, "*", None, ""),
|
||||
"*#h" => Reference::new(None, None, "*", None, "h"),
|
||||
"@/" => Reference::new(None, None, "@/", None, None),
|
||||
"@?" => Reference::new(None, None, "@", "", None),
|
||||
"@?#" => Reference::new(None, None, "@", "", ""),
|
||||
"@#foo" => Reference::new(None, None, "@", None, "foo"),
|
||||
"foo/bar" => Reference::new(None, None, "foo/bar", None, None),
|
||||
"foo/bar?baz" => Reference::new(None, None, "foo/bar", "baz", None),
|
||||
"foo/bar?baz#cat" => Reference::new(None, None, "foo/bar", "baz", "cat"),
|
||||
"a?b#c" => Reference::new(None, None, "a", "b", "c"),
|
||||
"?#" => Reference::new(None, None, "", "", ""),
|
||||
"ftp:foo/bar?baz#" => Reference::new("ftp", None, "foo/bar", "baz", ""),
|
||||
"ftp:bar#" => Reference::new("ftp", None, "bar", None, ""),
|
||||
"ftp:?bar#" => Reference::new("ftp", None, "", "bar", ""),
|
||||
"ftp:::?bar#" => Reference::new("ftp", None, "::", "bar", ""),
|
||||
"#foo" => Reference::new(None, None, "", None, "foo"),
|
||||
"a:/#" => Reference::new("a", None, "/", None, ""),
|
||||
"a:/?a#" => Reference::new("a", None, "/", "a", ""),
|
||||
"a:/?a#b" => Reference::new("a", None, "/", "a", "b"),
|
||||
"a:?a#b" => Reference::new("a", None, "", "a", "b"),
|
||||
"a://?a#b" => Reference::new("a", Authority::new(None, "", None), "", "a", "b"),
|
||||
"a://:?a#b" => Reference::new("a", Authority::new(None, "", 0), "", "a", "b"),
|
||||
"a://:2000?a#b" => Reference::new("a", Authority::new(None, "", 2000), "", "a", "b"),
|
||||
"a://a:2000?a#b" => Reference::new("a", Authority::new(None, "a", 2000), "", "a", "b"),
|
||||
"a://a:@2000?a#b" => Reference::new("a", Authority::new("a:", "2000", None), "", "a", "b"),
|
||||
"a://a:@:80?a#b" => Reference::new("a", Authority::new("a:", "", 80), "", "a", "b"),
|
||||
"a://a:@b:80?a#b" => Reference::new("a", Authority::new("a:", "b", 80), "", "a", "b"),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display() {
|
||||
assert_displays_eq! {
|
||||
"abc", "@):0", "[a]"
|
||||
"abc", "@):0", "[a]",
|
||||
"http://rocket.rs", "http://a:b@rocket.rs", "git://a@b:800/foo?bar",
|
||||
"git://a@b:800/foo?bar#baz",
|
||||
"a:b", "a@b", "a?b", "a?b#c",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ use std::fmt;
|
|||
|
||||
use ref_cast::RefCast;
|
||||
use stable_pattern::{Pattern, Searcher, ReverseSearcher, Split, SplitInternal};
|
||||
use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||
use crate::uri::fmt::{percent_encode, DEFAULT_ENCODE_SET};
|
||||
|
||||
use crate::uncased::UncasedStr;
|
||||
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Display};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crate::ext::IntoOwned;
|
||||
use crate::parse::{Extent, IndexedStr};
|
||||
use crate::uri::{Authority, Origin, Error, as_utf8_unchecked};
|
||||
use crate::uri::{Authority, Path, Query, Data, Error, as_utf8_unchecked, fmt};
|
||||
|
||||
/// A URI with a scheme, authority, path, and query:
|
||||
/// `http://user:pass@domain.com:4444/path?query`.
|
||||
/// A URI with a scheme, authority, path, and query.
|
||||
///
|
||||
/// # Structure
|
||||
///
|
||||
|
@ -14,19 +13,64 @@ use crate::uri::{Authority, Origin, Error, as_utf8_unchecked};
|
|||
/// URI with all optional parts:
|
||||
///
|
||||
/// ```text
|
||||
/// http://user:pass@domain.com:4444/path?query
|
||||
/// |--| |-----------------------||---------|
|
||||
/// scheme authority origin
|
||||
/// http://user:pass@domain.com:4444/foo/bar?some=query
|
||||
/// |--| |------------------------||------| |--------|
|
||||
/// scheme authority path query
|
||||
/// ```
|
||||
///
|
||||
/// The scheme part of the absolute URI and at least one of authority or origin
|
||||
/// are required.
|
||||
/// Only the scheme part of the URI is required.
|
||||
///
|
||||
/// # Normalization
|
||||
///
|
||||
/// Rocket prefers _normalized_ absolute URIs, an absolute URI with the
|
||||
/// following properties:
|
||||
///
|
||||
/// * The path and query, if any, are normalized with no empty segments.
|
||||
/// * If there is an authority, the path is empty or absolute with more than
|
||||
/// one character.
|
||||
///
|
||||
/// The [`Absolute::is_normalized()`] method checks for normalization while
|
||||
/// [`Absolute::into_normalized()`] normalizes any absolute URI.
|
||||
///
|
||||
/// As an example, the following URIs are all valid, normalized URIs:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # use rocket::http::uri::Absolute;
|
||||
/// # let valid_uris = [
|
||||
/// "http://rocket.rs",
|
||||
/// "scheme:/foo/bar",
|
||||
/// "scheme:/foo/bar?abc",
|
||||
/// # ];
|
||||
/// # for uri in &valid_uris {
|
||||
/// # let uri = Absolute::parse(uri).unwrap();
|
||||
/// # assert!(uri.is_normalized(), "{} non-normal?", uri);
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// By contrast, the following are valid but non-normal URIs:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # use rocket::http::uri::Absolute;
|
||||
/// # let invalid = [
|
||||
/// "http://rocket.rs/", // trailing '/'
|
||||
/// "ftp:/a/b/", // trailing empty segment
|
||||
/// "ftp:/a//c//d", // two empty segments
|
||||
/// "ftp:/a/b/?", // empty path segment
|
||||
/// "ftp:/?foo&", // trailing empty query segment
|
||||
/// # ];
|
||||
/// # for uri in &invalid {
|
||||
/// # assert!(!Absolute::parse(uri).unwrap().is_normalized());
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Absolute<'a> {
|
||||
source: Option<Cow<'a, str>>,
|
||||
scheme: IndexedStr<'a>,
|
||||
authority: Option<Authority<'a>>,
|
||||
origin: Option<Origin<'a>>,
|
||||
pub(crate) source: Option<Cow<'a, str>>,
|
||||
pub(crate) scheme: IndexedStr<'a>,
|
||||
pub(crate) authority: Option<Authority<'a>>,
|
||||
pub(crate) path: Data<'a, fmt::Path>,
|
||||
pub(crate) query: Option<Data<'a, fmt::Query>>,
|
||||
}
|
||||
|
||||
impl IntoOwned for Absolute<'_> {
|
||||
|
@ -37,53 +81,35 @@ impl IntoOwned for Absolute<'_> {
|
|||
source: self.source.into_owned(),
|
||||
scheme: self.scheme.into_owned(),
|
||||
authority: self.authority.into_owned(),
|
||||
origin: self.origin.into_owned(),
|
||||
path: self.path.into_owned(),
|
||||
query: self.query.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Absolute<'a> {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn raw(
|
||||
source: Cow<'a, [u8]>,
|
||||
scheme: Extent<&'a [u8]>,
|
||||
authority: Option<Authority<'a>>,
|
||||
origin: Option<Origin<'a>>,
|
||||
) -> Absolute<'a> {
|
||||
Absolute {
|
||||
authority, origin,
|
||||
source: Some(as_utf8_unchecked(source)),
|
||||
scheme: scheme.into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(
|
||||
scheme: &'a str,
|
||||
authority: Option<Authority<'a>>,
|
||||
origin: Option<Origin<'a>>
|
||||
) -> Absolute<'a> {
|
||||
Absolute {
|
||||
authority, origin,
|
||||
source: None,
|
||||
scheme: Cow::Borrowed(scheme).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the string `string` into an `Absolute`. Parsing will never
|
||||
/// allocate. Returns an `Error` if `string` is not a valid absolute URI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// // Parse a valid authority URI.
|
||||
/// let uri = Absolute::parse("http://google.com").expect("valid URI");
|
||||
/// assert_eq!(uri.scheme(), "http");
|
||||
/// assert_eq!(uri.authority().unwrap().host(), "google.com");
|
||||
/// assert_eq!(uri.origin(), None);
|
||||
/// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
|
||||
/// assert_eq!(uri.scheme(), "https");
|
||||
/// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
|
||||
/// assert_eq!(uri.path(), "");
|
||||
/// assert!(uri.query().is_none());
|
||||
///
|
||||
/// // Prefer to use `uri!()` when the input is statically known:
|
||||
/// let uri = uri!("https://rocket.rs");
|
||||
/// assert_eq!(uri.scheme(), "https");
|
||||
/// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
|
||||
/// assert_eq!(uri.path(), "");
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Absolute<'a>, Error<'a>> {
|
||||
crate::parse::uri::absolute_from_str(string)
|
||||
|
@ -94,10 +120,8 @@ impl<'a> Absolute<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// let uri = Absolute::parse("ftp://127.0.0.1").expect("valid URI");
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("ftp://127.0.0.1");
|
||||
/// assert_eq!(uri.scheme(), "ftp");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
|
@ -110,16 +134,14 @@ impl<'a> Absolute<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// let uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("https://rocket.rs:80");
|
||||
/// assert_eq!(uri.scheme(), "https");
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), Some(80));
|
||||
///
|
||||
/// let uri = Absolute::parse("file:/web/home").expect("valid URI");
|
||||
/// let uri = uri!("file:/web/home");
|
||||
/// assert_eq!(uri.authority(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
|
@ -127,50 +149,154 @@ impl<'a> Absolute<'a> {
|
|||
self.authority.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the origin part of the absolute URI, if there is one.
|
||||
/// Returns the path part. May be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("ftp://rocket.rs/foo/bar");
|
||||
/// assert_eq!(uri.path(), "/foo/bar");
|
||||
///
|
||||
/// let uri = Absolute::parse("file:/web/home.html?new").expect("valid URI");
|
||||
/// assert_eq!(uri.scheme(), "file");
|
||||
/// let origin = uri.origin().unwrap();
|
||||
/// assert_eq!(origin.path(), "/web/home.html");
|
||||
/// assert_eq!(origin.query().unwrap(), "new");
|
||||
///
|
||||
/// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
|
||||
/// assert_eq!(uri.origin(), None);
|
||||
/// let uri = uri!("ftp://rocket.rs");
|
||||
/// assert!(uri.path().is_empty());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn origin(&self) -> Option<&Origin<'a>> {
|
||||
self.origin.as_ref()
|
||||
pub fn path(&self) -> Path<'_> {
|
||||
Path { source: &self.source, data: &self.path }
|
||||
}
|
||||
|
||||
/// Sets the authority in `self` to `authority` and returns `self`.
|
||||
/// Returns the query part with the leading `?`. May be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::{Absolute, Authority};
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("ftp://rocket.rs/foo?bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "bar");
|
||||
///
|
||||
/// let uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), Some(80));
|
||||
///
|
||||
/// let new_authority = Authority::parse("google.com").unwrap();
|
||||
/// let uri = uri.with_authority(new_authority);
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "google.com");
|
||||
/// assert_eq!(authority.port(), None);
|
||||
/// let uri = uri!("ftp://rocket.rs");
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
|
||||
self.set_authority(authority);
|
||||
pub fn query(&self) -> Option<Query<'_>> {
|
||||
self.query.as_ref().map(|data| Query { source: &self.source, data })
|
||||
}
|
||||
|
||||
/// Removes the query part of this URI, if there is any.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let mut uri = uri!("ftp://rocket.rs/foo?bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "bar");
|
||||
///
|
||||
/// uri.clear_query();
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn clear_query(&mut self) {
|
||||
self.set_query(None);
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||
///
|
||||
/// See [Normalization](#normalization) for more information on what it
|
||||
/// means for an absolute URI to be normalized. Note that `uri!()` always
|
||||
/// returns a normalized version of its static input.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// assert!(uri!("http://rocket.rs").is_normalized());
|
||||
/// assert!(uri!("http://rocket.rs///foo////bar").is_normalized());
|
||||
///
|
||||
/// assert!(Absolute::parse("http:/").unwrap().is_normalized());
|
||||
/// assert!(Absolute::parse("http://").unwrap().is_normalized());
|
||||
/// assert!(Absolute::parse("http://foo.rs/foo/bar").unwrap().is_normalized());
|
||||
/// assert!(Absolute::parse("foo:bar").unwrap().is_normalized());
|
||||
///
|
||||
/// assert!(!Absolute::parse("git://rocket.rs/").unwrap().is_normalized());
|
||||
/// assert!(!Absolute::parse("http:/foo//bar").unwrap().is_normalized());
|
||||
/// assert!(!Absolute::parse("foo:bar?baz&&bop").unwrap().is_normalized());
|
||||
/// ```
|
||||
pub fn is_normalized(&self) -> bool {
|
||||
let normalized_query = self.query().map_or(true, |q| q.is_normalized());
|
||||
if self.authority().is_some() && !self.path().is_empty() {
|
||||
self.path().is_normalized(true)
|
||||
&& self.path() != "/"
|
||||
&& normalized_query
|
||||
} else {
|
||||
self.path().is_normalized(false) && normalized_query
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes `self` in-place. Does nothing if `self` is already
|
||||
/// normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// let mut uri = Absolute::parse("git://rocket.rs/").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
///
|
||||
/// let mut uri = Absolute::parse("http:/foo//bar").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
///
|
||||
/// let mut uri = Absolute::parse("foo:bar?baz&&bop").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
/// ```
|
||||
pub fn normalize(&mut self) {
|
||||
if self.authority().is_some() && !self.path().is_empty() {
|
||||
if self.path() == "/" {
|
||||
self.set_path("");
|
||||
} else if !self.path().is_normalized(true) {
|
||||
self.path = self.path().to_normalized(true);
|
||||
}
|
||||
} else {
|
||||
self.path = self.path().to_normalized(false);
|
||||
}
|
||||
|
||||
if let Some(query) = self.query() {
|
||||
if !query.is_normalized() {
|
||||
self.query = query.to_normalized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes `self`. This is a no-op if `self` is already normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Absolute;
|
||||
///
|
||||
/// let mut uri = Absolute::parse("git://rocket.rs/").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
///
|
||||
/// let mut uri = Absolute::parse("http:/foo//bar").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
///
|
||||
/// let mut uri = Absolute::parse("foo:bar?baz&&bop").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
/// ```
|
||||
pub fn into_normalized(mut self) -> Self {
|
||||
self.normalize();
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -179,18 +305,16 @@ impl<'a> Absolute<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::{Absolute, Authority};
|
||||
///
|
||||
/// let mut uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let mut uri = uri!("https://rocket.rs:80");
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), Some(80));
|
||||
///
|
||||
/// let new_authority = Authority::parse("google.com:443").unwrap();
|
||||
/// let new_authority = uri!("rocket.rs:443");
|
||||
/// uri.set_authority(new_authority);
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "google.com");
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), Some(443));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
|
@ -198,53 +322,117 @@ impl<'a> Absolute<'a> {
|
|||
self.authority = Some(authority);
|
||||
}
|
||||
|
||||
/// Sets the origin in `self` to `origin` and returns `self`.
|
||||
/// Sets the authority in `self` to `authority` and returns `self`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::{Absolute, Origin};
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("https://rocket.rs:80");
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), Some(80));
|
||||
///
|
||||
/// let mut uri = Absolute::parse("http://rocket.rs/web/?new").unwrap();
|
||||
/// let origin = uri.origin().unwrap();
|
||||
/// assert_eq!(origin.path(), "/web/");
|
||||
/// assert_eq!(origin.query().unwrap(), "new");
|
||||
///
|
||||
/// let new_origin = Origin::parse("/launch").unwrap();
|
||||
/// let uri = uri.with_origin(new_origin);
|
||||
/// let origin = uri.origin().unwrap();
|
||||
/// assert_eq!(origin.path(), "/launch");
|
||||
/// assert_eq!(origin.query(), None);
|
||||
/// let new_authority = uri!("rocket.rs");
|
||||
/// let uri = uri.with_authority(new_authority);
|
||||
/// let authority = uri.authority().unwrap();
|
||||
/// assert_eq!(authority.host(), "rocket.rs");
|
||||
/// assert_eq!(authority.port(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn with_origin(mut self, origin: Origin<'a>) -> Self {
|
||||
self.set_origin(origin);
|
||||
pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
|
||||
self.set_authority(authority);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the origin in `self` to `origin`.
|
||||
/// PRIVATE API.
|
||||
#[doc(hidden)]
|
||||
impl<'a> Absolute<'a> {
|
||||
/// PRIVATE. Used by parser.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::{Absolute, Origin};
|
||||
///
|
||||
/// let mut uri = Absolute::parse("http://rocket.rs/web/?new").unwrap();
|
||||
/// let origin = uri.origin().unwrap();
|
||||
/// assert_eq!(origin.path(), "/web/");
|
||||
/// assert_eq!(origin.query().unwrap(), "new");
|
||||
///
|
||||
/// let new_origin = Origin::parse("/launch?when=now").unwrap();
|
||||
/// uri.set_origin(new_origin);
|
||||
/// let origin = uri.origin().unwrap();
|
||||
/// assert_eq!(origin.path(), "/launch");
|
||||
/// assert_eq!(origin.query().unwrap(), "when=now");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn set_origin(&mut self, origin: Origin<'a>) {
|
||||
self.origin = Some(origin);
|
||||
/// SAFETY: `source` must be valid UTF-8.
|
||||
/// CORRECTNESS: `scheme` must be non-empty.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn raw(
|
||||
source: Cow<'a, [u8]>,
|
||||
scheme: Extent<&'a [u8]>,
|
||||
authority: Option<Authority<'a>>,
|
||||
path: Extent<&'a [u8]>,
|
||||
query: Option<Extent<&'a [u8]>>,
|
||||
) -> Absolute<'a> {
|
||||
Absolute {
|
||||
source: Some(as_utf8_unchecked(source)),
|
||||
scheme: scheme.into(),
|
||||
authority,
|
||||
path: Data::raw(path),
|
||||
query: query.map(Data::raw)
|
||||
}
|
||||
}
|
||||
|
||||
/// PRIVATE. Used by tests.
|
||||
#[cfg(test)]
|
||||
pub fn new(
|
||||
scheme: &'a str,
|
||||
authority: impl Into<Option<Authority<'a>>>,
|
||||
path: &'a str,
|
||||
query: impl Into<Option<&'a str>>,
|
||||
) -> Absolute<'a> {
|
||||
assert!(!scheme.is_empty());
|
||||
Absolute::const_new(scheme, authority.into(), path, query.into())
|
||||
}
|
||||
|
||||
/// PRIVATE. Used by codegen.
|
||||
pub const fn const_new(
|
||||
scheme: &'a str,
|
||||
authority: Option<Authority<'a>>,
|
||||
path: &'a str,
|
||||
query: Option<&'a str>,
|
||||
) -> Absolute<'a> {
|
||||
Absolute {
|
||||
source: None,
|
||||
scheme: IndexedStr::Concrete(Cow::Borrowed(scheme)),
|
||||
authority,
|
||||
path: Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(path)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
},
|
||||
query: match query {
|
||||
Some(query) => Some(Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(query)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
}),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Have a way to get a validated `path` to do this. See `Path`?
|
||||
pub(crate) fn set_path<P>(&mut self, path: P)
|
||||
where P: Into<Cow<'a, str>>
|
||||
{
|
||||
self.path = Data::new(path.into());
|
||||
}
|
||||
|
||||
// TODO: Have a way to get a validated `query` to do this. See `Query`?
|
||||
pub(crate) fn set_query<Q: Into<Option<Cow<'a, str>>>>(&mut self, query: Q) {
|
||||
self.query = query.into().map(Data::new);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a String> for Absolute<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
|
||||
Absolute::parse(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Absolute<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
Absolute::parse(value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,20 +440,21 @@ impl<'a, 'b> PartialEq<Absolute<'b>> for Absolute<'a> {
|
|||
fn eq(&self, other: &Absolute<'b>) -> bool {
|
||||
self.scheme() == other.scheme()
|
||||
&& self.authority() == other.authority()
|
||||
&& self.origin() == other.origin()
|
||||
&& self.path() == other.path()
|
||||
&& self.query() == other.query()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Absolute<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.scheme())?;
|
||||
match self.authority {
|
||||
Some(ref authority) => write!(f, "://{}", authority)?,
|
||||
None => write!(f, ":")?
|
||||
impl std::fmt::Display for Absolute<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}:", self.scheme())?;
|
||||
if let Some(authority) = self.authority() {
|
||||
write!(f, "//{}", authority)?;
|
||||
}
|
||||
|
||||
if let Some(ref origin) = self.origin {
|
||||
write!(f, "{}", origin)?;
|
||||
write!(f, "{}", self.path())?;
|
||||
if let Some(query) = self.query() {
|
||||
write!(f, "?{}", query)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/// The literal `*` URI.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||
pub struct Asterisk;
|
||||
|
||||
impl std::fmt::Display for Asterisk {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
"*".fmt(f)
|
||||
}
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::convert::TryFrom;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::ext::IntoOwned;
|
||||
use crate::parse::{Extent, IndexedStr};
|
||||
use crate::uri::{as_utf8_unchecked, Error};
|
||||
use crate::uri::{as_utf8_unchecked, error::Error};
|
||||
|
||||
/// A URI with an authority only: `user:pass@host:8000`.
|
||||
///
|
||||
|
@ -21,26 +22,12 @@ use crate::uri::{as_utf8_unchecked, Error};
|
|||
/// Only the host part of the URI is required.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Authority<'a> {
|
||||
source: Option<Cow<'a, str>>,
|
||||
pub(crate) source: Option<Cow<'a, str>>,
|
||||
user_info: Option<IndexedStr<'a>>,
|
||||
host: Host<IndexedStr<'a>>,
|
||||
host: IndexedStr<'a>,
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Host<T> {
|
||||
Bracketed(T),
|
||||
Raw(T)
|
||||
}
|
||||
|
||||
impl<T: IntoOwned> IntoOwned for Host<T> {
|
||||
type Owned = Host<T::Owned>;
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
self.map_inner(IntoOwned::into_owned)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoOwned for Authority<'_> {
|
||||
type Owned = Authority<'static>;
|
||||
|
||||
|
@ -55,31 +42,42 @@ impl IntoOwned for Authority<'_> {
|
|||
}
|
||||
|
||||
impl<'a> Authority<'a> {
|
||||
// SAFETY: `source` must be valid UTF-8.
|
||||
// CORRECTNESS: `host` must be non-empty.
|
||||
pub(crate) unsafe fn raw(
|
||||
source: Cow<'a, [u8]>,
|
||||
user_info: Option<Extent<&'a [u8]>>,
|
||||
host: Host<Extent<&'a [u8]>>,
|
||||
host: Extent<&'a [u8]>,
|
||||
port: Option<u16>
|
||||
) -> Authority<'a> {
|
||||
Authority {
|
||||
source: Some(as_utf8_unchecked(source)),
|
||||
user_info: user_info.map(IndexedStr::from),
|
||||
host: host.map_inner(IndexedStr::from),
|
||||
port: port
|
||||
host: IndexedStr::from(host),
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn new(
|
||||
user_info: Option<&'a str>,
|
||||
host: Host<&'a str>,
|
||||
port: Option<u16>
|
||||
) -> Authority<'a> {
|
||||
pub fn new(
|
||||
user_info: impl Into<Option<&'a str>>,
|
||||
host: &'a str,
|
||||
port: impl Into<Option<u16>>,
|
||||
) -> Self {
|
||||
Authority::const_new(user_info.into(), host, port.into())
|
||||
}
|
||||
|
||||
/// PRIVATE. Used by codegen.
|
||||
#[doc(hidden)]
|
||||
pub const fn const_new(user_info: Option<&'a str>, host: &'a str, port: Option<u16>) -> Self {
|
||||
Authority {
|
||||
source: None,
|
||||
user_info: user_info.map(|u| Cow::Borrowed(u).into()),
|
||||
host: host.map_inner(|inner| Cow::Borrowed(inner).into()),
|
||||
port: port
|
||||
user_info: match user_info {
|
||||
Some(info) => Some(IndexedStr::Concrete(Cow::Borrowed(info))),
|
||||
None => None
|
||||
},
|
||||
host: IndexedStr::Concrete(Cow::Borrowed(host)),
|
||||
port,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,7 +87,7 @@ impl<'a> Authority<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Authority;
|
||||
///
|
||||
/// // Parse a valid authority URI.
|
||||
|
@ -99,7 +97,13 @@ impl<'a> Authority<'a> {
|
|||
/// assert_eq!(uri.port(), None);
|
||||
///
|
||||
/// // Invalid authority URIs fail to parse.
|
||||
/// Authority::parse("http://google.com").expect_err("invalid authority");
|
||||
/// Authority::parse("https://rocket.rs").expect_err("invalid authority");
|
||||
///
|
||||
/// // Prefer to use `uri!()` when the input is statically known:
|
||||
/// let uri = uri!("user:pass@host");
|
||||
/// assert_eq!(uri.user_info(), Some("user:pass"));
|
||||
/// assert_eq!(uri.host(), "host");
|
||||
/// assert_eq!(uri.port(), None);
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Authority<'a>, Error<'a>> {
|
||||
crate::parse::uri::authority_from_str(string)
|
||||
|
@ -108,12 +112,9 @@ impl<'a> Authority<'a> {
|
|||
/// Returns the user info part of the authority URI, if there is one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Authority;
|
||||
///
|
||||
/// let uri = Authority::parse("username:password@host").unwrap();
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("username:password@host");
|
||||
/// assert_eq!(uri.user_info(), Some("username:password"));
|
||||
/// ```
|
||||
pub fn user_info(&self) -> Option<&str> {
|
||||
|
@ -127,23 +128,21 @@ impl<'a> Authority<'a> {
|
|||
/// brackets will not be part of the returned string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Authority;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
///
|
||||
/// let uri = Authority::parse("domain.com:123").unwrap();
|
||||
/// let uri = uri!("domain.com:123");
|
||||
/// assert_eq!(uri.host(), "domain.com");
|
||||
///
|
||||
/// let uri = Authority::parse("username:password@host:123").unwrap();
|
||||
/// let uri = uri!("username:password@host:123");
|
||||
/// assert_eq!(uri.host(), "host");
|
||||
///
|
||||
/// let uri = Authority::parse("username:password@[1::2]:123").unwrap();
|
||||
/// assert_eq!(uri.host(), "1::2");
|
||||
/// let uri = uri!("username:password@[1::2]:123");
|
||||
/// assert_eq!(uri.host(), "[1::2]");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn host(&self) -> &str {
|
||||
self.host.inner().from_cow_source(&self.source)
|
||||
self.host.from_cow_source(&self.source)
|
||||
}
|
||||
|
||||
/// Returns the port part of the authority URI, if there is one.
|
||||
|
@ -151,18 +150,16 @@ impl<'a> Authority<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Authority;
|
||||
///
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// // With a port.
|
||||
/// let uri = Authority::parse("username:password@host:123").unwrap();
|
||||
/// let uri = uri!("username:password@host:123");
|
||||
/// assert_eq!(uri.port(), Some(123));
|
||||
///
|
||||
/// let uri = Authority::parse("domain.com:8181").unwrap();
|
||||
/// let uri = uri!("domain.com:8181");
|
||||
/// assert_eq!(uri.port(), Some(8181));
|
||||
///
|
||||
/// // Without a port.
|
||||
/// let uri = Authority::parse("username:password@host").unwrap();
|
||||
/// let uri = uri!("username:password@host");
|
||||
/// assert_eq!(uri.port(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
|
@ -175,7 +172,6 @@ impl<'b> PartialEq<Authority<'b>> for Authority<'_> {
|
|||
fn eq(&self, other: &Authority<'b>) -> bool {
|
||||
self.user_info() == other.user_info()
|
||||
&& self.host() == other.host()
|
||||
&& self.host.is_bracketed() == other.host.is_bracketed()
|
||||
&& self.port() == other.port()
|
||||
}
|
||||
}
|
||||
|
@ -186,11 +182,7 @@ impl Display for Authority<'_> {
|
|||
write!(f, "{}@", user_info)?;
|
||||
}
|
||||
|
||||
match self.host {
|
||||
Host::Bracketed(_) => write!(f, "[{}]", self.host())?,
|
||||
Host::Raw(_) => write!(f, "{}", self.host())?
|
||||
}
|
||||
|
||||
self.host().fmt(f)?;
|
||||
if let Some(port) = self.port {
|
||||
write!(f, ":{}", port)?;
|
||||
}
|
||||
|
@ -199,29 +191,19 @@ impl Display for Authority<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> Host<T> {
|
||||
#[inline]
|
||||
fn inner(&self) -> &T {
|
||||
match *self {
|
||||
Host::Bracketed(ref inner) | Host::Raw(ref inner) => inner
|
||||
}
|
||||
}
|
||||
// Because inference doesn't take `&String` to `&str`.
|
||||
impl<'a> TryFrom<&'a String> for Authority<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
#[inline]
|
||||
fn is_bracketed(&self) -> bool {
|
||||
match *self {
|
||||
Host::Bracketed(_) => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn map_inner<F, U>(self, f: F) -> Host<U>
|
||||
where F: FnOnce(T) -> U
|
||||
{
|
||||
match self {
|
||||
Host::Bracketed(inner) => Host::Bracketed(f(inner)),
|
||||
Host::Raw(inner) => Host::Raw(f(inner))
|
||||
}
|
||||
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
|
||||
Authority::parse(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Authority<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
Authority::parse(value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//! Errors arising from parsing invalid URIs.
|
||||
|
||||
use std::fmt;
|
||||
|
||||
pub use crate::parse::uri::Error;
|
||||
|
||||
/// The error type returned when a URI conversion fails.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TryFromUriError(pub(crate) ());
|
||||
|
||||
impl fmt::Display for TryFromUriError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"invalid conversion from general to specific URI variant".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// An error interpreting a segment as a [`PathBuf`] component in
|
||||
/// [`Segments::to_path_buf()`].
|
||||
///
|
||||
/// [`PathBuf`]: std::path::PathBuf
|
||||
/// [`Segments::to_path_buf()`]: crate::uri::Segments::to_path_buf()
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum PathError {
|
||||
/// The segment started with the wrapped invalid character.
|
||||
BadStart(char),
|
||||
/// The segment contained the wrapped invalid character.
|
||||
BadChar(char),
|
||||
/// The segment ended with the wrapped invalid character.
|
||||
BadEnd(char),
|
||||
}
|
|
@ -4,12 +4,13 @@ use std::borrow::Cow;
|
|||
use percent_encoding::{AsciiSet, utf8_percent_encode};
|
||||
|
||||
use crate::RawStr;
|
||||
use crate::uri::{UriPart, Path, Query};
|
||||
use crate::uri::fmt::{Part, Path, Query};
|
||||
use crate::parse::uri::tables::PATH_CHARS;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct UNSAFE_ENCODE_SET<P: UriPart>(PhantomData<P>);
|
||||
pub struct UNSAFE_ENCODE_SET<P: Part>(PhantomData<P>);
|
||||
|
||||
pub trait EncodeSet {
|
||||
const SET: AsciiSet;
|
||||
}
|
||||
|
@ -32,7 +33,7 @@ const fn set_from_table(table: &'static [u8; 256]) -> AsciiSet {
|
|||
|
||||
const PATH_SET: AsciiSet = set_from_table(&PATH_CHARS);
|
||||
|
||||
impl<P: UriPart> Default for UNSAFE_ENCODE_SET<P> {
|
||||
impl<P: Part> Default for UNSAFE_ENCODE_SET<P> {
|
||||
#[inline(always)]
|
||||
fn default() -> Self { UNSAFE_ENCODE_SET(PhantomData) }
|
||||
}
|
||||
|
@ -51,7 +52,7 @@ impl EncodeSet for UNSAFE_ENCODE_SET<Query> {
|
|||
|
||||
#[derive(Clone, Copy)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct ENCODE_SET<P: UriPart>(PhantomData<P>);
|
||||
pub struct ENCODE_SET<P: Part>(PhantomData<P>);
|
||||
|
||||
impl EncodeSet for ENCODE_SET<Path> {
|
||||
const SET: AsciiSet = <UNSAFE_ENCODE_SET<Path>>::SET
|
|
@ -1,25 +1,23 @@
|
|||
use std::fmt::{self, Write};
|
||||
use std::marker::PhantomData;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
||||
use crate::uri::{Absolute, Origin, Reference};
|
||||
use crate::uri::fmt::{UriDisplay, Part, Path, Query, Kind};
|
||||
|
||||
/// A struct used to format strings for [`UriDisplay`].
|
||||
///
|
||||
/// # Marker Generic: `Formatter<Path>` vs. `Formatter<Query>`
|
||||
///
|
||||
/// Like [`UriDisplay`], the [`UriPart`] parameter `P` in `Formatter<P>` must be
|
||||
/// Like [`UriDisplay`], the [`Part`] parameter `P` in `Formatter<P>` must be
|
||||
/// either [`Path`] or [`Query`] resulting in either `Formatter<Path>` or
|
||||
/// `Formatter<Query>`. The `Path` version is used when formatting parameters
|
||||
/// in the path part of the URI while the `Query` version is used when
|
||||
/// formatting parameters in the query part of the URI. The
|
||||
/// [`write_named_value()`] method is only available to `UriDisplay<Query>`.
|
||||
///
|
||||
/// [`UriPart`]: crate::uri::UriPart
|
||||
/// [`Path`]: crate::uri::Path
|
||||
/// [`Query`]: crate::uri::Query
|
||||
///
|
||||
/// # Overview
|
||||
///
|
||||
/// A mutable version of this struct is passed to [`UriDisplay::fmt()`]. This
|
||||
|
@ -39,10 +37,6 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
/// calls `write_named_vlaue()`, the nested names are joined by a `.`,
|
||||
/// written out followed by a `=`, followed by the value.
|
||||
///
|
||||
/// [`UriDisplay`]: crate::uri::UriDisplay
|
||||
/// [`UriDisplay::fmt()`]: crate::uri::UriDisplay::fmt()
|
||||
/// [`write_named_value()`]: crate::uri::Formatter::write_named_value()
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// Usage is fairly straightforward:
|
||||
|
@ -59,8 +53,6 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
/// called, after a call to `write_named_value` or `write_value`, or after a
|
||||
/// call to [`refresh()`].
|
||||
///
|
||||
/// [`refresh()`]: crate::uri::Formatter::refresh()
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The following example uses all of the `write` methods in a varied order to
|
||||
|
@ -72,7 +64,7 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
/// # extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query};
|
||||
///
|
||||
/// struct Outer {
|
||||
/// value: Inner,
|
||||
|
@ -105,7 +97,7 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
///
|
||||
/// let inner = Inner { value: 0, extra: 1 };
|
||||
/// let outer = Outer { value: inner, another: 2, extra: 3 };
|
||||
/// let uri_string = format!("{}", &outer as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &outer as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "outer_field.inner_field=0&\
|
||||
/// outer_field=1&\
|
||||
/// outer_field=inside&\
|
||||
|
@ -123,17 +115,17 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
/// # #[macro_use] extern crate rocket;
|
||||
/// use std::fmt::{self, Write};
|
||||
///
|
||||
/// use rocket::http::uri::{UriDisplay, Formatter, UriPart, Path, Query};
|
||||
/// use rocket::http::uri::fmt::{UriDisplay, Formatter, Part, Path, Query};
|
||||
///
|
||||
/// pub struct Complex(u8, u8);
|
||||
///
|
||||
/// impl<P: UriPart> UriDisplay<P> for Complex {
|
||||
/// impl<P: Part> UriDisplay<P> for Complex {
|
||||
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||
/// write!(f, "{}+{}", self.0, self.1)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let uri_string = format!("{}", &Complex(42, 231) as &UriDisplay<Path>);
|
||||
/// let uri_string = format!("{}", &Complex(42, 231) as &dyn UriDisplay<Path>);
|
||||
/// assert_eq!(uri_string, "42+231");
|
||||
///
|
||||
/// #[derive(UriDisplayQuery)]
|
||||
|
@ -142,13 +134,15 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
|||
/// }
|
||||
///
|
||||
/// let message = Message { number: Complex(42, 47) };
|
||||
/// let uri_string = format!("{}", &message as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &message as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "number=42+47");
|
||||
/// ```
|
||||
///
|
||||
/// [`write_value()`]: crate::uri::Formatter::write_value()
|
||||
/// [`write_raw()`]: crate::uri::Formatter::write_raw()
|
||||
pub struct Formatter<'i, P: UriPart> {
|
||||
/// [`write_named_value()`]: Formatter::write_value()
|
||||
/// [`write_value()`]: Formatter::write_value()
|
||||
/// [`write_raw()`]: Formatter::write_raw()
|
||||
/// [`refresh()`]: Formatter::refresh()
|
||||
pub struct Formatter<'i, P: Part> {
|
||||
prefixes: SmallVec<[&'static str; 3]>,
|
||||
inner: &'i mut (dyn Write + 'i),
|
||||
previous: bool,
|
||||
|
@ -156,7 +150,7 @@ pub struct Formatter<'i, P: UriPart> {
|
|||
_marker: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<'i, P: UriPart> Formatter<'i, P> {
|
||||
impl<'i, P: Part> Formatter<'i, P> {
|
||||
#[inline(always)]
|
||||
pub(crate) fn new(inner: &'i mut (dyn Write + 'i)) -> Self {
|
||||
Formatter {
|
||||
|
@ -191,11 +185,11 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// # extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, UriPart, Path};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Part, Path};
|
||||
///
|
||||
/// struct Foo;
|
||||
///
|
||||
/// impl<P: UriPart> UriDisplay<P> for Foo {
|
||||
/// impl<P: Part> UriDisplay<P> for Foo {
|
||||
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||
/// f.write_raw("f")?;
|
||||
/// f.write_raw("o")?;
|
||||
|
@ -204,7 +198,7 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// }
|
||||
///
|
||||
/// let foo = Foo;
|
||||
/// let uri_string = format!("{}", &foo as &UriDisplay<Path>);
|
||||
/// let uri_string = format!("{}", &foo as &dyn UriDisplay<Path>);
|
||||
/// assert_eq!(uri_string, "foo");
|
||||
/// ```
|
||||
pub fn write_raw<S: AsRef<str>>(&mut self, string: S) -> fmt::Result {
|
||||
|
@ -247,11 +241,11 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// # extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, UriPart, Path, Query};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Part, Path, Query};
|
||||
///
|
||||
/// struct Foo(usize);
|
||||
///
|
||||
/// impl<P: UriPart> UriDisplay<P> for Foo {
|
||||
/// impl<P: Part> UriDisplay<P> for Foo {
|
||||
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||
/// f.write_value(&self.0)
|
||||
/// }
|
||||
|
@ -259,10 +253,10 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
///
|
||||
/// let foo = Foo(123);
|
||||
///
|
||||
/// let uri_string = format!("{}", &foo as &UriDisplay<Path>);
|
||||
/// let uri_string = format!("{}", &foo as &dyn UriDisplay<Path>);
|
||||
/// assert_eq!(uri_string, "123");
|
||||
///
|
||||
/// let uri_string = format!("{}", &foo as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &foo as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "123");
|
||||
/// ```
|
||||
#[inline]
|
||||
|
@ -283,7 +277,7 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// # #[macro_use] extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query, Path};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query, Path};
|
||||
///
|
||||
/// struct Foo;
|
||||
///
|
||||
|
@ -296,18 +290,9 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let uri_string = format!("{}", &Foo as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &Foo as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "araw&format");
|
||||
///
|
||||
///// #[derive(UriDisplayQuery)]
|
||||
///// struct Message {
|
||||
///// inner: Foo,
|
||||
///// }
|
||||
/////
|
||||
///// let msg = Message { inner: Foo };
|
||||
///// let uri_string = format!("{}", &msg as &UriDisplay);
|
||||
///// assert_eq!(uri_string, "inner=araw&inner=format");
|
||||
///
|
||||
/// impl UriDisplay<Path> for Foo {
|
||||
/// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
||||
/// f.write_raw("a")?;
|
||||
|
@ -317,8 +302,17 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
|||
/// }
|
||||
/// }
|
||||
///
|
||||
/// let uri_string = format!("{}", &Foo as &UriDisplay<Path>);
|
||||
/// let uri_string = format!("{}", &Foo as &dyn UriDisplay<Path>);
|
||||
/// assert_eq!(uri_string, "araw/format");
|
||||
///
|
||||
/// #[derive(UriDisplayQuery)]
|
||||
/// struct Message {
|
||||
/// inner: Foo,
|
||||
/// }
|
||||
///
|
||||
/// let msg = Message { inner: Foo };
|
||||
/// let uri_string = format!("{}", &msg as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "inner=araw&inner=format");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn refresh(&mut self) {
|
||||
|
@ -380,7 +374,7 @@ impl Formatter<'_, Query> {
|
|||
/// # extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query};
|
||||
///
|
||||
/// struct Foo {
|
||||
/// name: usize
|
||||
|
@ -395,7 +389,7 @@ impl Formatter<'_, Query> {
|
|||
/// }
|
||||
///
|
||||
/// let foo = Foo { name: 123 };
|
||||
/// let uri_string = format!("{}", &foo as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &foo as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "name=123");
|
||||
/// ```
|
||||
#[inline]
|
||||
|
@ -404,7 +398,7 @@ impl Formatter<'_, Query> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<P: UriPart> fmt::Write for Formatter<'_, P> {
|
||||
impl<P: Part> fmt::Write for Formatter<'_, P> {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.write_raw(s)
|
||||
}
|
||||
|
@ -425,41 +419,168 @@ pub enum UriQueryArgument<'a> {
|
|||
Value(&'a dyn UriDisplay<Query>)
|
||||
}
|
||||
|
||||
/// No prefix at all.
|
||||
#[doc(hidden)]
|
||||
pub struct Void;
|
||||
|
||||
// Used by code generation.
|
||||
#[doc(hidden)]
|
||||
pub struct UriArguments<'a> {
|
||||
pub path: UriArgumentsKind<&'a [&'a dyn UriDisplay<Path>]>,
|
||||
pub query: Option<UriArgumentsKind<&'a [UriQueryArgument<'a>]>>,
|
||||
pub trait ValidRoutePrefix {
|
||||
type Output;
|
||||
|
||||
fn append(self, path: Cow<'static, str>, query: Option<Cow<'static, str>>) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a> ValidRoutePrefix for Origin<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn append(self, path: Cow<'static, str>, query: Option<Cow<'static, str>>) -> Self::Output {
|
||||
// No-op if `self` is already normalzied.
|
||||
let mut prefix = self.into_normalized();
|
||||
prefix.clear_query();
|
||||
|
||||
if prefix.path() == "/" {
|
||||
// Avoid a double `//` to start.
|
||||
return Origin::new(path, query);
|
||||
} else if path == "/" {
|
||||
// Appending path to `/` is a no-op, but append any query.
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
Origin::new(format!("{}{}", prefix.path(), path), query)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValidRoutePrefix for Absolute<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn append(self, path: Cow<'static, str>, query: Option<Cow<'static, str>>) -> Self::Output {
|
||||
// No-op if `self` is already normalzied.
|
||||
let mut prefix = self.into_normalized();
|
||||
prefix.clear_query();
|
||||
|
||||
if prefix.authority().is_some() {
|
||||
// The prefix is normalized. Appending a `/` is a no-op.
|
||||
if path == "/" {
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
|
||||
// In these cases, appending `path` would be a no-op or worse.
|
||||
if prefix.path().is_empty() || prefix.path() == "/" {
|
||||
prefix.set_path(path);
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
if path == "/" {
|
||||
prefix.set_query(query);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
prefix.set_path(format!("{}{}", prefix.path(), path));
|
||||
prefix.set_query(query);
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
// `Self` is a valid suffix for `T`.
|
||||
#[doc(hidden)]
|
||||
pub trait ValidRouteSuffix<T> {
|
||||
type Output;
|
||||
|
||||
fn prepend(self, prefix: T) -> Self::Output;
|
||||
}
|
||||
|
||||
impl<'a> ValidRouteSuffix<Origin<'a>> for Reference<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn prepend(self, prefix: Origin<'a>) -> Self::Output {
|
||||
Reference::from(prefix).with_query_fragment_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValidRouteSuffix<Absolute<'a>> for Reference<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn prepend(self, prefix: Absolute<'a>) -> Self::Output {
|
||||
Reference::from(prefix).with_query_fragment_of(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValidRouteSuffix<Origin<'a>> for Absolute<'a> {
|
||||
type Output = Origin<'a>;
|
||||
|
||||
fn prepend(self, mut prefix: Origin<'a>) -> Self::Output {
|
||||
if let Some(query) = self.query {
|
||||
if prefix.query().is_none() {
|
||||
prefix.set_query(query.value.into_concrete(&self.source));
|
||||
}
|
||||
}
|
||||
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ValidRouteSuffix<Absolute<'a>> for Absolute<'a> {
|
||||
type Output = Self;
|
||||
|
||||
fn prepend(self, mut prefix: Absolute<'a>) -> Self::Output {
|
||||
if let Some(query) = self.query {
|
||||
if prefix.query().is_none() {
|
||||
prefix.set_query(query.value.into_concrete(&self.source));
|
||||
}
|
||||
}
|
||||
|
||||
prefix
|
||||
}
|
||||
}
|
||||
|
||||
// Used by code generation.
|
||||
impl UriArguments<'_> {
|
||||
#[doc(hidden)]
|
||||
pub fn into_origin(self) -> Origin<'static> {
|
||||
use std::borrow::Cow;
|
||||
#[doc(hidden)]
|
||||
pub struct RouteUriBuilder {
|
||||
pub path: Cow<'static, str>,
|
||||
pub query: Option<Cow<'static, str>>,
|
||||
}
|
||||
|
||||
// Used by code generation.
|
||||
#[doc(hidden)]
|
||||
pub struct PrefixedRouteUri<T>(T);
|
||||
|
||||
// Used by code generation.
|
||||
#[doc(hidden)]
|
||||
pub struct SuffixedRouteUri<T>(T);
|
||||
|
||||
// Used by code generation.
|
||||
#[doc(hidden)]
|
||||
impl RouteUriBuilder {
|
||||
/// Create a new `RouteUriBuilder` with the given path/query args.
|
||||
pub fn new(
|
||||
path_args: UriArgumentsKind<&[&dyn UriDisplay<Path>]>,
|
||||
query_args: Option<UriArgumentsKind<&[UriQueryArgument<'_>]>>,
|
||||
) -> Self {
|
||||
use self::{UriArgumentsKind::*, UriQueryArgument::*};
|
||||
|
||||
let path: Cow<'static, str> = match self.path {
|
||||
let path: Cow<'static, str> = match path_args {
|
||||
Static(path) => path.into(),
|
||||
Dynamic(args) => {
|
||||
let mut string = String::from("/");
|
||||
{
|
||||
let mut formatter = Formatter::<Path>::new(&mut string);
|
||||
for value in args {
|
||||
let _ = formatter.write_value(value);
|
||||
}
|
||||
}
|
||||
|
||||
string.into()
|
||||
}
|
||||
};
|
||||
|
||||
let query: Option<Cow<'_, str>> = self.query.and_then(|q| match q {
|
||||
Static(query) => Some(query.into()),
|
||||
Dynamic(args) if args.is_empty() => None,
|
||||
Dynamic(args) => {
|
||||
let query: Option<Cow<'_, str>> = match query_args {
|
||||
None => None,
|
||||
Some(Static(query)) => Some(query.into()),
|
||||
Some(Dynamic(args)) => {
|
||||
let mut string = String::new();
|
||||
{
|
||||
let mut f = Formatter::<Query>::new(&mut string);
|
||||
for arg in args {
|
||||
let _ = match arg {
|
||||
|
@ -468,23 +589,51 @@ impl UriArguments<'_> {
|
|||
Value(v) => f.write_value(v),
|
||||
};
|
||||
}
|
||||
|
||||
(!string.is_empty()).then(|| string.into())
|
||||
}
|
||||
};
|
||||
|
||||
RouteUriBuilder { path, query }
|
||||
}
|
||||
|
||||
match string.is_empty() {
|
||||
false => Some(string.into()),
|
||||
true => None,
|
||||
pub fn with_prefix<P: ValidRoutePrefix>(self, p: P) -> PrefixedRouteUri<P::Output> {
|
||||
PrefixedRouteUri(p.append(self.path, self.query))
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Origin::new(path, query)
|
||||
pub fn with_suffix<S>(self, suffix: S) -> SuffixedRouteUri<S::Output>
|
||||
where S: ValidRouteSuffix<Origin<'static>>
|
||||
{
|
||||
SuffixedRouteUri(suffix.prepend(self.render()))
|
||||
}
|
||||
|
||||
pub fn render(self) -> Origin<'static> {
|
||||
Origin::new(self.path, self.query)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<T> PrefixedRouteUri<T> {
|
||||
pub fn with_suffix<S: ValidRouteSuffix<T>>(self, suffix: S) -> SuffixedRouteUri<S::Output> {
|
||||
SuffixedRouteUri(suffix.prepend(self.0))
|
||||
}
|
||||
|
||||
pub fn render(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl<T> SuffixedRouteUri<T> {
|
||||
pub fn render(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// See https://github.com/SergioBenitez/Rocket/issues/1534.
|
||||
#[cfg(test)]
|
||||
mod prefix_soundness_test {
|
||||
use crate::uri::{Formatter, Query, UriDisplay};
|
||||
use crate::uri::fmt::{Formatter, UriDisplay, Query};
|
||||
|
||||
struct MyValue;
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::uri::{self, UriPart, UriDisplay};
|
||||
use crate::uri::fmt::UriDisplay;
|
||||
use crate::uri::fmt::{self, Part};
|
||||
|
||||
/// Conversion trait for parameters used in [`uri!`] invocations.
|
||||
///
|
||||
|
@ -17,9 +18,9 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
/// be automated. Rocket provides [`impl_from_uri_param_identity`] to generate
|
||||
/// the _identity_ implementations automatically. For a type `T`, these are:
|
||||
///
|
||||
/// * `impl<P: UriPart> FromUriParam<P, T> for T`
|
||||
/// * `impl<'x, P: UriPart> FromUriParam<P, &'x T> for T`
|
||||
/// * `impl<'x, P: UriPart> FromUriParam<P, &'x mut T> for T`
|
||||
/// * `impl<P: Part> FromUriParam<P, T> for T`
|
||||
/// * `impl<'x, P: Part> FromUriParam<P, &'x T> for T`
|
||||
/// * `impl<'x, P: Part> FromUriParam<P, &'x mut T> for T`
|
||||
///
|
||||
/// See [`impl_from_uri_param_identity`] for usage details.
|
||||
///
|
||||
|
@ -40,10 +41,10 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # use rocket::http::uri::{FromUriParam, UriPart};
|
||||
/// # use rocket::http::uri::fmt::{FromUriParam, Part};
|
||||
/// # struct S;
|
||||
/// # type String = S;
|
||||
/// impl<'a, P: UriPart> FromUriParam<P, &'a str> for String {
|
||||
/// impl<'a, P: Part> FromUriParam<P, &'a str> for String {
|
||||
/// type Target = &'a str;
|
||||
/// # fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
|
||||
/// }
|
||||
|
@ -99,7 +100,7 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
/// invocation. For instance, if the route has a type of `T` and you'd like to
|
||||
/// use a type of `S` in a `uri!` invocation, you'd implement `FromUriParam<P,
|
||||
/// T> for S` where `P` is `Path` for conversions valid in the path part of a
|
||||
/// URI, `Uri` for conversions valid in the query part of a URI, or `P: UriPart`
|
||||
/// URI, `Uri` for conversions valid in the query part of a URI, or `P: Part`
|
||||
/// when a conversion is valid in either case.
|
||||
///
|
||||
/// This is typically only warranted for owned-value types with corresponding
|
||||
|
@ -121,7 +122,7 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
/// # #[macro_use] extern crate rocket;
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
|
||||
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, FromUriParam, Query};
|
||||
///
|
||||
/// #[derive(FromForm)]
|
||||
/// struct User<'a> {
|
||||
|
@ -150,7 +151,7 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use std::fmt;
|
||||
/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
|
||||
/// # use rocket::http::uri::fmt::{Formatter, UriDisplay, FromUriParam, Query};
|
||||
/// #
|
||||
/// # #[derive(FromForm)]
|
||||
/// # struct User<'a> { name: &'a str, nickname: String, }
|
||||
|
@ -172,22 +173,22 @@ use crate::uri::{self, UriPart, UriDisplay};
|
|||
/// #[post("/<name>?<user..>")]
|
||||
/// fn some_route(name: &str, user: User<'_>) { /* .. */ }
|
||||
///
|
||||
/// let uri = uri!(some_route: name = "hey", user = ("Robert Mike", "Bob"));
|
||||
/// let uri = uri!(some_route(name = "hey", user = ("Robert Mike", "Bob")));
|
||||
/// assert_eq!(uri.path(), "/hey");
|
||||
/// assert_eq!(uri.query().unwrap(), "name=Robert%20Mike&nickname=Bob");
|
||||
/// ```
|
||||
///
|
||||
/// [`uri!`]: crate::uri
|
||||
/// [`UriDisplay`]: crate::uri::UriDisplay
|
||||
/// [`FromUriParam::Target`]: crate::uri::FromUriParam::Target
|
||||
/// [`Path`]: crate::uri::Path
|
||||
pub trait FromUriParam<P: UriPart, T> {
|
||||
/// [`uri!`]: rocket::uri
|
||||
/// [`FromUriParam::Target`]: crate::uri::fmt::FromUriParam::Target
|
||||
/// [`Path`]: crate::uri::fmt::Path
|
||||
/// [`Query`]: crate::uri::fmt::Query
|
||||
pub trait FromUriParam<P: Part, T> {
|
||||
/// The resulting type of this conversion.
|
||||
type Target: UriDisplay<P>;
|
||||
|
||||
/// Converts a value of type `T` into a value of type `Self::Target`. The
|
||||
/// resulting value of type `Self::Target` will be rendered into a URI using
|
||||
/// its [`UriDisplay`](crate::uri::UriDisplay) implementation.
|
||||
/// its [`UriDisplay`] implementation.
|
||||
fn from_uri_param(param: T) -> Self::Target;
|
||||
}
|
||||
|
||||
|
@ -200,7 +201,7 @@ macro_rules! impl_conversion_ref {
|
|||
($($A:ty => $B:ty),*) => ( impl_conversion_ref!(@_ $(() $A => $B),*); );
|
||||
|
||||
(@_ $(($($l:tt)*) $A:ty => $B:ty),*) => ($(
|
||||
impl_conversion_ref!([P] ($($l)* P: $crate::uri::UriPart) $A => $B);
|
||||
impl_conversion_ref!([P] ($($l)* P: $crate::uri::fmt::Part) $A => $B);
|
||||
)*);
|
||||
|
||||
($([$P:ty] ($($l:tt)*) $A:ty => $B:ty),*) => ($(
|
||||
|
@ -212,7 +213,7 @@ macro_rules! impl_conversion_ref {
|
|||
($([$P:ty] $A:ty => $B:ty),*) => ( impl_conversion_ref!($([$P] () $A => $B),*););
|
||||
|
||||
(@_ [$P:ty] ($($l:tt)*) $A:ty => $B:ty) => (
|
||||
impl<$($l)*> $crate::uri::FromUriParam<$P, $A> for $B {
|
||||
impl<$($l)*> $crate::uri::fmt::FromUriParam<$P, $A> for $B {
|
||||
type Target = $A;
|
||||
#[inline(always)] fn from_uri_param(param: $A) -> $A { param }
|
||||
}
|
||||
|
@ -224,13 +225,13 @@ macro_rules! impl_conversion_ref {
|
|||
///
|
||||
/// For a type `T`, the _identity_ implementations of `FromUriParam` are:
|
||||
///
|
||||
/// * `impl<P: UriPart> FromUriParam<P, T> for T`
|
||||
/// * `impl<P: Part> FromUriParam<P, T> for T`
|
||||
/// * `impl<'x> FromUriParam<P, &'x T> for T`
|
||||
/// * `impl<'x> FromUriParam<P, &'x mut T> for T`
|
||||
///
|
||||
/// where `P` is one of:
|
||||
///
|
||||
/// * `P: UriPart` (the generic `P`)
|
||||
/// * `P: Part` (the generic `P`)
|
||||
/// * [`Path`]
|
||||
/// * [`Query`]
|
||||
///
|
||||
|
@ -241,7 +242,7 @@ macro_rules! impl_conversion_ref {
|
|||
/// Generates the three _identity_ implementations for the generic `P`.
|
||||
///
|
||||
/// * Example: `impl_from_uri_param_identity!(MyType);`
|
||||
/// * Generates: `impl<P: UriPart> FromUriParam<P, _> for MyType { ... }`
|
||||
/// * Generates: `impl<P: Part> FromUriParam<P, _> for MyType { ... }`
|
||||
///
|
||||
/// 2. `impl_from_uri_param_identity!((generics*) Type);`
|
||||
///
|
||||
|
@ -250,11 +251,11 @@ macro_rules! impl_conversion_ref {
|
|||
/// implementation.
|
||||
///
|
||||
/// * Example: `impl_from_uri_param_identity!(('a) MyType<'a>);`
|
||||
/// * Generates: `impl<'a, P: UriPart> FromUriParam<P, _> for MyType<'a> { ... }`
|
||||
/// * Generates: `impl<'a, P: Part> FromUriParam<P, _> for MyType<'a> { ... }`
|
||||
///
|
||||
/// 3. `impl_from_uri_param_identity!([Part] Type);`
|
||||
///
|
||||
/// Generates the three _identity_ implementations for the `UriPart`
|
||||
/// Generates the three _identity_ implementations for the `Part`
|
||||
/// `Part`, where `Part` is a path to [`Path`] or [`Query`].
|
||||
///
|
||||
/// * Example: `impl_from_uri_param_identity!([Path] MyType);`
|
||||
|
@ -267,9 +268,9 @@ macro_rules! impl_conversion_ref {
|
|||
/// * Example: `impl_from_uri_param_identity!([Path] ('a) MyType<'a>);`
|
||||
/// * Generates: `impl<'a> FromUriParam<Path, _> for MyType<'a> { ... }`
|
||||
///
|
||||
/// [`FromUriParam`]: crate::uri::FromUriParam
|
||||
/// [`Path`]: crate::uri::Path
|
||||
/// [`Query`]: crate::uri::Query
|
||||
/// [`FromUriParam`]: crate::uri::fmt::FromUriParam
|
||||
/// [`Path`]: crate::uri::fmt::Path
|
||||
/// [`Query`]: crate::uri::fmt::Query
|
||||
#[macro_export(local_inner_macros)]
|
||||
macro_rules! impl_from_uri_param_identity {
|
||||
($(($($l:tt)*) $T:ty),*) => ($( impl_conversion_ref!(($($l)*) $T => $T); )*);
|
||||
|
@ -297,16 +298,16 @@ impl_conversion_ref! {
|
|||
('a) String => &'a str
|
||||
}
|
||||
|
||||
impl_from_uri_param_identity!([uri::Path] ('a) &'a Path);
|
||||
impl_from_uri_param_identity!([uri::Path] PathBuf);
|
||||
impl_from_uri_param_identity!([fmt::Path] ('a) &'a Path);
|
||||
impl_from_uri_param_identity!([fmt::Path] PathBuf);
|
||||
|
||||
impl_conversion_ref! {
|
||||
[uri::Path] ('a) &'a Path => PathBuf,
|
||||
[uri::Path] ('a) PathBuf => &'a Path
|
||||
[fmt::Path] ('a) &'a Path => PathBuf,
|
||||
[fmt::Path] ('a) PathBuf => &'a Path
|
||||
}
|
||||
|
||||
/// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`.
|
||||
impl<'a> FromUriParam<uri::Path, &'a str> for PathBuf {
|
||||
impl<'a> FromUriParam<fmt::Path, &'a str> for PathBuf {
|
||||
type Target = &'a Path;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -316,7 +317,7 @@ impl<'a> FromUriParam<uri::Path, &'a str> for PathBuf {
|
|||
}
|
||||
|
||||
/// A no cost conversion allowing an `&&str` to be used in place of a `PathBuf`.
|
||||
impl<'a, 'b> FromUriParam<uri::Path, &'a &'b str> for PathBuf {
|
||||
impl<'a, 'b> FromUriParam<fmt::Path, &'a &'b str> for PathBuf {
|
||||
type Target = &'b Path;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -326,7 +327,7 @@ impl<'a, 'b> FromUriParam<uri::Path, &'a &'b str> for PathBuf {
|
|||
}
|
||||
|
||||
/// A no cost conversion allowing any `T` to be used in place of an `Option<T>`.
|
||||
impl<A, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Option<T> {
|
||||
impl<A, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Option<T> {
|
||||
type Target = T::Target;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -336,7 +337,7 @@ impl<A, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Option<T>
|
|||
}
|
||||
|
||||
/// A no cost conversion allowing `T` to be used in place of an `Result<T, E>`.
|
||||
impl<A, E, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Result<T, E> {
|
||||
impl<A, E, T: FromUriParam<fmt::Path, A>> FromUriParam<fmt::Path, A> for Result<T, E> {
|
||||
type Target = T::Target;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -345,7 +346,7 @@ impl<A, E, T: FromUriParam<uri::Path, A>> FromUriParam<uri::Path, A> for Result<
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Option<A>> for Option<T> {
|
||||
impl<A, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Option<A>> for Option<T> {
|
||||
type Target = Option<T::Target>;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -354,7 +355,7 @@ impl<A, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Option<A>> for
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, E, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Option<A>> for Result<T, E> {
|
||||
impl<A, E, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Option<A>> for Result<T, E> {
|
||||
type Target = Option<T::Target>;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -363,7 +364,7 @@ impl<A, E, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Option<A>> f
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, E, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Result<A, E>> for Result<T, E> {
|
||||
impl<A, E, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Result<A, E>> for Result<T, E> {
|
||||
type Target = Result<T::Target, E>;
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -372,7 +373,7 @@ impl<A, E, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Result<A, E>
|
|||
}
|
||||
}
|
||||
|
||||
impl<A, E, T: FromUriParam<uri::Query, A>> FromUriParam<uri::Query, Result<A, E>> for Option<T> {
|
||||
impl<A, E, T: FromUriParam<fmt::Query, A>> FromUriParam<fmt::Query, Result<A, E>> for Option<T> {
|
||||
type Target = Result<T::Target, E>;
|
||||
|
||||
#[inline(always)]
|
|
@ -0,0 +1,14 @@
|
|||
//! Type safe and URI safe formatting types and traits.
|
||||
|
||||
mod uri_display;
|
||||
mod formatter;
|
||||
mod from_uri_param;
|
||||
mod encoding;
|
||||
mod part;
|
||||
|
||||
pub use self::formatter::*;
|
||||
pub use self::uri_display::*;
|
||||
pub use self::from_uri_param::*;
|
||||
pub use self::part::*;
|
||||
|
||||
pub(crate) use self::encoding::*;
|
|
@ -0,0 +1,84 @@
|
|||
use crate::parse::IndexedStr;
|
||||
|
||||
/// Marker trait for types that mark a part of a URI.
|
||||
///
|
||||
/// This trait exists solely to categorize types that mark a part of the URI,
|
||||
/// currently [`Path`] and [`Query`]. Said another way, types that implement
|
||||
/// this trait are marker types that represent a part of a URI at the
|
||||
/// type-level.
|
||||
///
|
||||
/// This trait is _sealed_: it cannot be implemented outside of Rocket.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// You will find this trait in traits like [`UriDisplay`] or structs like
|
||||
/// [`Formatter`] as the bound on a generic parameter: `P: Part`. Because the
|
||||
/// trait is sealed, the generic type is guaranteed to be instantiated as one of
|
||||
/// [`Query`] or [`Path`], effectively creating two instances of the generic
|
||||
/// items: `UriDisplay<Query>` and `UriDisplay<Path>`, and `Formatter<Query>`
|
||||
/// and `Formatter<Path>`. Unlike having two distinct, non-generic traits, this
|
||||
/// approach enables succinct, type-checked generic implementations of these
|
||||
/// items.
|
||||
///
|
||||
/// [`UriDisplay`]: crate::uri::fmt::UriDisplay
|
||||
/// [`Formatter`]: crate::uri::fmt::Formatter
|
||||
pub trait Part: private::Sealed {
|
||||
/// The dynamic version of `Self`.
|
||||
#[doc(hidden)]
|
||||
const KIND: Kind;
|
||||
|
||||
/// The delimiter used to separate components of this URI part.
|
||||
/// Specifically, `/` for `Path` and `&` for `Query`.
|
||||
#[doc(hidden)]
|
||||
const DELIMITER: char;
|
||||
|
||||
/// The raw form of a segment in this part.
|
||||
#[doc(hidden)]
|
||||
type Raw: Send + Sync + 'static;
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
impl Sealed for super::Path {}
|
||||
impl Sealed for super::Query {}
|
||||
}
|
||||
|
||||
/// Dynamic version of the `Path` and `Query` parts.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Kind { Path, Query }
|
||||
|
||||
/// Marker type indicating use of a type for the path [`Part`] of a URI.
|
||||
///
|
||||
/// In route URIs, this corresponds to all of the text before a `?`, if any, or
|
||||
/// all of the text in the URI otherwise:
|
||||
///
|
||||
/// ```text
|
||||
/// #[get("/home/<name>/<page>?<item>")]
|
||||
/// ^------------------ Path
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Path { }
|
||||
|
||||
/// Marker type indicating use of a type for the query [`Part`] of a URI.
|
||||
///
|
||||
/// In route URIs, this corresponds to all of the text after a `?`, if any.
|
||||
///
|
||||
/// ```text
|
||||
/// #[get("/home/<name>/<page>?<item>&<form..>")]
|
||||
/// ^-------------- Query
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Query { }
|
||||
|
||||
impl Part for Path {
|
||||
const KIND: Kind = Kind::Path;
|
||||
const DELIMITER: char = '/';
|
||||
type Raw = IndexedStr<'static>;
|
||||
}
|
||||
|
||||
impl Part for Query {
|
||||
const KIND: Kind = Kind::Query;
|
||||
const DELIMITER: char = '&';
|
||||
type Raw = (IndexedStr<'static>, IndexedStr<'static>);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use std::{fmt, path};
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||
use crate::RawStr;
|
||||
use crate::uri::fmt::{Part, Path, Query, Formatter};
|
||||
|
||||
/// Trait implemented by types that can be displayed as part of a URI in
|
||||
/// [`uri!`].
|
||||
|
@ -14,8 +15,8 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
///
|
||||
/// # Marker Generic: `Path`, `Query`
|
||||
///
|
||||
/// The [`UriPart`] parameter `P` in `UriDisplay<P>` must be either [`Path`] or
|
||||
/// [`Query`] (see the [`UriPart`] documentation for how this is enforced),
|
||||
/// The [`Part`] parameter `P` in `UriDisplay<P>` must be either [`Path`] or
|
||||
/// [`Query`] (see the [`Part`] documentation for how this is enforced),
|
||||
/// resulting in either `UriDisplay<Path>` or `UriDisplay<Query>`.
|
||||
///
|
||||
/// As the names might imply, the `Path` version of the trait is used when
|
||||
|
@ -37,16 +38,12 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # use std::fmt;
|
||||
/// # use rocket::http::uri::{UriPart, UriDisplay, Formatter};
|
||||
/// # use rocket::http::uri::fmt::{Part, UriDisplay, Formatter};
|
||||
/// # struct SomeType;
|
||||
/// impl<P: UriPart> UriDisplay<P> for SomeType
|
||||
/// impl<P: Part> UriDisplay<P> for SomeType
|
||||
/// # { fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { Ok(()) } }
|
||||
/// ```
|
||||
///
|
||||
/// [`UriPart`]: crate::uri::UriPart
|
||||
/// [`Path`]: crate::uri::Path
|
||||
/// [`Query`]: crate::uri::Query
|
||||
///
|
||||
/// # Code Generation
|
||||
///
|
||||
/// When the [`uri!`] macro is used to generate a URI for a route, the types for
|
||||
|
@ -74,18 +71,18 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// # fn get_item(id: i32, track: Option<String>) { /* .. */ }
|
||||
/// #
|
||||
/// // With unnamed parameters.
|
||||
/// uri!(get_item: 100, Some("inbound"));
|
||||
/// uri!(get_item(100, Some("inbound")));
|
||||
///
|
||||
/// // With named parameters.
|
||||
/// uri!(get_item: id = 100, track = Some("inbound"));
|
||||
/// uri!(get_item: track = Some("inbound"), id = 100);
|
||||
/// uri!(get_item(id = 100, track = Some("inbound")));
|
||||
/// uri!(get_item(track = Some("inbound"), id = 100));
|
||||
///
|
||||
/// // Ignoring `track`.
|
||||
/// uri!(get_item: 100, _);
|
||||
/// uri!(get_item: 100, None as Option<String>);
|
||||
/// uri!(get_item: id = 100, track = _);
|
||||
/// uri!(get_item: track = _, id = 100);
|
||||
/// uri!(get_item: id = 100, track = None as Option<&str>);
|
||||
/// uri!(get_item(100, _));
|
||||
/// uri!(get_item(100, None as Option<String>));
|
||||
/// uri!(get_item(id = 100, track = _));
|
||||
/// uri!(get_item(track = _, id = 100));
|
||||
/// uri!(get_item(id = 100, track = None as Option<&str>));
|
||||
/// ```
|
||||
///
|
||||
/// After verifying parameters and their types, Rocket will generate code
|
||||
|
@ -93,10 +90,11 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # use rocket::http::uri::{UriDisplay, Path, Query, Origin};
|
||||
/// # use rocket::http::uri::Origin;
|
||||
/// # use rocket::http::uri::fmt::{UriDisplay, Path, Query};
|
||||
/// #
|
||||
/// Origin::parse(&format!("/item/{}?track={}",
|
||||
/// &100 as &UriDisplay<Path>, &"inbound" as &UriDisplay<Query>));
|
||||
/// &100 as &dyn UriDisplay<Path>, &"inbound" as &dyn UriDisplay<Query>));
|
||||
/// ```
|
||||
///
|
||||
/// For this expression to typecheck, `i32` must implement `UriDisplay<Path>`
|
||||
|
@ -105,11 +103,11 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// seen, the implementations will be used to display the value in a URI-safe
|
||||
/// manner.
|
||||
///
|
||||
/// [`uri!`]: ../../../rocket/macro.uri.html
|
||||
/// [`uri!`]: rocket::uri
|
||||
///
|
||||
/// # Provided Implementations
|
||||
///
|
||||
/// Rocket implements `UriDisplay<P>` for all `P: UriPart` for several built-in
|
||||
/// Rocket implements `UriDisplay<P>` for all `P: Part` for several built-in
|
||||
/// types.
|
||||
///
|
||||
/// * **i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32,
|
||||
|
@ -167,7 +165,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// If the `Result` is `Ok`, uses the implementation of `UriDisplay` for
|
||||
/// `T`. Otherwise, nothing is rendered.
|
||||
///
|
||||
/// [`FromUriParam`]: crate::uri::FromUriParam
|
||||
/// [`FromUriParam`]: crate::uri::fmt::FromUriParam
|
||||
///
|
||||
/// # Deriving
|
||||
///
|
||||
|
@ -176,7 +174,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # use rocket::http::uri::{UriDisplay, Query, Path};
|
||||
/// # use rocket::http::uri::fmt::{UriDisplay, Query, Path};
|
||||
/// // Derives `UriDisplay<Query>`
|
||||
/// #[derive(UriDisplayQuery)]
|
||||
/// struct User {
|
||||
|
@ -185,7 +183,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// }
|
||||
///
|
||||
/// let user = User { name: "Michael Smith".into(), age: 31 };
|
||||
/// let uri_string = format!("{}", &user as &UriDisplay<Query>);
|
||||
/// let uri_string = format!("{}", &user as &dyn UriDisplay<Query>);
|
||||
/// assert_eq!(uri_string, "name=Michael%20Smith&age=31");
|
||||
///
|
||||
/// // Derives `UriDisplay<Path>`
|
||||
|
@ -193,7 +191,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// struct Name(String);
|
||||
///
|
||||
/// let name = Name("Bob Smith".into());
|
||||
/// let uri_string = format!("{}", &name as &UriDisplay<Path>);
|
||||
/// let uri_string = format!("{}", &name as &dyn UriDisplay<Path>);
|
||||
/// assert_eq!(uri_string, "Bob%20Smith");
|
||||
/// ```
|
||||
///
|
||||
|
@ -204,11 +202,9 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// [`UriDisplay<Path>`] and [`UriDisplay<Query>`] derive documentation for full
|
||||
/// details.
|
||||
///
|
||||
/// [`Ignorable`]: crate::uri::Ignorable
|
||||
/// [`Ignorable`]: crate::uri::fmt::Ignorable
|
||||
/// [`UriDisplay<Path>`]: ../../derive.UriDisplayPath.html
|
||||
/// [`UriDisplay<Query>`]: ../../derive.UriDisplayQuery.html
|
||||
/// [`Formatter::write_named_value()`]: crate::uri::Formatter::write_named_value()
|
||||
/// [`Formatter::write_value()`]: crate::uri::Formatter::write_value()
|
||||
///
|
||||
/// # Implementing
|
||||
///
|
||||
|
@ -259,7 +255,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
///
|
||||
/// use std::fmt;
|
||||
/// use rocket::http::impl_from_uri_param_identity;
|
||||
/// use rocket::http::uri::{Formatter, FromUriParam, UriDisplay, Path};
|
||||
/// use rocket::http::uri::fmt::{Formatter, FromUriParam, UriDisplay, Path};
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// impl UriDisplay<Path> for Name<'_> {
|
||||
|
@ -276,7 +272,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
///
|
||||
/// #[get("/name/<name>")]
|
||||
/// fn redirector(name: Name<'_>) -> Redirect {
|
||||
/// Redirect::to(uri!(real: name))
|
||||
/// Redirect::to(uri!(real(name)))
|
||||
/// }
|
||||
///
|
||||
/// #[get("/<name>")]
|
||||
|
@ -284,15 +280,15 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
|||
/// format!("Hello, {}!", name.0)
|
||||
/// }
|
||||
///
|
||||
/// let uri = uri!(real: Name("Mike Smith".into()));
|
||||
/// let uri = uri!(real(Name("Mike Smith".into())));
|
||||
/// assert_eq!(uri.path(), "/name:Mike%20Smith");
|
||||
/// ```
|
||||
pub trait UriDisplay<P: UriPart> {
|
||||
pub trait UriDisplay<P: Part> {
|
||||
/// Formats `self` in a URI-safe manner using the given formatter.
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result;
|
||||
}
|
||||
|
||||
impl<P: UriPart> fmt::Display for &dyn UriDisplay<P> {
|
||||
impl<P: Part> fmt::Display for &dyn UriDisplay<P> {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
UriDisplay::fmt(*self, &mut <Formatter<'_, P>>::new(f))
|
||||
|
@ -302,10 +298,10 @@ impl<P: UriPart> fmt::Display for &dyn UriDisplay<P> {
|
|||
// Direct implementations: these are the leaves of a call to `UriDisplay::fmt`.
|
||||
|
||||
/// Percent-encodes the raw string.
|
||||
impl<P: UriPart> UriDisplay<P> for str {
|
||||
impl<P: Part> UriDisplay<P> for str {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
f.write_raw(&Uri::percent_encode(self))
|
||||
f.write_raw(RawStr::new(self).percent_encode().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -328,7 +324,7 @@ impl UriDisplay<Path> for path::Path {
|
|||
macro_rules! impl_with_display {
|
||||
($($T:ty),+) => {$(
|
||||
/// This implementation is identical to the `Display` implementation.
|
||||
impl<P: UriPart> UriDisplay<P> for $T {
|
||||
impl<P: Part> UriDisplay<P> for $T {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
use std::fmt::Write;
|
||||
|
@ -351,7 +347,7 @@ impl_with_display! {
|
|||
// implementation.
|
||||
|
||||
/// Percent-encodes the raw string. Defers to `str`.
|
||||
impl<P: UriPart> UriDisplay<P> for String {
|
||||
impl<P: Part> UriDisplay<P> for String {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
self.as_str().fmt(f)
|
||||
|
@ -359,7 +355,7 @@ impl<P: UriPart> UriDisplay<P> for String {
|
|||
}
|
||||
|
||||
/// Percent-encodes the raw string. Defers to `str`.
|
||||
impl<P: UriPart> UriDisplay<P> for Cow<'_, str> {
|
||||
impl<P: Part> UriDisplay<P> for Cow<'_, str> {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
self.as_ref().fmt(f)
|
||||
|
@ -375,7 +371,7 @@ impl UriDisplay<Path> for path::PathBuf {
|
|||
}
|
||||
|
||||
/// Defers to the `UriDisplay<P>` implementation for `T`.
|
||||
impl<P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &T {
|
||||
impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &T {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
UriDisplay::fmt(*self, f)
|
||||
|
@ -383,7 +379,7 @@ impl<P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &T {
|
|||
}
|
||||
|
||||
/// Defers to the `UriDisplay<P>` implementation for `T`.
|
||||
impl<P: UriPart, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T {
|
||||
impl<P: Part, T: UriDisplay<P> + ?Sized> UriDisplay<P> for &mut T {
|
||||
#[inline(always)]
|
||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||
UriDisplay::fmt(*self, f)
|
||||
|
@ -419,7 +415,7 @@ impl<T: UriDisplay<Query>, E> UriDisplay<Query> for Result<T, E> {
|
|||
///
|
||||
/// When a parameter is explicitly ignored in `uri!` by supplying `_` as the
|
||||
/// parameter's value, that parameter's type is required to implement this
|
||||
/// trait for the corresponding `UriPart`.
|
||||
/// trait for the corresponding `Part`.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
|
@ -427,12 +423,12 @@ impl<T: UriDisplay<Query>, E> UriDisplay<Query> for Result<T, E> {
|
|||
/// fn get_item(id: i32, track: Option<u8>) { /* .. */ }
|
||||
///
|
||||
/// // Ignore the `track` parameter: `Option<u8>` must be `Ignorable`.
|
||||
/// uri!(get_item: 100, _);
|
||||
/// uri!(get_item: id = 100, track = _);
|
||||
/// uri!(get_item(100, _));
|
||||
/// uri!(get_item(id = 100, track = _));
|
||||
///
|
||||
/// // Provide a value for `track`.
|
||||
/// uri!(get_item: 100, Some(4));
|
||||
/// uri!(get_item: id = 100, track = Some(4));
|
||||
/// uri!(get_item(100, Some(4)));
|
||||
/// uri!(get_item(id = 100, track = Some(4)));
|
||||
/// ```
|
||||
///
|
||||
/// # Implementations
|
||||
|
@ -442,23 +438,24 @@ impl<T: UriDisplay<Query>, E> UriDisplay<Query> for Result<T, E> {
|
|||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Ignorable, Query};
|
||||
/// use rocket::http::uri::fmt::{Ignorable, Query};
|
||||
///
|
||||
/// # struct MyType;
|
||||
/// impl Ignorable<Query> for MyType { }
|
||||
/// ```
|
||||
pub trait Ignorable<P: UriPart> { }
|
||||
pub trait Ignorable<P: Part> { }
|
||||
|
||||
impl<T> Ignorable<Query> for Option<T> { }
|
||||
impl<T, E> Ignorable<Query> for Result<T, E> { }
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
||||
pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||
|
||||
#[cfg(test)]
|
||||
mod uri_display_tests {
|
||||
use std::path;
|
||||
use crate::uri::{FromUriParam, UriDisplay, Query, Path};
|
||||
use crate::uri::fmt::{FromUriParam, UriDisplay};
|
||||
use crate::uri::fmt::{Query, Path};
|
||||
|
||||
macro_rules! uri_display {
|
||||
(<$P:ident, $Target:ty> $source:expr) => ({
|
||||
|
@ -566,7 +563,7 @@ mod uri_display_tests {
|
|||
|
||||
#[test]
|
||||
fn check_ignorables() {
|
||||
use crate::uri::assert_ignorable;
|
||||
use crate::uri::fmt::assert_ignorable;
|
||||
|
||||
assert_ignorable::<Query, Option<usize>>();
|
||||
assert_ignorable::<Query, Option<Wrapper<usize>>>();
|
|
@ -1,106 +1,25 @@
|
|||
//! Types for URIs and traits for rendering URI components.
|
||||
|
||||
mod uri;
|
||||
mod uri_display;
|
||||
mod formatter;
|
||||
mod from_uri_param;
|
||||
mod origin;
|
||||
mod reference;
|
||||
mod authority;
|
||||
mod absolute;
|
||||
mod segments;
|
||||
mod path_query;
|
||||
mod asterisk;
|
||||
|
||||
pub(crate) mod encoding;
|
||||
pub mod error;
|
||||
pub mod fmt;
|
||||
|
||||
pub use crate::parse::uri::Error;
|
||||
#[doc(inline)]
|
||||
pub use self::error::Error;
|
||||
|
||||
pub use self::uri::*;
|
||||
pub use self::authority::*;
|
||||
pub use self::origin::*;
|
||||
pub use self::absolute::*;
|
||||
pub use self::uri_display::*;
|
||||
pub use self::formatter::*;
|
||||
pub use self::from_uri_param::*;
|
||||
pub use self::segments::*;
|
||||
|
||||
mod private {
|
||||
pub trait Sealed {}
|
||||
impl Sealed for super::Path {}
|
||||
impl Sealed for super::Query {}
|
||||
}
|
||||
|
||||
/// Marker trait for types that mark a part of a URI.
|
||||
///
|
||||
/// This trait exists solely to categorize types that mark a part of the URI,
|
||||
/// currently [`Path`] and [`Query`]. Said another way, types that implement
|
||||
/// this trait are marker types that represent a part of a URI at the
|
||||
/// type-level.
|
||||
///
|
||||
/// This trait is _sealed_: it cannot be implemented outside of Rocket.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// You will find this trait in traits like [`UriDisplay`] or structs like
|
||||
/// [`Formatter`] as the bound on a generic parameter: `P: UriPart`. Because the
|
||||
/// trait is sealed, the generic type is guaranteed to be instantiated as one of
|
||||
/// [`Query`] or [`Path`], effectively creating two instances of the generic
|
||||
/// items: `UriDisplay<Query>` and `UriDisplay<Path>`, and `Formatter<Query>`
|
||||
/// and `Formatter<Path>`. Unlike having two distinct, non-generic traits, this
|
||||
/// approach enables succinct, type-checked generic implementations of these
|
||||
/// items.
|
||||
///
|
||||
/// [`Query`]: crate::uri::Query
|
||||
/// [`Path`]: crate::uri::Path
|
||||
/// [`UriDisplay`]: crate::uri::UriDisplay
|
||||
/// [`Formatter`]: crate::uri::Formatter
|
||||
pub trait UriPart: private::Sealed {
|
||||
/// The dynamic version of `Self`.
|
||||
#[doc(hidden)]
|
||||
const KIND: Kind;
|
||||
|
||||
/// The delimiter used to separate components of this URI part.
|
||||
/// Specifically, `/` for `Path` and `&` for `Query`.
|
||||
#[doc(hidden)]
|
||||
const DELIMITER: char;
|
||||
}
|
||||
|
||||
/// Dynamic version of the `Path` and `Query` parts.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Kind { Path, Query }
|
||||
|
||||
/// Marker type indicating use of a type for the path [`UriPart`] of a URI.
|
||||
///
|
||||
/// In route URIs, this corresponds to all of the text before a `?`, if any, or
|
||||
/// all of the text in the URI otherwise:
|
||||
///
|
||||
/// ```text
|
||||
/// #[get("/home/<name>/<page>?<item>")]
|
||||
/// ^------------------ Path
|
||||
/// ```
|
||||
///
|
||||
/// [`UriPart`]: crate::uri::UriPart
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Path { }
|
||||
|
||||
/// Marker type indicating use of a type for the query [`UriPart`] of a URI.
|
||||
///
|
||||
/// In route URIs, this corresponds to all of the text after a `?`, if any.
|
||||
///
|
||||
/// ```text
|
||||
/// #[get("/home/<name>/<page>?<item>&<form..>")]
|
||||
/// ^-------------- Query
|
||||
/// ```
|
||||
///
|
||||
/// [`UriPart`]: crate::uri::UriPart
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Query { }
|
||||
|
||||
impl UriPart for Path {
|
||||
const KIND: Kind = Kind::Path;
|
||||
const DELIMITER: char = '/';
|
||||
}
|
||||
|
||||
impl UriPart for Query {
|
||||
const KIND: Kind = Kind::Query;
|
||||
const DELIMITER: char = '&';
|
||||
}
|
||||
pub use self::reference::*;
|
||||
pub use self::path_query::*;
|
||||
pub use self::asterisk::*;
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
use std::borrow::Cow;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{self, Display};
|
||||
use std::hash::Hash;
|
||||
|
||||
use crate::ext::IntoOwned;
|
||||
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::parse::{Extent, IndexedStr, uri::tables::is_pchar};
|
||||
use crate::uri::{Error, Path, Query, Data, as_utf8_unchecked, fmt};
|
||||
use crate::{RawStr, RawStrBuf};
|
||||
|
||||
use state::Storage;
|
||||
|
||||
/// A URI with an absolute path and optional query: `/path?query`.
|
||||
///
|
||||
/// Origin URIs are the primary type of URI encountered in Rocket applications.
|
||||
|
@ -53,7 +49,7 @@ use state::Storage;
|
|||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// By contrast, the following are valid but _abnormal_ URIs:
|
||||
/// By contrast, the following are valid but _non-normal_ URIs:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
|
@ -77,7 +73,7 @@ use state::Storage;
|
|||
/// # extern crate rocket;
|
||||
/// # use rocket::http::uri::Origin;
|
||||
/// # let invalid = [
|
||||
/// // abnormal versions
|
||||
/// // non-normal versions
|
||||
/// "//", "/a/b/", "/a/ab//c//d", "/a?a&&b&",
|
||||
///
|
||||
/// // normalized versions
|
||||
|
@ -89,14 +85,11 @@ use state::Storage;
|
|||
/// # assert_eq!(abnormal.into_normalized(), expected);
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Origin<'a> {
|
||||
pub(crate) source: Option<Cow<'a, str>>,
|
||||
pub(crate) path: IndexedStr<'a>,
|
||||
pub(crate) query: Option<IndexedStr<'a>>,
|
||||
|
||||
pub(crate) decoded_path_segs: Storage<Vec<IndexedStr<'static>>>,
|
||||
pub(crate) decoded_query_segs: Storage<Vec<(IndexedStr<'static>, IndexedStr<'static>)>>,
|
||||
pub(crate) path: Data<'a, fmt::Path>,
|
||||
pub(crate) query: Option<Data<'a, fmt::Query>>,
|
||||
}
|
||||
|
||||
impl Hash for Origin<'_> {
|
||||
|
@ -117,7 +110,7 @@ impl Eq for Origin<'_> { }
|
|||
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
|
||||
self.path() == path && self.query().map_or("", |q| q.as_str()) == query
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,32 +134,15 @@ impl IntoOwned for Origin<'_> {
|
|||
source: self.source.into_owned(),
|
||||
path: self.path.into_owned(),
|
||||
query: self.query.into_owned(),
|
||||
decoded_path_segs: self.decoded_path_segs.map(|v| v.into_owned()),
|
||||
decoded_query_segs: self.decoded_query_segs.map(|v| v.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_to_indexed_str<P: UriPart>(
|
||||
value: &RawStr,
|
||||
(indexed, source): (&IndexedStr<'_>, &RawStr)
|
||||
) -> IndexedStr<'static> {
|
||||
let decoded = match P::KIND {
|
||||
uri::Kind::Path => value.percent_decode_lossy(),
|
||||
uri::Kind::Query => value.url_decode_lossy(),
|
||||
};
|
||||
|
||||
match decoded {
|
||||
Cow::Borrowed(b) if indexed.is_indexed() => {
|
||||
let indexed = IndexedStr::checked_from(b, source.as_str());
|
||||
debug_assert!(indexed.is_some());
|
||||
indexed.unwrap_or(IndexedStr::from(Cow::Borrowed("")))
|
||||
}
|
||||
cow => IndexedStr::from(Cow::Owned(cow.into_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Origin<'a> {
|
||||
/// The root: `'/'`.
|
||||
#[doc(hidden)]
|
||||
pub const ROOT: Origin<'static> = Origin::const_new("/", None);
|
||||
|
||||
/// SAFETY: `source` must be UTF-8.
|
||||
#[inline]
|
||||
pub(crate) unsafe fn raw(
|
||||
|
@ -176,11 +152,8 @@ impl<'a> Origin<'a> {
|
|||
) -> Origin<'a> {
|
||||
Origin {
|
||||
source: Some(as_utf8_unchecked(source)),
|
||||
path: path.into(),
|
||||
query: query.map(|q| q.into()),
|
||||
|
||||
decoded_path_segs: Storage::new(),
|
||||
decoded_query_segs: Storage::new(),
|
||||
path: Data::raw(path),
|
||||
query: query.map(Data::raw)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,19 +166,42 @@ impl<'a> Origin<'a> {
|
|||
{
|
||||
Origin {
|
||||
source: None,
|
||||
path: Indexed::from(path.into()),
|
||||
query: query.map(|q| Indexed::from(q.into())),
|
||||
decoded_path_segs: Storage::new(),
|
||||
decoded_query_segs: Storage::new(),
|
||||
path: Data::new(path.into()),
|
||||
query: query.map(Data::new),
|
||||
}
|
||||
}
|
||||
|
||||
// Used to fabricate URIs in several places. Equivalent to `Origin::new("/",
|
||||
// None)` or `Origin::parse("/").unwrap()`. Should not be used outside of
|
||||
// Rocket, though doing so would be harmless.
|
||||
// Used mostly for testing and to construct known good URIs from other parts
|
||||
// of Rocket. This should _really_ not be used outside of Rocket because the
|
||||
// resulting `Origin's` are not guaranteed to be valid origin URIs!
|
||||
#[doc(hidden)]
|
||||
pub fn dummy() -> Origin<'static> {
|
||||
Origin::new::<&'static str, &'static str>("/", None)
|
||||
pub fn path_only<P: Into<Cow<'a, str>>>(path: P) -> Origin<'a> {
|
||||
Origin::new(path, None::<&'a str>)
|
||||
}
|
||||
|
||||
// Used mostly for testing and to construct known good URIs from other parts
|
||||
// of Rocket. This should _really_ not be used outside of Rocket because the
|
||||
// resulting `Origin's` are not guaranteed to be valid origin URIs!
|
||||
#[doc(hidden)]
|
||||
pub const fn const_new(path: &'a str, query: Option<&'a str>) -> Origin<'a> {
|
||||
Origin {
|
||||
source: None,
|
||||
path: Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(path)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
},
|
||||
query: match query {
|
||||
Some(query) => Some(Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(query)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
}),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_query<Q: Into<Option<Cow<'a, str>>>>(&mut self, query: Q) {
|
||||
self.query = query.into().map(Data::new);
|
||||
}
|
||||
|
||||
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||
|
@ -214,7 +210,7 @@ impl<'a> Origin<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// // Parse a valid origin URI.
|
||||
|
@ -224,6 +220,11 @@ impl<'a> Origin<'a> {
|
|||
///
|
||||
/// // Invalid URIs fail to parse.
|
||||
/// Origin::parse("foo bar").expect_err("invalid URI");
|
||||
///
|
||||
/// // Prefer to use `uri!()` when the input is statically known:
|
||||
/// let uri = uri!("/a/b/c?query");
|
||||
/// assert_eq!(uri.path(), "/a/b/c");
|
||||
/// assert_eq!(uri.query().unwrap(), "query");
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
|
||||
crate::parse::uri::origin_from_str(string)
|
||||
|
@ -243,19 +244,14 @@ impl<'a> Origin<'a> {
|
|||
}
|
||||
|
||||
let (path, query) = RawStr::new(string).split_at_byte(b'?');
|
||||
let query = match query.is_empty() {
|
||||
false => Some(query.as_str()),
|
||||
true => None,
|
||||
};
|
||||
|
||||
let query = (!query.is_empty()).then(|| query.as_str());
|
||||
Ok(Origin::new(path.as_str(), query))
|
||||
}
|
||||
|
||||
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||
/// allocate. This method should be used instead of
|
||||
/// [`Origin::parse()`](crate::uri::Origin::parse()) when the source URI is
|
||||
/// already a `String`. Returns an `Error` if `string` is not a valid origin
|
||||
/// URI.
|
||||
/// allocate. This method should be used instead of [`Origin::parse()`] when
|
||||
/// the source URI is already a `String`. Returns an `Error` if `string` is
|
||||
/// not a valid origin URI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -266,7 +262,7 @@ impl<'a> Origin<'a> {
|
|||
/// let source = format!("/foo/{}/three", 2);
|
||||
/// let uri = Origin::parse_owned(source).expect("valid URI");
|
||||
/// assert_eq!(uri.path(), "/foo/2/three");
|
||||
/// assert_eq!(uri.query(), None);
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
pub fn parse_owned(string: String) -> Result<Origin<'static>, Error<'static>> {
|
||||
// We create a copy of a pointer to `string` to escape the borrow
|
||||
|
@ -280,7 +276,6 @@ impl<'a> Origin<'a> {
|
|||
// These two facts can be easily verified. An `&mut` can't be created
|
||||
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
||||
// since it's passed in to `source`.
|
||||
// let copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||
let copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||
let origin = Origin::parse(copy_of_str)?;
|
||||
debug_assert!(origin.source.is_some(), "Origin source parsed w/o source");
|
||||
|
@ -288,8 +283,6 @@ impl<'a> Origin<'a> {
|
|||
let origin = Origin {
|
||||
path: origin.path.into_owned(),
|
||||
query: origin.query.into_owned(),
|
||||
decoded_path_segs: origin.decoded_path_segs.into_owned(),
|
||||
decoded_query_segs: origin.decoded_query_segs.into_owned(),
|
||||
// At this point, it's impossible for anything to be borrowing
|
||||
// `string` except for `source`, even though Rust doesn't know it.
|
||||
// Because we're replacing `source` here, there can't possibly be a
|
||||
|
@ -300,118 +293,39 @@ impl<'a> Origin<'a> {
|
|||
Ok(origin)
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||
///
|
||||
/// See [Normalization](#normalization) for more information on what it
|
||||
/// means for an origin URI to be normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let normal = Origin::parse("/").unwrap();
|
||||
/// assert!(normal.is_normalized());
|
||||
///
|
||||
/// let normal = Origin::parse("/a/b/c").unwrap();
|
||||
/// assert!(normal.is_normalized());
|
||||
///
|
||||
/// let normal = Origin::parse("/a/b/c?a=b&c").unwrap();
|
||||
/// assert!(normal.is_normalized());
|
||||
///
|
||||
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||
/// assert!(!abnormal.is_normalized());
|
||||
///
|
||||
/// let abnormal = Origin::parse("/a?q&&b").unwrap();
|
||||
/// assert!(!abnormal.is_normalized());
|
||||
/// ```
|
||||
pub fn is_normalized(&self) -> bool {
|
||||
self.path().starts_with('/')
|
||||
&& self.raw_path_segments().all(|s| !s.is_empty())
|
||||
&& self.raw_query_segments().all(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Normalizes `self`.
|
||||
///
|
||||
/// See [Normalization](#normalization) for more information on what it
|
||||
/// means for an origin URI to be normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||
/// assert!(!abnormal.is_normalized());
|
||||
///
|
||||
/// let normalized = abnormal.into_normalized();
|
||||
/// assert!(normalized.is_normalized());
|
||||
/// assert_eq!(normalized, Origin::parse("/a/b/c/d").unwrap());
|
||||
/// ```
|
||||
pub fn into_normalized(mut self) -> Self {
|
||||
use std::fmt::Write;
|
||||
|
||||
if self.is_normalized() {
|
||||
self
|
||||
} else {
|
||||
let mut new_path = String::with_capacity(self.path().len());
|
||||
for seg in self.raw_path_segments().filter(|s| !s.is_empty()) {
|
||||
let _ = write!(new_path, "/{}", seg);
|
||||
}
|
||||
|
||||
if new_path.is_empty() {
|
||||
new_path.push('/');
|
||||
}
|
||||
|
||||
self.path = Indexed::from(Cow::Owned(new_path));
|
||||
|
||||
if let Some(q) = self.query() {
|
||||
let mut new_query = String::with_capacity(q.len());
|
||||
let raw_segments = self.raw_query_segments()
|
||||
.filter(|s| !s.is_empty())
|
||||
.enumerate();
|
||||
|
||||
for (i, seg) in raw_segments {
|
||||
if i != 0 { new_query.push('&'); }
|
||||
let _ = write!(new_query, "{}", seg);
|
||||
}
|
||||
|
||||
self.query = Some(Indexed::from(Cow::Owned(new_query)));
|
||||
}
|
||||
|
||||
// Note: normalization preserves segments!
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the path part of this URI.
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// A URI with only a path:
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a/b/c").unwrap();
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// assert_eq!(uri.path(), "/a/b/c");
|
||||
/// ```
|
||||
///
|
||||
/// A URI with a query:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a/b/c?name=bob").unwrap();
|
||||
/// let uri = uri!("/a/b/c?name=bob");
|
||||
/// assert_eq!(uri.path(), "/a/b/c");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn path(&self) -> &RawStr {
|
||||
self.path.from_cow_source(&self.source).into()
|
||||
pub fn path(&self) -> Path<'_> {
|
||||
Path { source: &self.source, data: &self.path }
|
||||
}
|
||||
|
||||
/// Returns the query part of this URI without the question mark, if there
|
||||
/// is any.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/a/b/c?alphabet=true");
|
||||
/// assert_eq!(uri.query().unwrap(), "alphabet=true");
|
||||
///
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn query(&self) -> Option<Query<'_>> {
|
||||
self.query.as_ref().map(|data| Query { source: &self.source, data })
|
||||
}
|
||||
|
||||
/// Applies the function `f` to the internal `path` and returns a new
|
||||
|
@ -424,94 +338,88 @@ impl<'a> Origin<'a> {
|
|||
/// Affix a trailing slash if one isn't present.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// let expected_uri = uri!("/a/b/c/d");
|
||||
/// assert_eq!(uri.map_path(|p| format!("{}/d", p)), Some(expected_uri));
|
||||
///
|
||||
/// let old_uri = Origin::parse("/a/b/c").unwrap();
|
||||
/// let expected_uri = Origin::parse("/a/b/c/").unwrap();
|
||||
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// let abnormal_map = uri.map_path(|p| format!("{}///d", p));
|
||||
/// assert_eq!(abnormal_map.unwrap(), "/a/b/c///d");
|
||||
///
|
||||
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
|
||||
/// let expected_uri = Origin::parse("/a/b/c//").unwrap();
|
||||
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// let expected = uri!("/b/c");
|
||||
/// let mapped = uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p));
|
||||
/// assert_eq!(mapped, Some(expected));
|
||||
///
|
||||
/// 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 uri = uri!("/a");
|
||||
/// assert_eq!(uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None);
|
||||
///
|
||||
/// 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);
|
||||
/// let uri = uri!("/a/b/c");
|
||||
/// assert_eq!(uri.map_path(|p| format!("hi/{}", p)), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
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();
|
||||
let path = f(self.path().raw()).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_string()).into(),
|
||||
path: Data::new(Cow::from(path.into_string())),
|
||||
query: self.query.clone(),
|
||||
decoded_path_segs: Storage::new(),
|
||||
decoded_query_segs: Storage::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the query part of this URI without the question mark, if there is
|
||||
/// any.
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
/// A URI with a query part:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a/b/c?alphabet=true").unwrap();
|
||||
/// assert_eq!(uri.query().unwrap(), "alphabet=true");
|
||||
/// ```
|
||||
///
|
||||
/// A URI without the query part:
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a/b/c").unwrap();
|
||||
/// assert_eq!(uri.query(), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn query(&self) -> Option<&RawStr> {
|
||||
self.query.as_ref().map(|q| q.from_cow_source(&self.source).into())
|
||||
}
|
||||
|
||||
/// Removes the query part of this URI, if there is any.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let mut uri = Origin::parse("/a/b/c?query=some").unwrap();
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let mut uri = uri!("/a/b/c?query=some");
|
||||
/// assert_eq!(uri.query().unwrap(), "query=some");
|
||||
///
|
||||
/// uri.clear_query();
|
||||
/// assert_eq!(uri.query(), None);
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
pub fn clear_query(&mut self) {
|
||||
self.query = None;
|
||||
self.set_query(None);
|
||||
}
|
||||
|
||||
/// Returns a (smart) iterator over the non-empty, percent-decoded segments
|
||||
/// of the path of this URI.
|
||||
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||
///
|
||||
/// See [Normalization](#normalization) for more information on what it
|
||||
/// means for an origin URI to be normalized. Note that `uri!()` always
|
||||
/// normalizes static input.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// assert!(Origin::parse("/").unwrap().is_normalized());
|
||||
/// assert!(Origin::parse("/a/b/c").unwrap().is_normalized());
|
||||
/// assert!(Origin::parse("/a/b/c?a=b&c").unwrap().is_normalized());
|
||||
///
|
||||
/// assert!(!Origin::parse("/a/b/c//d").unwrap().is_normalized());
|
||||
/// assert!(!Origin::parse("/a?q&&b").unwrap().is_normalized());
|
||||
///
|
||||
/// assert!(uri!("/a/b/c//d").is_normalized());
|
||||
/// assert!(uri!("/a?q&&b").is_normalized());
|
||||
/// ```
|
||||
pub fn is_normalized(&self) -> bool {
|
||||
self.path().is_normalized(true) && self.query().map_or(true, |q| q.is_normalized())
|
||||
}
|
||||
|
||||
/// Normalizes `self`. This is a no-op if `self` is already normalized.
|
||||
///
|
||||
/// See [Normalization](#normalization) for more information on what it
|
||||
/// means for an origin URI to be normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -519,25 +427,28 @@ impl<'a> Origin<'a> {
|
|||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a%20b/b%2Fc/d//e?query=some").unwrap();
|
||||
/// let path_segs: Vec<&str> = uri.path_segments().collect();
|
||||
/// assert_eq!(path_segs, &["a b", "b/c", "d", "e"]);
|
||||
/// let mut abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||
/// assert!(!abnormal.is_normalized());
|
||||
/// abnormal.normalize();
|
||||
/// assert!(abnormal.is_normalized());
|
||||
/// ```
|
||||
pub fn path_segments(&self) -> Segments<'_> {
|
||||
let cached = self.decoded_path_segs.get_or_set(|| {
|
||||
let (indexed, path) = (&self.path, self.path());
|
||||
self.raw_path_segments()
|
||||
.filter(|r| !r.is_empty())
|
||||
.map(|s| decode_to_indexed_str::<Path>(s, (indexed, path)))
|
||||
.collect()
|
||||
});
|
||||
|
||||
Segments { source: self.path(), segments: cached, pos: 0 }
|
||||
pub fn normalize(&mut self) {
|
||||
if !self.path().is_normalized(true) {
|
||||
self.path = self.path().to_normalized(true);
|
||||
}
|
||||
|
||||
/// Returns a (smart) iterator over the non-empty, url-decoded `(name,
|
||||
/// value)` pairs of the query of this URI. If there is no query, the
|
||||
/// iterator is empty.
|
||||
if let Some(query) = self.query() {
|
||||
if !query.is_normalized() {
|
||||
self.query = query.to_normalized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes `self` and returns a normalized version.
|
||||
///
|
||||
/// This is a no-op if `self` is already normalized. See
|
||||
/// [Normalization](#normalization) for more information on what it means
|
||||
/// for an origin URI to be normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -545,103 +456,13 @@ impl<'a> Origin<'a> {
|
|||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query_segments().collect();
|
||||
/// assert!(query_segs.is_empty());
|
||||
///
|
||||
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query_segments().collect();
|
||||
/// assert_eq!(query_segs, &[("a b/", "some one@gmail.com"), ("&=2", "")]);
|
||||
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||
/// assert!(!abnormal.is_normalized());
|
||||
/// assert!(abnormal.into_normalized().is_normalized());
|
||||
/// ```
|
||||
pub fn query_segments(&self) -> QuerySegments<'_> {
|
||||
let cached = self.decoded_query_segs.get_or_set(|| {
|
||||
let (indexed, query) = match (self.query.as_ref(), self.query()) {
|
||||
(Some(i), Some(q)) => (i, q),
|
||||
_ => return vec![],
|
||||
};
|
||||
|
||||
self.raw_query_segments()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.split_at_byte(b'='))
|
||||
.map(|(name, val)| {
|
||||
let name = decode_to_indexed_str::<Query>(name, (indexed, query));
|
||||
let val = decode_to_indexed_str::<Query>(val, (indexed, query));
|
||||
(name, val)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
QuerySegments { source: self.query(), segments: cached, pos: 0 }
|
||||
}
|
||||
|
||||
/// Returns an iterator over the raw, undecoded segments of the path in this
|
||||
/// URI. Segments may be empty.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
||||
/// assert!(segments.is_empty());
|
||||
///
|
||||
/// let uri = Origin::parse("//").unwrap();
|
||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
||||
/// assert_eq!(segments, &["", ""]);
|
||||
///
|
||||
/// let uri = Origin::parse("/a").unwrap();
|
||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
||||
/// assert_eq!(segments, &["a"]);
|
||||
///
|
||||
/// let uri = Origin::parse("/a//b///c/d?query¶m").unwrap();
|
||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
||||
/// assert_eq!(segments, &["a", "", "b", "", "", "c", "d"]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn raw_path_segments(&self) -> impl Iterator<Item = &RawStr> {
|
||||
let path = match self.path() {
|
||||
p if p == "/" => None,
|
||||
p if p.starts_with('/') => Some(&p[1..]),
|
||||
p => Some(p)
|
||||
};
|
||||
|
||||
path.map(|p| p.split(Path::DELIMITER))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the non-empty, non-url-decoded `(name, value)`
|
||||
/// pairs of the query of this URI. If there is no query, the iterator is
|
||||
/// empty. Segments may be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// assert!(uri.raw_query_segments().next().is_none());
|
||||
///
|
||||
/// let uri = Origin::parse("/?a=b&dog").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
||||
/// assert_eq!(query_segs, &["a=b", "dog"]);
|
||||
///
|
||||
/// let uri = Origin::parse("/?&").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
||||
/// assert_eq!(query_segs, &["", ""]);
|
||||
///
|
||||
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
||||
/// assert_eq!(query_segs, &["a+b%2F=some+one%40gmail.com", "", "%26%3D2"]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn raw_query_segments(&self) -> impl Iterator<Item = &RawStr> {
|
||||
self.query()
|
||||
.into_iter()
|
||||
.flat_map(|q| q.split(Query::DELIMITER))
|
||||
pub fn into_normalized(mut self) -> Self {
|
||||
self.normalize();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -670,11 +491,11 @@ impl<'a> TryFrom<&'a str> for Origin<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Display for Origin<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl std::fmt::Display for Origin<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.path())?;
|
||||
if let Some(q) = self.query() {
|
||||
write!(f, "?{}", q)?;
|
||||
if let Some(query) = self.query() {
|
||||
write!(f, "?{}", query)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -687,7 +508,7 @@ mod tests {
|
|||
|
||||
fn seg_count(path: &str, expected: usize) -> bool {
|
||||
let origin = Origin::parse(path).unwrap();
|
||||
let segments = origin.path_segments();
|
||||
let segments = origin.path().segments();
|
||||
let actual = segments.len();
|
||||
if actual != expected {
|
||||
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
|
||||
|
@ -707,7 +528,7 @@ mod tests {
|
|||
Err(e) => panic!("failed to parse {}: {}", path, e)
|
||||
};
|
||||
|
||||
let actual: Vec<&str> = uri.path_segments().collect();
|
||||
let actual: Vec<&str> = uri.path().segments().collect();
|
||||
actual == expected
|
||||
}
|
||||
|
||||
|
@ -792,7 +613,7 @@ mod tests {
|
|||
|
||||
fn test_query(uri: &str, query: Option<&str>) {
|
||||
let uri = Origin::parse(uri).unwrap();
|
||||
assert_eq!(uri.query().map(|s| s.as_str()), query);
|
||||
assert_eq!(uri.query().map(|q| q.as_str()), query);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -0,0 +1,387 @@
|
|||
use std::hash::Hash;
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
|
||||
use state::Storage;
|
||||
|
||||
use crate::{RawStr, ext::IntoOwned};
|
||||
use crate::uri::Segments;
|
||||
use crate::uri::fmt::{self, Part};
|
||||
use crate::parse::{IndexedStr, Extent};
|
||||
|
||||
// INTERNAL DATA STRUCTURE.
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Data<'a, P: Part> {
|
||||
pub(crate) value: IndexedStr<'a>,
|
||||
pub(crate) decoded_segments: Storage<Vec<P::Raw>>,
|
||||
}
|
||||
|
||||
impl<'a, P: Part> Data<'a, P> {
|
||||
pub(crate) fn raw(value: Extent<&'a [u8]>) -> Self {
|
||||
Data { value: value.into(), decoded_segments: Storage::new() }
|
||||
}
|
||||
|
||||
// INTERNAL METHOD.
|
||||
#[doc(hidden)]
|
||||
pub fn new<S: Into<Cow<'a, str>>>(value: S) -> Self {
|
||||
Data {
|
||||
value: IndexedStr::from(value.into()),
|
||||
decoded_segments: Storage::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A URI path: `/foo/bar`, `foo/bar`, etc.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Path<'a> {
|
||||
pub(crate) source: &'a Option<Cow<'a, str>>,
|
||||
pub(crate) data: &'a Data<'a, fmt::Path>,
|
||||
}
|
||||
|
||||
/// A URI query: `?foo&bar`.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Query<'a> {
|
||||
pub(crate) source: &'a Option<Cow<'a, str>>,
|
||||
pub(crate) data: &'a Data<'a, fmt::Query>,
|
||||
}
|
||||
|
||||
fn decode_to_indexed_str<P: fmt::Part>(
|
||||
value: &RawStr,
|
||||
(indexed, source): (&IndexedStr<'_>, &RawStr)
|
||||
) -> IndexedStr<'static> {
|
||||
let decoded = match P::KIND {
|
||||
fmt::Kind::Path => value.percent_decode_lossy(),
|
||||
fmt::Kind::Query => value.url_decode_lossy(),
|
||||
};
|
||||
|
||||
match decoded {
|
||||
Cow::Borrowed(b) if indexed.is_indexed() => {
|
||||
let indexed = IndexedStr::checked_from(b, source.as_str());
|
||||
debug_assert!(indexed.is_some());
|
||||
indexed.unwrap_or(IndexedStr::from(Cow::Borrowed("")))
|
||||
}
|
||||
cow => IndexedStr::from(Cow::Owned(cow.into_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Path<'a> {
|
||||
/// Returns the raw path value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo%20bar%2dbaz");
|
||||
/// assert_eq!(uri.path(), "/foo%20bar%2dbaz");
|
||||
/// assert_eq!(uri.path().raw(), "/foo%20bar%2dbaz");
|
||||
/// ```
|
||||
pub fn raw(&self) -> &'a RawStr {
|
||||
self.data.value.from_cow_source(&self.source).into()
|
||||
}
|
||||
|
||||
/// Returns the raw, undecoded path value as an `&str`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo%20bar%2dbaz");
|
||||
/// assert_eq!(uri.path(), "/foo%20bar%2dbaz");
|
||||
/// assert_eq!(uri.path().as_str(), "/foo%20bar%2dbaz");
|
||||
/// ```
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
self.raw().as_str()
|
||||
}
|
||||
|
||||
/// Whether `self` is normalized, i.e, it has no empty segments.
|
||||
///
|
||||
/// If `absolute`, then a starting `/` is required.
|
||||
pub(crate) fn is_normalized(&self, absolute: bool) -> bool {
|
||||
(!absolute || self.raw().starts_with('/'))
|
||||
&& self.raw_segments().all(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Normalizes `self`. If `absolute`, a starting `/` is required.
|
||||
pub(crate) fn to_normalized(&self, absolute: bool) -> Data<'static, fmt::Path> {
|
||||
let mut path = String::with_capacity(self.raw().len());
|
||||
let absolute = absolute || self.raw().starts_with('/');
|
||||
for (i, seg) in self.raw_segments().filter(|s| !s.is_empty()).enumerate() {
|
||||
if absolute || i != 0 { path.push('/'); }
|
||||
let _ = write!(path, "{}", seg);
|
||||
}
|
||||
|
||||
if path.is_empty() && absolute {
|
||||
path.push('/');
|
||||
}
|
||||
|
||||
Data {
|
||||
value: IndexedStr::from(Cow::Owned(path)),
|
||||
decoded_segments: Storage::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator over the raw, undecoded segments. Segments may be
|
||||
/// empty.
|
||||
///
|
||||
/// ### Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// assert_eq!(uri.path().raw_segments().count(), 0);
|
||||
///
|
||||
/// let uri = Origin::parse("//").unwrap();
|
||||
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||
/// assert_eq!(segments, &["", ""]);
|
||||
///
|
||||
/// // Recall that `uri!()` normalizes static inputs.
|
||||
/// let uri = uri!("//");
|
||||
/// assert_eq!(uri.path().raw_segments().count(), 0);
|
||||
///
|
||||
/// let uri = Origin::parse("/a").unwrap();
|
||||
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||
/// assert_eq!(segments, &["a"]);
|
||||
///
|
||||
/// let uri = Origin::parse("/a//b///c/d?query¶m").unwrap();
|
||||
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||
/// assert_eq!(segments, &["a", "", "b", "", "", "c", "d"]);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn raw_segments(&self) -> impl Iterator<Item = &'a RawStr> {
|
||||
let path = match self.raw() {
|
||||
p if p.is_empty() || p == "/" => None,
|
||||
p if p.starts_with(fmt::Path::DELIMITER) => Some(&p[1..]),
|
||||
p => Some(p)
|
||||
};
|
||||
|
||||
path.map(|p| p.split(fmt::Path::DELIMITER))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Returns a (smart) iterator over the non-empty, percent-decoded segments.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a%20b/b%2Fc/d//e?query=some").unwrap();
|
||||
/// let path_segs: Vec<&str> = uri.path().segments().collect();
|
||||
/// assert_eq!(path_segs, &["a b", "b/c", "d", "e"]);
|
||||
/// ```
|
||||
pub fn segments(&self) -> Segments<'a, fmt::Path> {
|
||||
let cached = self.data.decoded_segments.get_or_set(|| {
|
||||
let (indexed, path) = (&self.data.value, self.raw());
|
||||
self.raw_segments()
|
||||
.filter(|r| !r.is_empty())
|
||||
.map(|s| decode_to_indexed_str::<fmt::Path>(s, (indexed, path)))
|
||||
.collect()
|
||||
});
|
||||
|
||||
Segments::new(self.raw(), cached)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Query<'a> {
|
||||
/// Returns the raw, undecoded query value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo?baz+bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "baz+bar");
|
||||
/// assert_eq!(uri.query().unwrap().raw(), "baz+bar");
|
||||
/// ```
|
||||
pub fn raw(&self) -> &'a RawStr {
|
||||
self.data.value.from_cow_source(&self.source).into()
|
||||
}
|
||||
|
||||
/// Returns the raw, undecoded query value as an `&str`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo/bar?baz+bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "baz+bar");
|
||||
/// assert_eq!(uri.query().unwrap().as_str(), "baz+bar");
|
||||
/// ```
|
||||
pub fn as_str(&self) -> &'a str {
|
||||
self.raw().as_str()
|
||||
}
|
||||
|
||||
/// Whether `self` is normalized, i.e, it has no empty segments.
|
||||
pub(crate) fn is_normalized(&self) -> bool {
|
||||
!self.is_empty() && self.raw_segments().all(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Normalizes `self`.
|
||||
pub(crate) fn to_normalized(&self) -> Option<Data<'static, fmt::Query>> {
|
||||
let mut query = String::with_capacity(self.raw().len());
|
||||
for (i, seg) in self.raw_segments().filter(|s| !s.is_empty()).enumerate() {
|
||||
if i != 0 { query.push('&'); }
|
||||
let _ = write!(query, "{}", seg);
|
||||
}
|
||||
|
||||
if query.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Data {
|
||||
value: IndexedStr::from(Cow::Owned(query)),
|
||||
decoded_segments: Storage::new(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over the non-empty, undecoded `(name, value)` pairs
|
||||
/// of this query. If there is no query, the iterator is empty. Segments may
|
||||
/// be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// assert!(uri.query().is_none());
|
||||
///
|
||||
/// let uri = Origin::parse("/?a=b&dog").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||
/// assert_eq!(query_segs, &["a=b", "dog"]);
|
||||
///
|
||||
/// // This is not normalized, so the query is `""`, the empty string.
|
||||
/// let uri = Origin::parse("/?&").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||
/// assert_eq!(query_segs, &["", ""]);
|
||||
///
|
||||
/// // Recall that `uri!()` normalizes.
|
||||
/// let uri = uri!("/?&");
|
||||
/// assert!(uri.query().is_none());
|
||||
///
|
||||
/// // These are raw and undecoded. Use `segments()` for decoded variant.
|
||||
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||
/// assert_eq!(query_segs, &["a+b%2F=some+one%40gmail.com", "", "%26%3D2"]);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn raw_segments(&self) -> impl Iterator<Item = &'a RawStr> {
|
||||
let query = match self.raw() {
|
||||
q if q.is_empty() => None,
|
||||
q => Some(q)
|
||||
};
|
||||
|
||||
query.map(|p| p.split(fmt::Query::DELIMITER))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Returns a (smart) iterator over the non-empty, url-decoded `(name,
|
||||
/// value)` pairs of this query. If there is no query, the iterator is
|
||||
/// empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/").unwrap();
|
||||
/// assert!(uri.query().is_none());
|
||||
///
|
||||
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||
/// let query_segs: Vec<_> = uri.query().unwrap().segments().collect();
|
||||
/// assert_eq!(query_segs, &[("a b/", "some one@gmail.com"), ("&=2", "")]);
|
||||
/// ```
|
||||
pub fn segments(&self) -> Segments<'a, fmt::Query> {
|
||||
let cached = self.data.decoded_segments.get_or_set(|| {
|
||||
let (indexed, query) = (&self.data.value, self.raw());
|
||||
self.raw_segments()
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|s| s.split_at_byte(b'='))
|
||||
.map(|(k, v)| {
|
||||
let key = decode_to_indexed_str::<fmt::Query>(k, (indexed, query));
|
||||
let val = decode_to_indexed_str::<fmt::Query>(v, (indexed, query));
|
||||
(key, val)
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
Segments::new(self.raw(), cached)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_partial_eq {
|
||||
($A:ty = $B:ty) => (
|
||||
impl PartialEq<$A> for $B {
|
||||
#[inline(always)]
|
||||
fn eq(&self, other: &$A) -> bool {
|
||||
let left: &RawStr = self.as_ref();
|
||||
let right: &RawStr = other.as_ref();
|
||||
left == right
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
macro_rules! impl_traits {
|
||||
($T:ident) => (
|
||||
impl Hash for $T<'_> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.raw().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for $T<'_> { }
|
||||
|
||||
impl IntoOwned for Data<'_, fmt::$T> {
|
||||
type Owned = Data<'static, fmt::$T>;
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
Data {
|
||||
value: self.value.into_owned(),
|
||||
decoded_segments: self.decoded_segments.map(|v| v.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for $T<'_> {
|
||||
type Target = RawStr;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<RawStr> for $T<'_> {
|
||||
fn as_ref(&self) -> &RawStr {
|
||||
self.raw()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $T<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.raw())
|
||||
}
|
||||
}
|
||||
|
||||
impl_partial_eq!($T<'_> = $T<'_>);
|
||||
impl_partial_eq!(str = $T<'_>);
|
||||
impl_partial_eq!(&str = $T<'_>);
|
||||
impl_partial_eq!($T<'_> = str);
|
||||
impl_partial_eq!($T<'_> = &str);
|
||||
impl_partial_eq!(RawStr = $T<'_>);
|
||||
impl_partial_eq!(&RawStr = $T<'_>);
|
||||
impl_partial_eq!($T<'_> = RawStr);
|
||||
impl_partial_eq!($T<'_> = &RawStr);
|
||||
)
|
||||
}
|
||||
|
||||
impl_traits!(Path);
|
||||
impl_traits!(Query);
|
|
@ -0,0 +1,549 @@
|
|||
use std::{borrow::Cow, convert::TryFrom};
|
||||
|
||||
use crate::RawStr;
|
||||
use crate::ext::IntoOwned;
|
||||
use crate::uri::{Authority, Data, Origin, Absolute, Asterisk};
|
||||
use crate::uri::{Path, Query, Error, as_utf8_unchecked, fmt};
|
||||
use crate::parse::{Extent, IndexedStr};
|
||||
|
||||
/// A URI-reference with optional scheme, authority, relative path, query, and
|
||||
/// fragment parts.
|
||||
///
|
||||
/// # Structure
|
||||
///
|
||||
/// The following diagram illustrates the syntactic structure of a URI reference
|
||||
/// with all optional parts:
|
||||
///
|
||||
/// ```text
|
||||
/// http://user:pass@domain.com:4444/foo/bar?some=query#and-fragment
|
||||
/// |--| |------------------------||------| |--------| |----------|
|
||||
/// scheme authority path query fragment
|
||||
/// ```
|
||||
///
|
||||
/// All parts are optional. When a scheme and authority are not present, the
|
||||
/// path may be relative: `foo/bar?baz#cat`.
|
||||
///
|
||||
/// # Conversion
|
||||
///
|
||||
/// All other URI types ([`Origin`], [`Absolute`], and so on) are valid URI
|
||||
/// references. As such, conversion between the types is lossless:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// let absolute = uri!("http://rocket.rs");
|
||||
/// let reference: Reference = absolute.into();
|
||||
/// assert_eq!(reference.scheme(), Some("http"));
|
||||
/// assert_eq!(reference.authority().unwrap().host(), "rocket.rs");
|
||||
///
|
||||
/// let origin = uri!("/foo/bar");
|
||||
/// let reference: Reference = origin.into();
|
||||
/// assert_eq!(reference.path(), "/foo/bar");
|
||||
/// ```
|
||||
///
|
||||
/// Note that `uri!()` macro _always_ prefers the more specific URI variant to
|
||||
/// `Reference` when possible, as is demonstrated above for `absolute` and
|
||||
/// `origin`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Reference<'a> {
|
||||
source: Option<Cow<'a, str>>,
|
||||
scheme: Option<IndexedStr<'a>>,
|
||||
authority: Option<Authority<'a>>,
|
||||
path: Data<'a, fmt::Path>,
|
||||
query: Option<Data<'a, fmt::Query>>,
|
||||
fragment: Option<IndexedStr<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Reference<'a> {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn raw(
|
||||
source: Cow<'a, [u8]>,
|
||||
scheme: Option<Extent<&'a [u8]>>,
|
||||
authority: Option<Authority<'a>>,
|
||||
path: Extent<&'a [u8]>,
|
||||
query: Option<Extent<&'a [u8]>>,
|
||||
fragment: Option<Extent<&'a [u8]>>,
|
||||
) -> Reference<'a> {
|
||||
Reference {
|
||||
source: Some(as_utf8_unchecked(source)),
|
||||
scheme: scheme.map(|s| s.into()),
|
||||
authority: authority.map(|s| s.into()),
|
||||
path: Data::raw(path),
|
||||
query: query.map(Data::raw),
|
||||
fragment: fragment.map(|f| f.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// PRIVATE. Used during test.
|
||||
#[cfg(test)]
|
||||
pub fn new(
|
||||
scheme: impl Into<Option<&'a str>>,
|
||||
auth: impl Into<Option<Authority<'a>>>,
|
||||
path: &'a str,
|
||||
query: impl Into<Option<&'a str>>,
|
||||
frag: impl Into<Option<&'a str>>,
|
||||
) -> Reference<'a> {
|
||||
Reference::const_new(scheme.into(), auth.into(), path, query.into(), frag.into())
|
||||
}
|
||||
|
||||
/// PRIVATE. Used by codegen.
|
||||
#[doc(hidden)]
|
||||
pub const fn const_new(
|
||||
scheme: Option<&'a str>,
|
||||
authority: Option<Authority<'a>>,
|
||||
path: &'a str,
|
||||
query: Option<&'a str>,
|
||||
fragment: Option<&'a str>,
|
||||
) -> Reference<'a> {
|
||||
Reference {
|
||||
source: None,
|
||||
scheme: match scheme {
|
||||
Some(scheme) => Some(IndexedStr::Concrete(Cow::Borrowed(scheme))),
|
||||
None => None
|
||||
},
|
||||
authority,
|
||||
path: Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(path)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
},
|
||||
query: match query {
|
||||
Some(query) => Some(Data {
|
||||
value: IndexedStr::Concrete(Cow::Borrowed(query)),
|
||||
decoded_segments: state::Storage::new(),
|
||||
}),
|
||||
None => None,
|
||||
},
|
||||
fragment: match fragment {
|
||||
Some(frag) => Some(IndexedStr::Concrete(Cow::Borrowed(frag))),
|
||||
None => None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Parses the string `string` into an `Reference`. Parsing will never
|
||||
/// allocate. Returns an `Error` if `string` is not a valid origin URI.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// // Parse a valid URI reference.
|
||||
/// let uri = Reference::parse("/a/b/c?query").expect("valid URI");
|
||||
/// assert_eq!(uri.path(), "/a/b/c");
|
||||
/// assert_eq!(uri.query().unwrap(), "query");
|
||||
///
|
||||
/// // Invalid URIs fail to parse.
|
||||
/// Reference::parse("foo bar").expect_err("invalid URI");
|
||||
///
|
||||
/// // Prefer to use `uri!()` when the input is statically known:
|
||||
/// let uri = uri!("/a/b/c?query#fragment");
|
||||
/// assert_eq!(uri.path(), "/a/b/c");
|
||||
/// assert_eq!(uri.query().unwrap(), "query");
|
||||
/// assert_eq!(uri.fragment().unwrap(), "fragment");
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Self, Error<'a>> {
|
||||
crate::parse::uri::reference_from_str(string)
|
||||
}
|
||||
|
||||
/// Parses the string `string` into a `Reference`. Never allocates.
|
||||
///
|
||||
/// This method should be used instead of [`Reference::parse()`] when the
|
||||
/// source URI is already a `String`. Returns an `Error` if `string` is not
|
||||
/// a valid URI reference.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// let source = format!("/foo?{}#3", 2);
|
||||
/// let uri = Reference::parse_owned(source).unwrap();
|
||||
/// assert_eq!(uri.path(), "/foo");
|
||||
/// assert_eq!(uri.query().unwrap(), "2");
|
||||
/// assert_eq!(uri.fragment().unwrap(), "3");
|
||||
/// ```
|
||||
pub fn parse_owned(string: String) -> Result<Self, Error<'a>> {
|
||||
// We create a copy of a pointer to `string` to escape the borrow
|
||||
// checker. This is so that we can "move out of the borrow" later.
|
||||
//
|
||||
// For this to be correct and safe, we need to ensure that:
|
||||
//
|
||||
// 1. No `&mut` references to `string` are created after this line.
|
||||
// 2. `string` isn't dropped while `copy_of_str` is live.
|
||||
//
|
||||
// These two facts can be easily verified. An `&mut` can't be created
|
||||
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
||||
// since it's passed in to `source`.
|
||||
let copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||
let uri_ref = Reference::parse(copy_of_str)?;
|
||||
debug_assert!(uri_ref.source.is_some(), "UriRef parsed w/o source");
|
||||
|
||||
let uri_ref = Reference {
|
||||
scheme: uri_ref.scheme.into_owned(),
|
||||
authority: uri_ref.authority.into_owned(),
|
||||
path: uri_ref.path.into_owned(),
|
||||
query: uri_ref.query.into_owned(),
|
||||
fragment: uri_ref.fragment.into_owned(),
|
||||
// At this point, it's impossible for anything to be borrowing
|
||||
// `string` except for `source`, even though Rust doesn't know it.
|
||||
// Because we're replacing `source` here, there can't possibly be a
|
||||
// borrow remaining, it's safe to "move out of the borrow".
|
||||
source: Some(Cow::Owned(string)),
|
||||
};
|
||||
|
||||
Ok(uri_ref)
|
||||
}
|
||||
|
||||
/// Returns the scheme. If `Some`, is non-empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("http://rocket.rs?foo#bar");
|
||||
/// assert_eq!(uri.scheme(), Some("http"));
|
||||
///
|
||||
/// let uri = uri!("ftp:/?foo#bar");
|
||||
/// assert_eq!(uri.scheme(), Some("ftp"));
|
||||
///
|
||||
/// let uri = uri!("?foo#bar");
|
||||
/// assert_eq!(uri.scheme(), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn scheme(&self) -> Option<&str> {
|
||||
self.scheme.as_ref().map(|s| s.from_cow_source(&self.source))
|
||||
}
|
||||
|
||||
/// Returns the authority part.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("http://rocket.rs:4444?foo#bar");
|
||||
/// let auth = uri!("rocket.rs:4444");
|
||||
/// assert_eq!(uri.authority().unwrap(), &auth);
|
||||
///
|
||||
/// let uri = uri!("?foo#bar");
|
||||
/// assert_eq!(uri.authority(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||
self.authority.as_ref()
|
||||
}
|
||||
|
||||
/// Returns the path part. May be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||
/// assert_eq!(uri.path(), "/guide");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn path(&self) -> Path<'_> {
|
||||
Path { source: &self.source, data: &self.path }
|
||||
}
|
||||
|
||||
/// Returns the query part. May be empty.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "foo");
|
||||
///
|
||||
/// let uri = uri!("http://rocket.rs/guide?q=bar");
|
||||
/// assert_eq!(uri.query().unwrap(), "q=bar");
|
||||
///
|
||||
/// // Empty query parts are normalized away by `uri!()`.
|
||||
/// let uri = uri!("http://rocket.rs/guide?#bar");
|
||||
/// assert!(uri.query().is_none());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn query(&self) -> Option<Query<'_>> {
|
||||
self.query.as_ref().map(|data| Query { source: &self.source, data })
|
||||
}
|
||||
|
||||
/// Returns the fragment part, if any.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||
/// assert_eq!(uri.fragment().unwrap(), "bar");
|
||||
///
|
||||
/// // Fragment parts aren't normalized away, unlike query parts.
|
||||
/// let uri = uri!("http://rocket.rs/guide?foo#");
|
||||
/// assert_eq!(uri.fragment().unwrap(), "");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn fragment(&self) -> Option<&RawStr> {
|
||||
self.fragment.as_ref()
|
||||
.map(|frag| frag.from_cow_source(&self.source).into())
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||
///
|
||||
/// Normalization for a URI reference is equivalent to normalization for an
|
||||
/// absolute URI. See [`Absolute#normalization`] for more information on
|
||||
/// what it means for an absolute URI to be normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// assert!(Reference::parse("foo/bar").unwrap().is_normalized());
|
||||
/// assert!(Reference::parse("foo/bar#").unwrap().is_normalized());
|
||||
/// assert!(Reference::parse("http://").unwrap().is_normalized());
|
||||
/// assert!(Reference::parse("http://foo.rs/foo/bar").unwrap().is_normalized());
|
||||
/// assert!(Reference::parse("foo:bar#baz").unwrap().is_normalized());
|
||||
/// assert!(Reference::parse("http://rocket.rs#foo").unwrap().is_normalized());
|
||||
///
|
||||
/// assert!(!Reference::parse("http://?").unwrap().is_normalized());
|
||||
/// assert!(!Reference::parse("git://rocket.rs/").unwrap().is_normalized());
|
||||
/// assert!(!Reference::parse("http:/foo//bar").unwrap().is_normalized());
|
||||
/// assert!(!Reference::parse("foo:bar?baz&&bop#c").unwrap().is_normalized());
|
||||
/// assert!(!Reference::parse("http://rocket.rs?#foo").unwrap().is_normalized());
|
||||
///
|
||||
/// // Recall that `uri!()` normalizes static input.
|
||||
/// assert!(uri!("http://rocket.rs#foo").is_normalized());
|
||||
/// assert!(uri!("http://rocket.rs///foo////bar#cat").is_normalized());
|
||||
/// ```
|
||||
pub fn is_normalized(&self) -> bool {
|
||||
let normalized_query = self.query().map_or(true, |q| q.is_normalized());
|
||||
if self.authority().is_some() && !self.path().is_empty() {
|
||||
self.path().is_normalized(true)
|
||||
&& self.path() != "/"
|
||||
&& normalized_query
|
||||
} else {
|
||||
self.path().is_normalized(false) && normalized_query
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes `self` in-place. Does nothing if `self` is already
|
||||
/// normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// let mut uri = Reference::parse("git://rocket.rs/").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
///
|
||||
/// let mut uri = Reference::parse("http:/foo//bar?baz&&#cat").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
///
|
||||
/// let mut uri = Reference::parse("foo:bar?baz&&bop").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// uri.normalize();
|
||||
/// assert!(uri.is_normalized());
|
||||
/// ```
|
||||
pub fn normalize(&mut self) {
|
||||
if self.authority().is_some() && !self.path().is_empty() {
|
||||
if self.path() == "/" {
|
||||
self.set_path("");
|
||||
} else if !self.path().is_normalized(true) {
|
||||
self.path = self.path().to_normalized(true);
|
||||
}
|
||||
} else {
|
||||
self.path = self.path().to_normalized(false);
|
||||
}
|
||||
|
||||
if let Some(query) = self.query() {
|
||||
if !query.is_normalized() {
|
||||
self.query = query.to_normalized();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalizes `self`. This is a no-op if `self` is already normalized.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Reference;
|
||||
///
|
||||
/// let mut uri = Reference::parse("git://rocket.rs/").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
///
|
||||
/// let mut uri = Reference::parse("http:/foo//bar?baz&&#cat").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
///
|
||||
/// let mut uri = Reference::parse("foo:bar?baz&&bop").unwrap();
|
||||
/// assert!(!uri.is_normalized());
|
||||
/// assert!(uri.into_normalized().is_normalized());
|
||||
/// ```
|
||||
pub fn into_normalized(mut self) -> Self {
|
||||
self.normalize();
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn set_path<P>(&mut self, path: P)
|
||||
where P: Into<Cow<'a, str>>
|
||||
{
|
||||
self.path = Data::new(path.into());
|
||||
}
|
||||
|
||||
/// Returns the conrete path and query.
|
||||
pub(crate) fn with_query_fragment_of(mut self, other: Reference<'a>) -> Self {
|
||||
if let Some(query) = other.query {
|
||||
if self.query().is_none() {
|
||||
self.query = Some(Data::new(query.value.into_concrete(&self.source)));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(frag) = other.fragment {
|
||||
if self.fragment().is_none() {
|
||||
self.fragment = Some(IndexedStr::from(frag.into_concrete(&self.source)));
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<Reference<'_>> for Reference<'_> {
|
||||
fn eq(&self, other: &Reference<'_>) -> bool {
|
||||
self.scheme() == other.scheme()
|
||||
&& self.authority() == other.authority()
|
||||
&& self.path() == other.path()
|
||||
&& self.query() == other.query()
|
||||
&& self.fragment() == other.fragment()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoOwned for Reference<'_> {
|
||||
type Owned = Reference<'static>;
|
||||
|
||||
fn into_owned(self) -> Self::Owned {
|
||||
Reference {
|
||||
source: self.source.into_owned(),
|
||||
scheme: self.scheme.into_owned(),
|
||||
authority: self.authority.into_owned(),
|
||||
path: self.path.into_owned(),
|
||||
query: self.query.into_owned(),
|
||||
fragment: self.fragment.into_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Absolute<'a>> for Reference<'a> {
|
||||
fn from(absolute: Absolute<'a>) -> Self {
|
||||
Reference {
|
||||
source: absolute.source,
|
||||
scheme: Some(absolute.scheme),
|
||||
authority: absolute.authority,
|
||||
path: absolute.path,
|
||||
query: absolute.query,
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Origin<'a>> for Reference<'a> {
|
||||
fn from(origin: Origin<'a>) -> Self {
|
||||
Reference {
|
||||
source: origin.source,
|
||||
scheme: None,
|
||||
authority: None,
|
||||
path: origin.path,
|
||||
query: origin.query,
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Authority<'a>> for Reference<'a> {
|
||||
fn from(authority: Authority<'a>) -> Self {
|
||||
Reference {
|
||||
source: match authority.source {
|
||||
Some(Cow::Borrowed(b)) => Some(Cow::Borrowed(b)),
|
||||
_ => None
|
||||
},
|
||||
authority: Some(authority),
|
||||
scheme: None,
|
||||
path: Data::new(""),
|
||||
query: None,
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Asterisk> for Reference<'_> {
|
||||
fn from(_: Asterisk) -> Self {
|
||||
Reference {
|
||||
source: None,
|
||||
authority: None,
|
||||
scheme: None,
|
||||
path: Data::new("*"),
|
||||
query: None,
|
||||
fragment: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Reference<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||
Reference::parse(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Reference<'static> {
|
||||
type Error = Error<'static>;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
Reference::parse_owned(value)
|
||||
}
|
||||
}
|
||||
|
||||
// Because inference doesn't take `&String` to `&str`.
|
||||
impl<'a> TryFrom<&'a String> for Reference<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
|
||||
Reference::parse(value.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Reference<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if let Some(scheme) = self.scheme() {
|
||||
write!(f, "{}:", scheme)?;
|
||||
}
|
||||
|
||||
if let Some(authority) = self.authority() {
|
||||
write!(f, "//{}", authority)?;
|
||||
}
|
||||
|
||||
write!(f, "{}", self.path())?;
|
||||
|
||||
if let Some(query) = self.query() {
|
||||
write!(f, "?{}", query)?;
|
||||
}
|
||||
|
||||
if let Some(frag) = self.fragment() {
|
||||
write!(f, "#{}", frag)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::RawStr;
|
||||
use crate::parse::IndexedStr;
|
||||
use crate::uri::fmt::{Part, Path, Query};
|
||||
use crate::uri::error::PathError;
|
||||
|
||||
/// Iterator over the non-empty, percent-decoded segments of a URI path.
|
||||
/// Iterator over the non-empty, percent-decoded segments of a URI component.
|
||||
///
|
||||
/// Returned by [`Origin::path_segments()`].
|
||||
/// Returned by [`Path::segments()`] and [`Query::segments()`].
|
||||
///
|
||||
/// [`Path::segments()`]: crate::uri::Path::segments()
|
||||
/// [`Query::segments()`]: crate::uri::Query::segments()
|
||||
///
|
||||
/// ### Examples
|
||||
///
|
||||
|
@ -14,7 +18,7 @@ use crate::parse::IndexedStr;
|
|||
/// use rocket::http::uri::Origin;
|
||||
///
|
||||
/// let uri = Origin::parse("/a%20z/////b/c////////d").unwrap();
|
||||
/// let segments = uri.path_segments();
|
||||
/// let segments = uri.path().segments();
|
||||
/// for (i, segment) in segments.enumerate() {
|
||||
/// match i {
|
||||
/// 0 => assert_eq!(segment, "a z"),
|
||||
|
@ -24,31 +28,44 @@ use crate::parse::IndexedStr;
|
|||
/// _ => panic!("only four segments")
|
||||
/// }
|
||||
/// }
|
||||
/// # assert_eq!(uri.path_segments().len(), 4);
|
||||
/// # assert_eq!(uri.path_segments().count(), 4);
|
||||
/// # assert_eq!(uri.path_segments().next(), Some("a z"));
|
||||
/// # assert_eq!(uri.path().segments().len(), 4);
|
||||
/// # assert_eq!(uri.path().segments().count(), 4);
|
||||
/// # assert_eq!(uri.path().segments().next(), Some("a z"));
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Segments<'o> {
|
||||
pub(super) source: &'o RawStr,
|
||||
pub(super) segments: &'o [IndexedStr<'static>],
|
||||
pub struct Segments<'a, P: Part> {
|
||||
pub(super) source: &'a RawStr,
|
||||
pub(super) segments: &'a [P::Raw],
|
||||
pub(super) pos: usize,
|
||||
}
|
||||
|
||||
/// An error interpreting a segment as a [`PathBuf`] component in
|
||||
/// [`Segments::to_path_buf()`].
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum PathError {
|
||||
/// The segment started with the wrapped invalid character.
|
||||
BadStart(char),
|
||||
/// The segment contained the wrapped invalid character.
|
||||
BadChar(char),
|
||||
/// The segment ended with the wrapped invalid character.
|
||||
BadEnd(char),
|
||||
}
|
||||
impl<P: Part> Segments<'_, P> {
|
||||
#[doc(hidden)]
|
||||
#[inline(always)]
|
||||
pub fn new<'a>(source: &'a RawStr, segments: &'a [P::Raw]) -> Segments<'a, P> {
|
||||
Segments { source, segments, pos: 0, }
|
||||
}
|
||||
|
||||
impl<'o> Segments<'o> {
|
||||
/// Returns the number of path segments left.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo/bar?baz&cat&car");
|
||||
///
|
||||
/// let mut segments = uri.path().segments();
|
||||
/// assert_eq!(segments.len(), 2);
|
||||
///
|
||||
/// segments.next();
|
||||
/// assert_eq!(segments.len(), 1);
|
||||
///
|
||||
/// segments.next();
|
||||
/// assert_eq!(segments.len(), 0);
|
||||
///
|
||||
/// segments.next();
|
||||
/// assert_eq!(segments.len(), 0);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
let max_pos = std::cmp::min(self.pos, self.segments.len());
|
||||
|
@ -56,28 +73,88 @@ impl<'o> Segments<'o> {
|
|||
}
|
||||
|
||||
/// Returns `true` if there are no segments left.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo/bar?baz&cat&car");
|
||||
///
|
||||
/// let mut segments = uri.path().segments();
|
||||
/// assert!(!segments.is_empty());
|
||||
///
|
||||
/// segments.next();
|
||||
/// segments.next();
|
||||
/// assert!(segments.is_empty());
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Skips `n` segments.
|
||||
/// Returns a new `Segments` with `n` segments skipped.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo/bar/baz/cat");
|
||||
///
|
||||
/// let mut segments = uri.path().segments();
|
||||
/// assert_eq!(segments.len(), 4);
|
||||
/// assert_eq!(segments.next(), Some("foo"));
|
||||
///
|
||||
/// let mut segments = segments.skip(2);
|
||||
/// assert_eq!(segments.len(), 1);
|
||||
/// assert_eq!(segments.next(), Some("cat"));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn skip(mut self, n: usize) -> Self {
|
||||
self.pos = std::cmp::min(self.pos + n, self.segments.len());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the `n`th segment from the current position.
|
||||
impl<'a> Segments<'a, Path> {
|
||||
/// Returns the `n`th segment, 0-indexed, from the current position.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/foo/bar/baaaz/cat");
|
||||
///
|
||||
/// let segments = uri.path().segments();
|
||||
/// assert_eq!(segments.get(0), Some("foo"));
|
||||
/// assert_eq!(segments.get(1), Some("bar"));
|
||||
/// assert_eq!(segments.get(2), Some("baaaz"));
|
||||
/// assert_eq!(segments.get(3), Some("cat"));
|
||||
/// assert_eq!(segments.get(4), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get(&self, n: usize) -> Option<&'o str> {
|
||||
pub fn get(&self, n: usize) -> Option<&'a str> {
|
||||
self.segments.get(self.pos + n)
|
||||
.map(|i| i.from_source(Some(self.source.as_str())))
|
||||
}
|
||||
|
||||
/// Returns `true` if `self` is a prefix of `other`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let a = uri!("/foo/bar/baaaz/cat");
|
||||
/// let b = uri!("/foo/bar");
|
||||
///
|
||||
/// assert!(b.path().segments().prefix_of(a.path().segments()));
|
||||
/// assert!(!a.path().segments().prefix_of(b.path().segments()));
|
||||
///
|
||||
/// let a = uri!("/foo/bar/baaaz/cat");
|
||||
/// let b = uri!("/dog/foo/bar");
|
||||
/// assert!(b.path().segments().skip(1).prefix_of(a.path().segments()));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn prefix_of<'b>(self, other: Segments<'b>) -> bool {
|
||||
pub fn prefix_of<'b>(self, other: Segments<'b, Path>) -> bool {
|
||||
if self.len() > other.len() {
|
||||
return false;
|
||||
}
|
||||
|
@ -86,17 +163,17 @@ impl<'o> Segments<'o> {
|
|||
}
|
||||
|
||||
/// Creates a `PathBuf` from `self`. The returned `PathBuf` is
|
||||
/// percent-decoded. If a segment is equal to "..", the previous segment (if
|
||||
/// percent-decoded. If a segment is equal to `..`, the previous segment (if
|
||||
/// any) is skipped.
|
||||
///
|
||||
/// For security purposes, if a segment meets any of the following
|
||||
/// conditions, an `Err` is returned indicating the condition met:
|
||||
///
|
||||
/// * Decoded segment starts with any of: '*'
|
||||
/// * Decoded segment starts with any of: `*`
|
||||
/// * Decoded segment ends with any of: `:`, `>`, `<`
|
||||
/// * Decoded segment contains any of: `/`
|
||||
/// * On Windows, decoded segment contains any of: `\`
|
||||
/// * Percent-encoding results in invalid UTF8.
|
||||
/// * Percent-encoding results in invalid UTF-8.
|
||||
///
|
||||
/// Additionally, if `allow_dotfiles` is `false`, an `Err` is returned if
|
||||
/// the following condition is met:
|
||||
|
@ -106,6 +183,21 @@ impl<'o> Segments<'o> {
|
|||
/// As a result of these conditions, a `PathBuf` derived via `FromSegments`
|
||||
/// is safe to interpolate within, or use as a suffix of, a path without
|
||||
/// additional checks.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let uri = uri!("/a/b/c/d/.pass");
|
||||
///
|
||||
/// let path = uri.path().segments().to_path_buf(true);
|
||||
/// assert_eq!(path.unwrap(), Path::new("a/b/c/d/.pass"));
|
||||
///
|
||||
/// let path = uri.path().segments().to_path_buf(false);
|
||||
/// assert!(path.is_err());
|
||||
/// ```
|
||||
pub fn to_path_buf(&self, allow_dotfiles: bool) -> Result<PathBuf, PathError> {
|
||||
let mut buf = PathBuf::new();
|
||||
for segment in self.clone() {
|
||||
|
@ -130,62 +222,45 @@ impl<'o> Segments<'o> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Should we check the filename against the list in `FileName`?
|
||||
// That list is mostly for writing, while this is likely to be read.
|
||||
// TODO: Add an option to allow/disallow shell characters?
|
||||
|
||||
Ok(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'o> Iterator for Segments<'o> {
|
||||
type Item = &'o str;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = self.get(0)?;
|
||||
self.pos += 1;
|
||||
Some(item)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.len(), Some(self.len()))
|
||||
}
|
||||
|
||||
fn count(self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Decoded query segments iterator.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct QuerySegments<'o> {
|
||||
pub(super) source: Option<&'o RawStr>,
|
||||
pub(super) segments: &'o [(IndexedStr<'static>, IndexedStr<'static>)],
|
||||
pub(super) pos: usize,
|
||||
}
|
||||
|
||||
impl<'o> QuerySegments<'o> {
|
||||
/// Returns the number of query segments left.
|
||||
pub fn len(&self) -> usize {
|
||||
let max_pos = std::cmp::min(self.pos, self.segments.len());
|
||||
self.segments.len() - max_pos
|
||||
}
|
||||
|
||||
/// Skip `n` segments.
|
||||
pub fn skip(mut self, n: usize) -> Self {
|
||||
self.pos = std::cmp::min(self.pos + n, self.segments.len());
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the `n`th segment from the current position.
|
||||
impl<'a> Segments<'a, Query> {
|
||||
/// Returns the `n`th segment, 0-indexed, from the current position.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// let uri = uri!("/?foo=1&bar=hi+there&baaaz&cat=dog&=oh");
|
||||
///
|
||||
/// let segments = uri.query().unwrap().segments();
|
||||
/// assert_eq!(segments.get(0), Some(("foo", "1")));
|
||||
/// assert_eq!(segments.get(1), Some(("bar", "hi there")));
|
||||
/// assert_eq!(segments.get(2), Some(("baaaz", "")));
|
||||
/// assert_eq!(segments.get(3), Some(("cat", "dog")));
|
||||
/// assert_eq!(segments.get(4), Some(("", "oh")));
|
||||
/// assert_eq!(segments.get(5), None);
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn get(&self, n: usize) -> Option<(&'o str, &'o str)> {
|
||||
pub fn get(&self, n: usize) -> Option<(&'a str, &'a str)> {
|
||||
let (name, val) = self.segments.get(self.pos + n)?;
|
||||
let source = self.source.map(|s| s.as_str());
|
||||
let source = Some(self.source.as_str());
|
||||
let name = name.from_source(source);
|
||||
let val = val.from_source(source);
|
||||
Some((name, val))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'o> Iterator for QuerySegments<'o> {
|
||||
type Item = (&'o str, &'o str);
|
||||
macro_rules! impl_iterator {
|
||||
($T:ty => $I:ty) => (
|
||||
impl<'a> Iterator for Segments<'a, $T> {
|
||||
type Item = $I;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let item = self.get(0)?;
|
||||
|
@ -200,4 +275,10 @@ impl<'o> Iterator for QuerySegments<'o> {
|
|||
fn count(self) -> usize {
|
||||
self.len()
|
||||
}
|
||||
}
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
impl_iterator!(Path => &'a str);
|
||||
impl_iterator!(Query => (&'a str, &'a str));
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
use std::fmt::{self, Display};
|
||||
use std::convert::From;
|
||||
use std::borrow::Cow;
|
||||
use std::str::Utf8Error;
|
||||
use std::convert::TryFrom;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::RawStr;
|
||||
use crate::ext::IntoOwned;
|
||||
use crate::parse::Extent;
|
||||
use crate::uri::{Origin, Authority, Absolute, Error};
|
||||
use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||
use crate::uri::{Origin, Authority, Absolute, Reference, Asterisk};
|
||||
use crate::uri::error::{Error, TryFromUriError};
|
||||
|
||||
/// An `enum` encapsulating any of the possible URI variants.
|
||||
///
|
||||
|
@ -16,15 +12,7 @@ use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
|||
///
|
||||
/// In Rocket, this type will rarely be used directly. Instead, you will
|
||||
/// typically encounter URIs via the [`Origin`] type. This is because all
|
||||
/// incoming requests contain origin-type URIs.
|
||||
///
|
||||
/// Nevertheless, the `Uri` type is typically enountered as a conversion target.
|
||||
/// In particular, you will likely see generic bounds of the form: `T:
|
||||
/// TryInto<Uri>` (for instance, in [`Redirect`](rocket::response::Redirect)
|
||||
/// methods). This means that you can provide any type `T` that implements
|
||||
/// `TryInto<Uri>`, or, equivalently, any type `U` for which `Uri` implements
|
||||
/// `TryFrom<U>` or `From<U>`. These include `&str` and `String`, [`Origin`],
|
||||
/// [`Authority`], and [`Absolute`].
|
||||
/// incoming requests accepred by Rocket contain URIs in origin-form.
|
||||
///
|
||||
/// ## Parsing
|
||||
///
|
||||
|
@ -32,8 +20,7 @@ use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
|||
/// compliant "request target" parser with limited liberties for real-world
|
||||
/// deviations. In particular, the parser deviates as follows:
|
||||
///
|
||||
/// * It accepts `%` characters without two trailing hex-digits unless it is
|
||||
/// the only character in the URI.
|
||||
/// * It accepts `%` characters without two trailing hex-digits.
|
||||
///
|
||||
/// * It accepts the following additional unencoded characters in query parts,
|
||||
/// to match real-world browser behavior:
|
||||
|
@ -46,63 +33,102 @@ use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
|||
/// methods of the internal structure.
|
||||
///
|
||||
/// [RFC 7230]: https://tools.ietf.org/html/rfc7230
|
||||
///
|
||||
/// ## Percent Encoding/Decoding
|
||||
///
|
||||
/// This type also provides the following percent encoding/decoding helper
|
||||
/// methods: [`Uri::percent_encode()`], [`Uri::percent_decode()`], and
|
||||
/// [`Uri::percent_decode_lossy()`].
|
||||
///
|
||||
/// [`Origin`]: crate::uri::Origin
|
||||
/// [`Authority`]: crate::uri::Authority
|
||||
/// [`Absolute`]: crate::uri::Absolute
|
||||
/// [`Uri::parse()`]: crate::uri::Uri::parse()
|
||||
/// [`Uri::percent_encode()`]: crate::uri::Uri::percent_encode()
|
||||
/// [`Uri::percent_decode()`]: crate::uri::Uri::percent_decode()
|
||||
/// [`Uri::percent_decode_lossy()`]: crate::uri::Uri::percent_decode_lossy()
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Uri<'a> {
|
||||
/// An asterisk: exactly `*`.
|
||||
Asterisk(Asterisk),
|
||||
/// An origin URI.
|
||||
Origin(Origin<'a>),
|
||||
/// An authority URI.
|
||||
Authority(Authority<'a>),
|
||||
/// An absolute URI.
|
||||
Absolute(Absolute<'a>),
|
||||
/// An asterisk: exactly `*`.
|
||||
Asterisk,
|
||||
/// A URI reference.
|
||||
Reference(Reference<'a>),
|
||||
}
|
||||
|
||||
impl<'a> Uri<'a> {
|
||||
#[inline]
|
||||
pub(crate) unsafe fn raw_absolute(
|
||||
source: Cow<'a, [u8]>,
|
||||
scheme: Extent<&'a [u8]>,
|
||||
path: Extent<&'a [u8]>,
|
||||
query: Option<Extent<&'a [u8]>>,
|
||||
) -> Uri<'a> {
|
||||
let origin = Origin::raw(source.clone(), path, query);
|
||||
Uri::Absolute(Absolute::raw(source.clone(), scheme, None, Some(origin)))
|
||||
}
|
||||
|
||||
/// Parses the string `string` into a `Uri`. Parsing will never allocate.
|
||||
/// Returns an `Error` if `string` is not a valid URI.
|
||||
/// Parses the string `string` into a `Uri` of kind `T`.
|
||||
///
|
||||
/// This is identical to `T::parse(string).map(Uri::from)`.
|
||||
///
|
||||
/// `T` is typically one of [`Asterisk`], [`Origin`], [`Authority`],
|
||||
/// [`Absolute`], or [`Reference`]. Parsing never allocates. Returns an
|
||||
/// `Error` if `string` is not a valid URI of kind `T`.
|
||||
///
|
||||
/// To perform an ambgiuous parse into _any_ valid URI type, use
|
||||
/// [`Uri::parse_any()`].
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Origin};
|
||||
///
|
||||
/// // Parse a valid origin URI (note: in practice, use `Origin::parse()`).
|
||||
/// let uri = Uri::parse("/a/b/c?query").expect("valid URI");
|
||||
/// let uri = Uri::parse::<Origin>("/a/b/c?query").expect("valid URI");
|
||||
/// let origin = uri.origin().expect("origin URI");
|
||||
/// assert_eq!(origin.path(), "/a/b/c");
|
||||
/// assert_eq!(origin.query().unwrap(), "query");
|
||||
///
|
||||
/// // Prefer to use the `uri!()` macro for static inputs. The return value
|
||||
/// // is of the specific type, not `Uri`.
|
||||
/// let origin = uri!("/a/b/c?query");
|
||||
/// assert_eq!(origin.path(), "/a/b/c");
|
||||
/// assert_eq!(origin.query().unwrap(), "query");
|
||||
///
|
||||
/// // Invalid URIs fail to parse.
|
||||
/// Uri::parse("foo bar").expect_err("invalid URI");
|
||||
/// Uri::parse::<Origin>("foo bar").expect_err("invalid URI");
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Uri<'a>, Error<'_>> {
|
||||
pub fn parse<T>(string: &'a str) -> Result<Uri<'a>, Error<'_>>
|
||||
where T: Into<Uri<'a>> + TryFrom<&'a str, Error = Error<'a>>
|
||||
{
|
||||
T::try_from(string).map(|v| v.into())
|
||||
}
|
||||
|
||||
/// Parse `string` into a the "best fit" URI type.
|
||||
///
|
||||
/// Always prefer to use `uri!()` for statically known inputs.
|
||||
///
|
||||
/// Because URI parsing is ambgious (that is, there isn't a one-to-one
|
||||
/// mapping between strings and a URI type), the internal type returned by
|
||||
/// this method _may_ not be the desired type. This method chooses the "best
|
||||
/// fit" type for a given string by preferring to parse in the following
|
||||
/// order:
|
||||
///
|
||||
/// * `Asterisk`
|
||||
/// * `Origin`
|
||||
/// * `Authority`
|
||||
/// * `Absolute`
|
||||
/// * `Reference`
|
||||
///
|
||||
/// Thus, even though `*` is a valid `Asterisk` _and_ `Reference` URI, it
|
||||
/// will parse as an `Asterisk`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Origin, Reference};
|
||||
///
|
||||
/// // An absolute path is an origin _unless_ it contains a fragment.
|
||||
/// let uri = Uri::parse_any("/a/b/c?query").expect("valid URI");
|
||||
/// let origin = uri.origin().expect("origin URI");
|
||||
/// assert_eq!(origin.path(), "/a/b/c");
|
||||
/// assert_eq!(origin.query().unwrap(), "query");
|
||||
///
|
||||
/// let uri = Uri::parse_any("/a/b/c?query#fragment").expect("valid URI");
|
||||
/// let reference = uri.reference().expect("reference URI");
|
||||
/// assert_eq!(reference.path(), "/a/b/c");
|
||||
/// assert_eq!(reference.query().unwrap(), "query");
|
||||
/// assert_eq!(reference.fragment().unwrap(), "fragment");
|
||||
///
|
||||
/// // Prefer to use the `uri!()` macro for static inputs. The return type
|
||||
/// // is the internal type, not `Uri`. The explicit type is not required.
|
||||
/// let uri: Origin = uri!("/a/b/c?query");
|
||||
/// let uri: Reference = uri!("/a/b/c?query#fragment");
|
||||
/// ```
|
||||
pub fn parse_any(string: &'a str) -> Result<Uri<'a>, Error<'_>> {
|
||||
crate::parse::uri::from_str(string)
|
||||
}
|
||||
|
||||
|
@ -112,13 +138,19 @@ impl<'a> Uri<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Absolute, Origin};
|
||||
///
|
||||
/// let uri = Uri::parse("/a/b/c?query").expect("valid URI");
|
||||
/// let uri = Uri::parse::<Origin>("/a/b/c?query").expect("valid URI");
|
||||
/// assert!(uri.origin().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||
/// let uri = Uri::from(uri!("/a/b/c?query"));
|
||||
/// assert!(uri.origin().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse::<Absolute>("https://rocket.rs").expect("valid URI");
|
||||
/// assert!(uri.origin().is_none());
|
||||
///
|
||||
/// let uri = Uri::from(uri!("https://rocket.rs"));
|
||||
/// assert!(uri.origin().is_none());
|
||||
/// ```
|
||||
pub fn origin(&self) -> Option<&Origin<'a>> {
|
||||
|
@ -134,13 +166,19 @@ impl<'a> Uri<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Absolute, Authority};
|
||||
///
|
||||
/// let uri = Uri::parse("user:pass@domain.com").expect("valid URI");
|
||||
/// let uri = Uri::parse::<Authority>("user:pass@domain.com").expect("valid URI");
|
||||
/// assert!(uri.authority().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||
/// let uri = Uri::from(uri!("user:pass@domain.com"));
|
||||
/// assert!(uri.authority().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse::<Absolute>("https://rocket.rs").expect("valid URI");
|
||||
/// assert!(uri.authority().is_none());
|
||||
///
|
||||
/// let uri = Uri::from(uri!("https://rocket.rs"));
|
||||
/// assert!(uri.authority().is_none());
|
||||
/// ```
|
||||
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||
|
@ -156,13 +194,19 @@ impl<'a> Uri<'a> {
|
|||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Absolute, Origin};
|
||||
///
|
||||
/// let uri = Uri::parse("http://google.com").expect("valid URI");
|
||||
/// let uri = Uri::parse::<Absolute>("http://rocket.rs").expect("valid URI");
|
||||
/// assert!(uri.absolute().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse("/path").expect("valid URI");
|
||||
/// let uri = Uri::from(uri!("http://rocket.rs"));
|
||||
/// assert!(uri.absolute().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse::<Origin>("/path").expect("valid URI");
|
||||
/// assert!(uri.absolute().is_none());
|
||||
///
|
||||
/// let uri = Uri::from(uri!("/path"));
|
||||
/// assert!(uri.absolute().is_none());
|
||||
/// ```
|
||||
pub fn absolute(&self) -> Option<&Absolute<'a>> {
|
||||
|
@ -172,61 +216,32 @@ impl<'a> Uri<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a URL-encoded version of the string. Any reserved characters are
|
||||
/// percent-encoded.
|
||||
/// Returns the internal instance of `Reference` if `self` is a
|
||||
/// `Uri::Reference`. Otherwise, returns `None`.
|
||||
///
|
||||
/// # Examples
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::{Uri, Absolute, Reference};
|
||||
///
|
||||
/// let encoded = Uri::percent_encode("hello?a=<b>hi</b>");
|
||||
/// assert_eq!(encoded, "hello%3Fa%3D%3Cb%3Ehi%3C%2Fb%3E");
|
||||
/// let uri = Uri::parse::<Reference>("foo/bar").expect("valid URI");
|
||||
/// assert!(uri.reference().is_some());
|
||||
///
|
||||
/// let uri = Uri::from(uri!("foo/bar"));
|
||||
/// assert!(uri.reference().is_some());
|
||||
///
|
||||
/// let uri = Uri::parse::<Absolute>("https://rocket.rs").expect("valid URI");
|
||||
/// assert!(uri.reference().is_none());
|
||||
///
|
||||
/// let uri = Uri::from(uri!("https://rocket.rs"));
|
||||
/// assert!(uri.reference().is_none());
|
||||
/// ```
|
||||
pub fn percent_encode<S>(string: &S) -> Cow<'_, str>
|
||||
where S: AsRef<str> + ?Sized
|
||||
{
|
||||
percent_encode::<DEFAULT_ENCODE_SET>(RawStr::new(string))
|
||||
pub fn reference(&self) -> Option<&Reference<'a>> {
|
||||
match self {
|
||||
Uri::Reference(ref inner) => Some(inner),
|
||||
_ => None
|
||||
}
|
||||
|
||||
/// Returns a URL-decoded version of the string. If the percent encoded
|
||||
/// values are not valid UTF-8, an `Err` is returned.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
///
|
||||
/// let decoded = Uri::percent_decode("/Hello%2C%20world%21".as_bytes());
|
||||
/// assert_eq!(decoded.unwrap(), "/Hello, world!");
|
||||
/// ```
|
||||
pub fn percent_decode<S>(bytes: &S) -> Result<Cow<'_, str>, Utf8Error>
|
||||
where S: AsRef<[u8]> + ?Sized
|
||||
{
|
||||
let decoder = percent_encoding::percent_decode(bytes.as_ref());
|
||||
decoder.decode_utf8()
|
||||
}
|
||||
|
||||
/// Returns a URL-decoded version of the path. Any invalid UTF-8
|
||||
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
|
||||
/// replacement character.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Uri;
|
||||
///
|
||||
/// let decoded = Uri::percent_decode_lossy("/Hello%2C%20world%21".as_bytes());
|
||||
/// assert_eq!(decoded, "/Hello, world!");
|
||||
/// ```
|
||||
pub fn percent_decode_lossy<S>(bytes: &S) -> Cow<'_, str>
|
||||
where S: AsRef<[u8]> + ?Sized
|
||||
{
|
||||
let decoder = percent_encoding::percent_decode(bytes.as_ref());
|
||||
decoder.decode_utf8_lossy()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -237,26 +252,26 @@ pub(crate) unsafe fn as_utf8_unchecked(input: Cow<'_, [u8]>) -> Cow<'_, str> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a str> for Uri<'a> {
|
||||
type Error = Error<'a>;
|
||||
|
||||
#[inline]
|
||||
fn try_from(string: &'a str) -> Result<Uri<'a>, Self::Error> {
|
||||
Uri::parse(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for Uri<'static> {
|
||||
type Error = Error<'static>;
|
||||
|
||||
#[inline]
|
||||
fn try_from(string: String) -> Result<Uri<'static>, Self::Error> {
|
||||
// TODO: Potentially optimize this like `Origin::parse_owned`.
|
||||
Uri::parse(&string)
|
||||
.map(|u| u.into_owned())
|
||||
.map_err(|e| e.into_owned())
|
||||
}
|
||||
}
|
||||
// impl<'a> TryFrom<&'a str> for Uri<'a> {
|
||||
// type Error = Error<'a>;
|
||||
//
|
||||
// #[inline]
|
||||
// fn try_from(string: &'a str) -> Result<Uri<'a>, Self::Error> {
|
||||
// Uri::parse(string)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl TryFrom<String> for Uri<'static> {
|
||||
// type Error = Error<'static>;
|
||||
//
|
||||
// #[inline]
|
||||
// fn try_from(string: String) -> Result<Uri<'static>, Self::Error> {
|
||||
// // TODO: Potentially optimize this like `Origin::parse_owned`.
|
||||
// Uri::parse(&string)
|
||||
// .map(|u| u.into_owned())
|
||||
// .map_err(|e| e.into_owned())
|
||||
// }
|
||||
// }
|
||||
|
||||
impl IntoOwned for Uri<'_> {
|
||||
type Owned = Uri<'static>;
|
||||
|
@ -266,7 +281,8 @@ impl IntoOwned for Uri<'_> {
|
|||
Uri::Origin(origin) => Uri::Origin(origin.into_owned()),
|
||||
Uri::Authority(authority) => Uri::Authority(authority.into_owned()),
|
||||
Uri::Absolute(absolute) => Uri::Absolute(absolute.into_owned()),
|
||||
Uri::Asterisk => Uri::Asterisk
|
||||
Uri::Reference(reference) => Uri::Reference(reference.into_owned()),
|
||||
Uri::Asterisk(asterisk) => Uri::Asterisk(asterisk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -277,42 +293,53 @@ impl Display for Uri<'_> {
|
|||
Uri::Origin(ref origin) => write!(f, "{}", origin),
|
||||
Uri::Authority(ref authority) => write!(f, "{}", authority),
|
||||
Uri::Absolute(ref absolute) => write!(f, "{}", absolute),
|
||||
Uri::Asterisk => write!(f, "*")
|
||||
Uri::Reference(ref reference) => write!(f, "{}", reference),
|
||||
Uri::Asterisk(ref asterisk) => write!(f, "{}", asterisk)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The error type returned when a URI conversion fails.
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct TryFromUriError(());
|
||||
|
||||
impl fmt::Display for TryFromUriError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
"invalid conversion from general to specific URI variant".fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_uri_from {
|
||||
($type:ident) => (
|
||||
impl<'a> From<$type<'a>> for Uri<'a> {
|
||||
fn from(other: $type<'a>) -> Uri<'a> {
|
||||
Uri::$type(other)
|
||||
($T:ident $(<$lt:lifetime>)?) => (
|
||||
impl<'a> From<$T $(<$lt>)?> for Uri<'a> {
|
||||
fn from(other: $T $(<$lt>)?) -> Uri<'a> {
|
||||
Uri::$T(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<Uri<'a>> for $type<'a> {
|
||||
impl<'a> TryFrom<Uri<'a>> for $T $(<$lt>)? {
|
||||
type Error = TryFromUriError;
|
||||
|
||||
fn try_from(uri: Uri<'a>) -> Result<Self, Self::Error> {
|
||||
match uri {
|
||||
Uri::$type(inner) => Ok(inner),
|
||||
Uri::$T(inner) => Ok(inner),
|
||||
_ => Err(TryFromUriError(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, $($lt)?> PartialEq<$T $(<$lt>)?> for Uri<'b> {
|
||||
fn eq(&self, other: &$T $(<$lt>)?) -> bool {
|
||||
match self {
|
||||
Uri::$T(inner) => inner == other,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b, $($lt)?> PartialEq<Uri<'b>> for $T $(<$lt>)? {
|
||||
fn eq(&self, other: &Uri<'b>) -> bool {
|
||||
match other {
|
||||
Uri::$T(inner) => inner == self,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
impl_uri_from!(Origin);
|
||||
impl_uri_from!(Authority);
|
||||
impl_uri_from!(Absolute);
|
||||
impl_uri_from!(Origin<'a>);
|
||||
impl_uri_from!(Authority<'a>);
|
||||
impl_uri_from!(Absolute<'a>);
|
||||
impl_uri_from!(Reference<'a>);
|
||||
impl_uri_from!(Asterisk);
|
||||
|
|
|
@ -164,7 +164,7 @@ impl Catcher {
|
|||
|
||||
Catcher {
|
||||
name: None,
|
||||
base: uri::Origin::new("/", None::<&str>),
|
||||
base: uri::Origin::ROOT,
|
||||
handler: Box::new(handler),
|
||||
code,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::form::prelude::*;
|
||||
use crate::http::uri::{Query, FromUriParam};
|
||||
use crate::http::uri::fmt::{Query, FromUriParam};
|
||||
|
||||
/// A form guard for parsing form types leniently.
|
||||
///
|
||||
|
|
|
@ -58,7 +58,7 @@ impl FileName {
|
|||
/// '(', ')', '&', ';', '#', '?', '*'`.
|
||||
///
|
||||
/// On Windows (and non-Unix OSs), these are the characters `'.', '<', '>',
|
||||
/// ':', '"', '/', '\\', '|', '?', '*', ',', ';', '=', '(', ')', '&', '#'`,
|
||||
/// ':', '"', '/', '\', '|', '?', '*', ',', ';', '=', '(', ')', '&', '#'`,
|
||||
/// and the reserved names `"CON", "PRN", "AUX", "NUL", "COM1", "COM2",
|
||||
/// "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",
|
||||
/// "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"`.
|
||||
|
@ -164,6 +164,33 @@ impl FileName {
|
|||
Some(file_name)
|
||||
}
|
||||
|
||||
/// Returns `true` if the _complete_ raw file name is safe.
|
||||
///
|
||||
/// Note that `.as_str()` returns a safe _subset_ of the raw file name, if
|
||||
/// there is one. If this method returns `true`, then that subset is the
|
||||
/// complete raw file name.
|
||||
///
|
||||
/// This method should be use sparingly. In particular, there is no
|
||||
/// advantage to calling `is_safe()` prior to calling `as_str()`; simply
|
||||
/// call `as_str()`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::form::name::FileName;
|
||||
///
|
||||
/// let name = FileName::new("some-file.txt");
|
||||
/// assert_eq!(name.as_str(), Some("some-file"));
|
||||
/// assert!(!name.is_safe());
|
||||
///
|
||||
/// let name = FileName::new("some-file");
|
||||
/// assert_eq!(name.as_str(), Some("some-file"));
|
||||
/// assert!(name.is_safe());
|
||||
/// ```
|
||||
pub fn is_safe(&self) -> bool {
|
||||
self.as_str().map_or(false, |s| s == &self.0)
|
||||
}
|
||||
|
||||
/// The raw, unsanitized, potentially unsafe file name. Prefer to use
|
||||
/// [`FileName::as_str()`], always.
|
||||
///
|
||||
|
@ -171,8 +198,8 @@ impl FileName {
|
|||
///
|
||||
/// This method returns the file name exactly as it was specified by the
|
||||
/// client. You should **_not_** use this name _unless_ you require the
|
||||
/// originally specified `filename` _and_ it is known to contain special,
|
||||
/// potentially dangerous characters, _and_:
|
||||
/// originally specified `filename` _and_ it is known not to contain
|
||||
/// special, potentially dangerous characters, _and_:
|
||||
///
|
||||
/// 1. All clients are known to be trusted, perhaps because the server
|
||||
/// only runs locally, serving known, local requests, or...
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::form::prelude::*;
|
||||
use crate::http::uri::{Query, FromUriParam};
|
||||
use crate::http::uri::fmt::{Query, FromUriParam};
|
||||
|
||||
/// A form guard for parsing form types strictly.
|
||||
///
|
||||
|
|
|
@ -45,8 +45,7 @@ impl<'c> LocalRequest<'c> {
|
|||
{
|
||||
// Try to parse `uri` into an `Origin`, storing whether it's good.
|
||||
let uri_str = uri.to_string();
|
||||
let try_origin = uri.try_into()
|
||||
.map_err(|_| Origin::new::<_, &'static str>(uri_str, None));
|
||||
let try_origin = uri.try_into().map_err(|_| Origin::path_only(uri_str));
|
||||
|
||||
// Create a request. We'll handle bad URIs later, in `_dispatch`.
|
||||
let origin = try_origin.clone().unwrap_or_else(|bad| bad);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::str::FromStr;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::http::uri::{Segments, PathError};
|
||||
use crate::http::uri::{Segments, error::PathError, fmt::Path};
|
||||
|
||||
/// Trait to convert a dynamic path segment string to a concrete value.
|
||||
///
|
||||
|
@ -228,6 +228,18 @@ impl_with_fromstr! {
|
|||
bool, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, SocketAddrV6, SocketAddr
|
||||
}
|
||||
|
||||
impl<'a> FromParam<'a> for PathBuf {
|
||||
type Error = PathError;
|
||||
|
||||
#[inline]
|
||||
fn from_param(param: &'a str) -> Result<Self, Self::Error> {
|
||||
use crate::http::private::Indexed;
|
||||
|
||||
let segments = &[Indexed::Indexed(0, param.len())];
|
||||
Segments::new(param.into(), segments).to_path_buf(false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: FromParam<'a>> FromParam<'a> for Result<T, T::Error> {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
|
@ -276,14 +288,14 @@ pub trait FromSegments<'r>: Sized {
|
|||
|
||||
/// Parses an instance of `Self` from many dynamic path parameter strings or
|
||||
/// returns an `Error` if one cannot be parsed.
|
||||
fn from_segments(segments: Segments<'r>) -> Result<Self, Self::Error>;
|
||||
fn from_segments(segments: Segments<'r, Path>) -> Result<Self, Self::Error>;
|
||||
}
|
||||
|
||||
impl<'r> FromSegments<'r> for Segments<'r> {
|
||||
impl<'r> FromSegments<'r> for Segments<'r, Path> {
|
||||
type Error = std::convert::Infallible;
|
||||
|
||||
#[inline(always)]
|
||||
fn from_segments(segments: Segments<'r>) -> Result<Segments<'r>, Self::Error> {
|
||||
fn from_segments(segments: Self) -> Result<Self, Self::Error> {
|
||||
Ok(segments)
|
||||
}
|
||||
}
|
||||
|
@ -307,7 +319,7 @@ impl<'r> FromSegments<'r> for Segments<'r> {
|
|||
impl FromSegments<'_> for PathBuf {
|
||||
type Error = PathError;
|
||||
|
||||
fn from_segments(segments: Segments<'_>) -> Result<Self, Self::Error> {
|
||||
fn from_segments(segments: Segments<'_, Path>) -> Result<Self, Self::Error> {
|
||||
segments.to_path_buf(false)
|
||||
}
|
||||
}
|
||||
|
@ -316,7 +328,7 @@ impl<'r, T: FromSegments<'r>> FromSegments<'r> for Result<T, T::Error> {
|
|||
type Error = std::convert::Infallible;
|
||||
|
||||
#[inline]
|
||||
fn from_segments(segments: Segments<'r>) -> Result<Result<T, T::Error>, Self::Error> {
|
||||
fn from_segments(segments: Segments<'r, Path>) -> Result<Result<T, T::Error>, Self::Error> {
|
||||
match T::from_segments(segments) {
|
||||
Ok(val) => Ok(Ok(val)),
|
||||
Err(e) => Ok(Err(e)),
|
||||
|
@ -328,7 +340,7 @@ impl<'r, T: FromSegments<'r>> FromSegments<'r> for Option<T> {
|
|||
type Error = std::convert::Infallible;
|
||||
|
||||
#[inline]
|
||||
fn from_segments(segments: Segments<'r>) -> Result<Option<T>, Self::Error> {
|
||||
fn from_segments(segments: Segments<'r, Path>) -> Result<Option<T>, Self::Error> {
|
||||
match T::from_segments(segments) {
|
||||
Ok(val) => Ok(Some(val)),
|
||||
Err(_) => Ok(None)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
use std::{ops::RangeFrom, sync::Arc};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
use std::future::Future;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
use std::ops::RangeFrom;
|
||||
use std::{future::Future, borrow::Cow, sync::Arc};
|
||||
use std::net::{IpAddr, SocketAddr};
|
||||
|
||||
use yansi::Paint;
|
||||
use state::{Container, Storage};
|
||||
|
@ -14,7 +13,7 @@ use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
|||
use crate::form::{self, ValueField, FromForm};
|
||||
|
||||
use crate::{Rocket, Route, Orbit};
|
||||
use crate::http::{hyper, uri::{Origin, Segments}, uncased::UncasedStr};
|
||||
use crate::http::{hyper, uri::{Origin, Segments, fmt::Path}, uncased::UncasedStr};
|
||||
use crate::http::{Method, Header, HeaderMap};
|
||||
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||
use crate::data::Limits;
|
||||
|
@ -794,18 +793,21 @@ impl<'r> Request<'r> {
|
|||
/// Get the segments beginning at the `n`th, 0-indexed, after the mount
|
||||
/// point for the currently matched route, if they exist. Used by codegen.
|
||||
#[inline]
|
||||
pub fn routed_segments(&self, n: RangeFrom<usize>) -> Segments<'_> {
|
||||
pub fn routed_segments(&self, n: RangeFrom<usize>) -> Segments<'_, Path> {
|
||||
let mount_segments = self.route()
|
||||
.map(|r| r.uri.metadata.base_segs.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
self.uri().path_segments().skip(mount_segments + n.start)
|
||||
self.uri().path().segments().skip(mount_segments + n.start)
|
||||
}
|
||||
|
||||
// Retrieves the pre-parsed query items. Used by matching and codegen.
|
||||
#[inline]
|
||||
pub fn query_fields(&self) -> impl Iterator<Item = ValueField<'_>> {
|
||||
self.uri().query_segments().map(ValueField::from)
|
||||
self.uri().query()
|
||||
.map(|q| q.segments().map(ValueField::from))
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Set `self`'s parameters given that the route used to reach this request
|
||||
|
@ -836,7 +838,7 @@ impl<'r> Request<'r> {
|
|||
) -> Result<Request<'r>, Error<'r>> {
|
||||
// Get a copy of the URI (only supports path-and-query) for later use.
|
||||
let uri = match (h_uri.scheme(), h_uri.authority(), h_uri.path_and_query()) {
|
||||
(None, None, Some(path_query)) => path_query.as_str(),
|
||||
(None, None, Some(path_query)) => path_query,
|
||||
_ => return Err(Error::InvalidUri(h_uri)),
|
||||
};
|
||||
|
||||
|
@ -846,8 +848,10 @@ impl<'r> Request<'r> {
|
|||
None => return Err(Error::BadMethod(h_method))
|
||||
};
|
||||
|
||||
// We need to re-parse the URI since we don't trust Hyper... :(
|
||||
let uri = Origin::parse(uri)?;
|
||||
// In debug, make sure we agree with Hyper. Otherwise, cross our fingers
|
||||
// and trust that it only gives us valid URIs like it's supposed to.
|
||||
debug_assert!(Origin::parse(uri.as_str()).is_ok());
|
||||
let uri = Origin::new(uri.path(), uri.query().map(Cow::Borrowed));
|
||||
|
||||
// Construct the request object.
|
||||
let mut request = Request::new(rocket, method, uri);
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryInto;
|
|||
|
||||
use crate::request::Request;
|
||||
use crate::response::{self, Response, Responder};
|
||||
use crate::http::uri::Uri;
|
||||
use crate::http::uri::Reference;
|
||||
use crate::http::Status;
|
||||
|
||||
/// An empty redirect response to a given URL.
|
||||
|
@ -11,19 +11,19 @@ use crate::http::Status;
|
|||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// All constructors accept a generic type of `T: TryInto<Uri<'static>>`. Among
|
||||
/// the candidate types are:
|
||||
/// All constructors accept a generic type of `T: TryInto<Reference<'static>>`.
|
||||
/// Among the candidate types are:
|
||||
///
|
||||
/// * `String`
|
||||
/// * `&'static str`
|
||||
/// * `String`, `&'static str`
|
||||
/// * [`Origin`](crate::http::uri::Origin)
|
||||
/// * [`Authority`](crate::http::uri::Authority)
|
||||
/// * [`Absolute`](crate::http::uri::Absolute)
|
||||
/// * [`Uri`](crate::http::uri::Uri)
|
||||
/// * [`Reference`](crate::http::uri::Reference)
|
||||
///
|
||||
/// Any non-`'static` strings must first be allocated using `.to_string()` or
|
||||
/// similar before being passed to a `Redirect` constructor. When redirecting to
|
||||
/// a route, _always_ use [`uri!`] to construct a valid [`Origin`]:
|
||||
/// a route, or any URI containing a route, _always_ use [`uri!`] to construct a
|
||||
/// valid URI:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
|
@ -36,14 +36,19 @@ use crate::http::Status;
|
|||
///
|
||||
/// #[get("/hi/<name>/<age>")]
|
||||
/// fn hi(name: String, age: u8) -> Redirect {
|
||||
/// Redirect::to(uri!(hello: name, age))
|
||||
/// Redirect::to(uri!(hello(name, age)))
|
||||
/// }
|
||||
///
|
||||
/// #[get("/bye/<name>/<age>")]
|
||||
/// fn bye(name: String, age: u8) -> Redirect {
|
||||
/// Redirect::to(uri!("https://rocket.rs/bye", hello(name, age), "?bye#now"))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// [`Origin`]: crate::http::uri::Origin
|
||||
/// [`uri!`]: ../macro.uri.html
|
||||
#[derive(Debug)]
|
||||
pub struct Redirect(Status, Option<Uri<'static>>);
|
||||
pub struct Redirect(Status, Option<Reference<'static>>);
|
||||
|
||||
impl Redirect {
|
||||
/// Construct a temporary "see other" (303) redirect response. This is the
|
||||
|
@ -54,13 +59,13 @@ impl Redirect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// # let query = "foo";
|
||||
/// let redirect = Redirect::to("/other_url");
|
||||
/// let redirect = Redirect::to(format!("https://google.com/{}", query));
|
||||
/// let redirect = Redirect::to(uri!("/foo/bar"));
|
||||
/// let redirect = Redirect::to(uri!("https://domain.com#foo"));
|
||||
/// ```
|
||||
pub fn to<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||
pub fn to<U: TryInto<Reference<'static>>>(uri: U) -> Redirect {
|
||||
Redirect(Status::SeeOther, uri.try_into().ok())
|
||||
}
|
||||
|
||||
|
@ -73,13 +78,14 @@ impl Redirect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// # let query = "foo";
|
||||
/// let redirect = Redirect::temporary("/other_url");
|
||||
/// let redirect = Redirect::temporary(format!("https://google.com/{}", query));
|
||||
/// let redirect = Redirect::temporary(uri!("some/other/path"));
|
||||
/// let redirect = Redirect::temporary(uri!("https://rocket.rs?foo"));
|
||||
/// let redirect = Redirect::temporary(format!("some-{}-thing", "crazy"));
|
||||
/// ```
|
||||
pub fn temporary<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||
pub fn temporary<U: TryInto<Reference<'static>>>(uri: U) -> Redirect {
|
||||
Redirect(Status::TemporaryRedirect, uri.try_into().ok())
|
||||
}
|
||||
|
||||
|
@ -93,13 +99,13 @@ impl Redirect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// # let query = "foo";
|
||||
/// let redirect = Redirect::permanent("/other_url");
|
||||
/// let redirect = Redirect::permanent(format!("https://google.com/{}", query));
|
||||
/// let redirect = Redirect::permanent(uri!("/other_url"));
|
||||
/// let redirect = Redirect::permanent(format!("some-{}-thing", "crazy"));
|
||||
/// ```
|
||||
pub fn permanent<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||
pub fn permanent<U: TryInto<Reference<'static>>>(uri: U) -> Redirect {
|
||||
Redirect(Status::PermanentRedirect, uri.try_into().ok())
|
||||
}
|
||||
|
||||
|
@ -113,13 +119,13 @@ impl Redirect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// # let query = "foo";
|
||||
/// let redirect = Redirect::found("/other_url");
|
||||
/// let redirect = Redirect::found(format!("https://google.com/{}", query));
|
||||
/// let redirect = Redirect::found(uri!("/other_url"));
|
||||
/// let redirect = Redirect::found(format!("some-{}-thing", "crazy"));
|
||||
/// ```
|
||||
pub fn found<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||
pub fn found<U: TryInto<Reference<'static>>>(uri: U) -> Redirect {
|
||||
Redirect(Status::Found, uri.try_into().ok())
|
||||
}
|
||||
|
||||
|
@ -131,13 +137,13 @@ impl Redirect {
|
|||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::response::Redirect;
|
||||
///
|
||||
/// # let query = "foo";
|
||||
/// let redirect = Redirect::moved("/other_url");
|
||||
/// let redirect = Redirect::moved(format!("https://google.com/{}", query));
|
||||
/// let redirect = Redirect::moved(uri!("here"));
|
||||
/// let redirect = Redirect::moved(format!("some-{}-thing", "crazy"));
|
||||
/// ```
|
||||
pub fn moved<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
||||
pub fn moved<U: TryInto<Reference<'static>>>(uri: U) -> Redirect {
|
||||
Redirect(Status::MovedPermanently, uri.try_into().ok())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -529,9 +529,9 @@ fn log_items<T, I, B, O>(e: &str, t: &str, items: I, base: B, origin: O)
|
|||
}
|
||||
|
||||
items.sort_by_key(|i| origin(i).path().as_str().chars().count());
|
||||
items.sort_by_key(|i| origin(i).path_segments().len());
|
||||
items.sort_by_key(|i| origin(i).path().segments().len());
|
||||
items.sort_by_key(|i| base(i).path().as_str().chars().count());
|
||||
items.sort_by_key(|i| base(i).path_segments().len());
|
||||
items.sort_by_key(|i| base(i).path().segments().len());
|
||||
items.iter().for_each(|i| launch_info_!("{}", i));
|
||||
}
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ impl<'a> RouteUri<'a> {
|
|||
self.origin.path().as_str()
|
||||
}
|
||||
|
||||
/// The query part of this route URI as an `Option<&str>`.
|
||||
/// The query part of this route URI, if there is one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
|
@ -180,10 +180,18 @@ impl<'a> RouteUri<'a> {
|
|||
/// use rocket::http::Method;
|
||||
/// # use rocket::route::dummy_handler as handler;
|
||||
///
|
||||
/// let index = Route::new(Method::Get, "/foo/bar", handler);
|
||||
/// assert!(index.uri.query().is_none());
|
||||
///
|
||||
/// // Normalization clears the empty '?'.
|
||||
/// let index = Route::new(Method::Get, "/foo/bar?", handler);
|
||||
/// assert!(index.uri.query().is_none());
|
||||
///
|
||||
/// let index = Route::new(Method::Get, "/foo/bar?a=1", handler);
|
||||
/// assert_eq!(index.uri.query(), Some("a=1"));
|
||||
/// assert_eq!(index.uri.query().unwrap(), "a=1");
|
||||
///
|
||||
/// let index = index.map_base(|base| format!("{}{}", "/boo", base)).unwrap();
|
||||
/// assert_eq!(index.uri.query(), Some("a=1"));
|
||||
/// assert_eq!(index.uri.query().unwrap(), "a=1");
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn query(&self) -> Option<&str> {
|
||||
|
@ -241,17 +249,17 @@ impl<'a> RouteUri<'a> {
|
|||
|
||||
impl Metadata {
|
||||
fn from(base: &Origin<'_>, origin: &Origin<'_>) -> Self {
|
||||
let base_segs = base.raw_path_segments()
|
||||
let base_segs = base.path().raw_segments()
|
||||
.map(Segment::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let path_segs = origin.raw_path_segments()
|
||||
let path_segs = origin.path().raw_segments()
|
||||
.map(Segment::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let query_segs = origin.raw_query_segments()
|
||||
.map(Segment::from)
|
||||
.collect::<Vec<_>>();
|
||||
let query_segs = origin.query()
|
||||
.map(|q| q.raw_segments().map(Segment::from).collect::<Vec<_>>())
|
||||
.unwrap_or_default();
|
||||
|
||||
let static_query_fields = query_segs.iter().filter(|s| !s.dynamic)
|
||||
.map(|s| ValueField::parse(&s.value))
|
||||
|
|
|
@ -106,7 +106,7 @@ impl Route {
|
|||
|
||||
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
|
||||
let route_segments = &route.uri.metadata.path_segs;
|
||||
let req_segments = req.uri().path_segments();
|
||||
let req_segments = req.uri().path().segments();
|
||||
|
||||
if route.uri.metadata.trailing_path {
|
||||
// The last route segment can be trailing, which is allowed to be empty.
|
||||
|
@ -145,10 +145,15 @@ fn queries_match(route: &Route, req: &Request<'_>) -> bool {
|
|||
.map(|(k, v)| (k.as_str(), v.as_str()));
|
||||
|
||||
for route_seg in route_query_fields {
|
||||
if !req.uri().query_segments().any(|req_seg| req_seg == route_seg) {
|
||||
if let Some(query) = req.uri().query() {
|
||||
if !query.segments().any(|req_seg| req_seg == route_seg) {
|
||||
trace_!("request {} missing static query {:?}", req, route_seg);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
trace_!("query-less request {} missing static query {:?}", req, route_seg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
|
@ -181,7 +186,7 @@ impl Collide for Catcher {
|
|||
/// * Have the same status code or are both defaults.
|
||||
fn collides_with(&self, other: &Self) -> bool {
|
||||
self.code == other.code
|
||||
&& self.base.path_segments().eq(other.base.path_segments())
|
||||
&& self.base.path().segments().eq(other.base.path().segments())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -193,7 +198,7 @@ impl Catcher {
|
|||
/// * Its base is a prefix of the normalized/decoded `req.path()`.
|
||||
pub(crate) fn matches(&self, status: Status, req: &Request<'_>) -> bool {
|
||||
self.code.map_or(true, |code| code == status.code)
|
||||
&& self.base.path_segments().prefix_of(req.uri().path_segments())
|
||||
&& self.base.path().segments().prefix_of(req.uri().path().segments())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Router {
|
|||
pub fn add_catcher(&mut self, catcher: Catcher) {
|
||||
let catchers = self.catchers.entry(catcher.code).or_default();
|
||||
catchers.push(catcher);
|
||||
catchers.sort_by(|a, b| b.base.path_segments().len().cmp(&a.base.path_segments().len()))
|
||||
catchers.sort_by(|a, b| b.base.path().segments().len().cmp(&a.base.path().segments().len()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -68,7 +68,7 @@ impl Router {
|
|||
(None, None) => None,
|
||||
(None, c@Some(_)) | (c@Some(_), None) => c,
|
||||
(Some(a), Some(b)) => {
|
||||
if b.base.path_segments().len() > a.base.path_segments().len() {
|
||||
if b.base.path().segments().len() > a.base.path().segments().len() {
|
||||
Some(b)
|
||||
} else {
|
||||
Some(a)
|
||||
|
|
|
@ -87,7 +87,7 @@ async fn hyper_service_fn(
|
|||
// fabricate one. This is weird. We should let the user know
|
||||
// that we failed to parse a request (by invoking some special
|
||||
// handler) instead of doing this.
|
||||
let dummy = Request::new(&rocket, Method::Get, Origin::dummy());
|
||||
let dummy = Request::new(&rocket, Method::Get, Origin::ROOT);
|
||||
let r = rocket.handle_error(Status::BadRequest, &dummy).await;
|
||||
return rocket.send_response(r, tx).await;
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
|
||||
use rocket::response::Redirect;
|
||||
|
||||
#[get("/google")]
|
||||
fn google() -> Redirect {
|
||||
Redirect::to("https://www.google.com")
|
||||
#[get("/http")]
|
||||
fn http() -> Redirect {
|
||||
Redirect::to(uri!("http://rocket.rs"))
|
||||
}
|
||||
|
||||
#[get("/rocket")]
|
||||
|
@ -18,13 +18,13 @@ mod test_absolute_uris_okay {
|
|||
|
||||
#[test]
|
||||
fn redirect_works() {
|
||||
let client = Client::debug_with(routes![google, redirect]).unwrap();
|
||||
let client = Client::debug_with(routes![http, redirect]).unwrap();
|
||||
|
||||
let response = client.get("/google").dispatch();
|
||||
let response = client.get(uri!(http)).dispatch();
|
||||
let location = response.headers().get_one("Location");
|
||||
assert_eq!(location, Some("https://www.google.com"));
|
||||
assert_eq!(location, Some("http://rocket.rs"));
|
||||
|
||||
let response = client.get("/rocket").dispatch();
|
||||
let response = client.get(uri!(redirect)).dispatch();
|
||||
let location = response.headers().get_one("Location");
|
||||
assert_eq!(location, Some("https://rocket.rs:80"));
|
||||
}
|
||||
|
|
|
@ -6,13 +6,13 @@ mod inner {
|
|||
|
||||
#[rocket::get("/")]
|
||||
pub fn hello() -> String {
|
||||
format!("Hello! Try {}.", uri!(super::hello_name: "Rust 2018"))
|
||||
format!("Hello! Try {}.", uri!(super::hello_name("Rust 2018")))
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::get("/<name>")]
|
||||
fn hello_name(name: String) -> String {
|
||||
format!("Hello, {}! This is {}.", name, rocket::uri!(hello_name: &name))
|
||||
format!("Hello, {}! This is {}.", name, rocket::uri!(hello_name(&name)))
|
||||
}
|
||||
|
||||
fn rocket() -> Rocket<Build> {
|
||||
|
|
|
@ -1,29 +1,29 @@
|
|||
#[macro_use] extern crate rocket;
|
||||
|
||||
use rocket::http::uri::Segments;
|
||||
use rocket::http::uri::{Segments, fmt::Path};
|
||||
|
||||
#[get("/test/<path..>")]
|
||||
fn test(path: Segments<'_>) -> String {
|
||||
fn test(path: Segments<'_, Path>) -> String {
|
||||
path.collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
#[get("/two/<path..>")]
|
||||
fn two(path: Segments<'_>) -> String {
|
||||
fn two(path: Segments<'_, Path>) -> String {
|
||||
path.collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
#[get("/one/two/<path..>")]
|
||||
fn one_two(path: Segments<'_>) -> String {
|
||||
fn one_two(path: Segments<'_, Path>) -> String {
|
||||
path.collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
#[get("/<path..>", rank = 2)]
|
||||
fn none(path: Segments<'_>) -> String {
|
||||
fn none(path: Segments<'_, Path>) -> String {
|
||||
path.collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
#[get("/static/<user>/is/<path..>")]
|
||||
fn dual(user: String, path: Segments<'_>) -> String {
|
||||
fn dual(user: String, path: Segments<'_, Path>) -> String {
|
||||
user + "/is/" + &path.collect::<Vec<_>>().join("/")
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use rocket::{Rocket, Build};
|
||||
use rocket::response::Redirect;
|
||||
use rocket::http::uri::Uri;
|
||||
|
||||
const NAME: &str = "John[]|\\%@^";
|
||||
|
||||
|
@ -13,12 +12,12 @@ fn hello(name: String) -> String {
|
|||
|
||||
#[get("/raw")]
|
||||
fn raw_redirect() -> Redirect {
|
||||
Redirect::to(format!("/hello/{}", Uri::percent_encode(NAME)))
|
||||
Redirect::to(uri!(hello(NAME)))
|
||||
}
|
||||
|
||||
#[get("/uri")]
|
||||
fn uri_redirect() -> Redirect {
|
||||
Redirect::to(uri!(hello: NAME))
|
||||
Redirect::to(uri!(hello(NAME)))
|
||||
}
|
||||
|
||||
fn rocket() -> Rocket<Build> {
|
||||
|
@ -28,7 +27,7 @@ fn rocket() -> Rocket<Build> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use rocket::local::blocking::Client;
|
||||
use rocket::http::{Status, uri::Uri};
|
||||
use rocket::http::Status;
|
||||
|
||||
#[test]
|
||||
fn uri_percent_encoding_redirect() {
|
||||
|
@ -49,8 +48,7 @@ mod tests {
|
|||
#[test]
|
||||
fn uri_percent_encoding_get() {
|
||||
let client = Client::debug(rocket()).unwrap();
|
||||
let name = Uri::percent_encode(NAME);
|
||||
let response = client.get(format!("/hello/{}", name)).dispatch();
|
||||
let response = client.get(uri!(hello(NAME))).dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
assert_eq!(response.into_string().unwrap(), format!("Hello, {}!", NAME));
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ mod paste_id;
|
|||
|
||||
use std::io;
|
||||
|
||||
use rocket::State;
|
||||
use rocket::data::{Data, ToByteUnit};
|
||||
use rocket::http::uri::Absolute;
|
||||
use rocket::response::content::Plain;
|
||||
|
@ -13,17 +12,15 @@ use rocket::tokio::fs::{self, File};
|
|||
|
||||
use crate::paste_id::PasteId;
|
||||
|
||||
const HOST: &str = "http://localhost:8000";
|
||||
const HOST: Absolute<'static> = uri!("http://localhost:8000");
|
||||
|
||||
const ID_LENGTH: usize = 3;
|
||||
|
||||
#[post("/", data = "<paste>")]
|
||||
async fn upload(paste: Data, host: &State<Absolute<'_>>) -> io::Result<String> {
|
||||
async fn upload(paste: Data) -> io::Result<String> {
|
||||
let id = PasteId::new(ID_LENGTH);
|
||||
paste.open(128.kibibytes()).into_file(id.file_path()).await?;
|
||||
|
||||
// TODO: Ok(uri!(HOST, retrieve: id))
|
||||
let host = host.inner().clone();
|
||||
Ok(host.with_origin(uri!(retrieve: id)).to_string())
|
||||
Ok(uri!(HOST, retrieve(id)).to_string())
|
||||
}
|
||||
|
||||
#[get("/<id>")]
|
||||
|
@ -57,6 +54,5 @@ fn index() -> &'static str {
|
|||
#[launch]
|
||||
fn rocket() -> _ {
|
||||
rocket::build()
|
||||
.manage(Absolute::parse(HOST).expect("valid host"))
|
||||
.mount("/", routes![index, upload, delete, retrieve])
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use rocket::http::uri::{self, FromUriParam};
|
||||
use rocket::http::uri::fmt;
|
||||
use rocket::request::FromParam;
|
||||
use rand::{self, Rng};
|
||||
|
||||
|
@ -46,7 +46,7 @@ impl<'a> FromParam<'a> for PasteId<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> FromUriParam<uri::Path, &'a str> for PasteId<'_> {
|
||||
impl<'a> fmt::FromUriParam<fmt::Path, &'a str> for PasteId<'_> {
|
||||
type Target = PasteId<'a>;
|
||||
|
||||
fn from_uri_param(param: &'a str) -> Self::Target {
|
||||
|
|
|
@ -25,7 +25,7 @@ fn upload_paste(client: &Client, body: &str) -> String {
|
|||
}
|
||||
|
||||
fn download_paste(client: &Client, id: &str) -> Option<String> {
|
||||
let response = client.get(uri!(super::retrieve: id)).dispatch();
|
||||
let response = client.get(uri!(super::retrieve(id))).dispatch();
|
||||
if response.status().class().is_success() {
|
||||
Some(response.into_string().unwrap())
|
||||
} else {
|
||||
|
@ -34,7 +34,7 @@ fn download_paste(client: &Client, id: &str) -> Option<String> {
|
|||
}
|
||||
|
||||
fn delete_paste(client: &Client, id: &str) {
|
||||
let response = client.delete(uri!(super::delete: id)).dispatch();
|
||||
let response = client.delete(uri!(super::delete(id))).dispatch();
|
||||
assert_eq!(response.status(), Status::Ok);
|
||||
}
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ async fn test_one_hi_per_second() {
|
|||
// Listen for 1 second at 1 `hi` per 250ms, see if we get ~4 `hi`'s, then
|
||||
// send a shutdown() signal, meaning we should get a `goodbye`.
|
||||
let client = Client::tracked(super::rocket()).await.unwrap();
|
||||
let response = client.get(uri!(super::one_hi_per_ms: 250)).dispatch().await;
|
||||
let response = client.get(uri!(super::one_hi_per_ms(250))).dispatch().await;
|
||||
let response = response.into_string();
|
||||
let timer = time::sleep(Duration::from_secs(1));
|
||||
|
||||
|
@ -100,11 +100,11 @@ fn test_login() {
|
|||
assert_eq!(r.into_string().unwrap(), "Hi! Please log in before continuing.");
|
||||
|
||||
for name in &["Bob", "Charley", "Joe Roger"] {
|
||||
let r = client.get(uri!(super::maybe_redir: name)).dispatch();
|
||||
let r = client.get(uri!(super::maybe_redir(name))).dispatch();
|
||||
assert_eq!(r.status(), Status::SeeOther);
|
||||
}
|
||||
|
||||
let r = client.get(uri!(super::maybe_redir: "Sergio")).dispatch();
|
||||
let r = client.get(uri!(super::maybe_redir("Sergio"))).dispatch();
|
||||
assert_eq!(r.status(), Status::Ok);
|
||||
assert_eq!(r.into_string().unwrap(), "Hello, Sergio!");
|
||||
}
|
||||
|
@ -139,11 +139,11 @@ fn test_xml() {
|
|||
#[test]
|
||||
fn test_either() {
|
||||
let client = Client::tracked(super::rocket()).unwrap();
|
||||
let r = client.get(uri!(super::json_or_msgpack: "json")).dispatch();
|
||||
let r = client.get(uri!(super::json_or_msgpack("json"))).dispatch();
|
||||
assert_eq!(r.content_type().unwrap(), ContentType::JSON);
|
||||
assert_eq!(r.into_string().unwrap(), "\"hi\"");
|
||||
|
||||
let r = client.get(uri!(super::json_or_msgpack: "msgpack")).dispatch();
|
||||
let r = client.get(uri!(super::json_or_msgpack("msgpack"))).dispatch();
|
||||
assert_eq!(r.content_type().unwrap(), ContentType::MsgPack);
|
||||
assert_eq!(r.into_bytes().unwrap(), &[162, 104, 105]);
|
||||
}
|
||||
|
@ -155,13 +155,13 @@ use super::Kind;
|
|||
#[test]
|
||||
fn test_custom() {
|
||||
let client = Client::tracked(super::rocket()).unwrap();
|
||||
let r = client.get(uri!(super::custom: Some(Kind::String))).dispatch();
|
||||
let r = client.get(uri!(super::custom(Some(Kind::String)))).dispatch();
|
||||
assert_eq!(r.into_string().unwrap(), "Hey, I'm some data.");
|
||||
|
||||
let r = client.get(uri!(super::custom: Some(Kind::Bytes))).dispatch();
|
||||
let r = client.get(uri!(super::custom(Some(Kind::Bytes)))).dispatch();
|
||||
assert_eq!(r.into_string().unwrap(), "Hi");
|
||||
|
||||
let r = client.get(uri!(super::custom: None as Option<Kind>)).dispatch();
|
||||
let r = client.get(uri!(super::custom(_))).dispatch();
|
||||
assert_eq!(r.status(), Status::Unauthorized);
|
||||
assert_eq!(r.content_type().unwrap(), ContentType::HTML);
|
||||
assert_eq!(r.into_string().unwrap(), "No no no!");
|
||||
|
@ -175,7 +175,7 @@ fn test_custom() {
|
|||
assert_eq!(response.status(), Status::Ok);
|
||||
|
||||
// Fetch it using `custom`.
|
||||
let r = client.get(uri!(super::custom: Some(Kind::File))).dispatch();
|
||||
let r = client.get(uri!(super::custom(Some(Kind::File)))).dispatch();
|
||||
assert_eq!(r.into_string(), Some(CONTENTS.into()));
|
||||
|
||||
// Delete it.
|
||||
|
|
|
@ -14,7 +14,7 @@ struct TemplateContext<'r> {
|
|||
|
||||
#[get("/")]
|
||||
pub fn index() -> Redirect {
|
||||
Redirect::to(uri!("/hbs", hello: name = "Your Name"))
|
||||
Redirect::to(uri!("/hbs", hello(name = "Your Name")))
|
||||
}
|
||||
|
||||
#[get("/hello/<name>")]
|
||||
|
@ -38,7 +38,7 @@ pub fn about() -> Template {
|
|||
#[catch(404)]
|
||||
pub fn not_found(req: &Request<'_>) -> Template {
|
||||
let mut map = std::collections::HashMap::new();
|
||||
map.insert("path", req.uri().path());
|
||||
map.insert("path", req.uri().path().raw());
|
||||
Template::render("hbs/error/404", &map)
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ struct TemplateContext<'r> {
|
|||
|
||||
#[get("/")]
|
||||
pub fn index() -> Redirect {
|
||||
Redirect::to(uri!("/tera", hello: name = "Your Name"))
|
||||
Redirect::to(uri!("/tera", hello(name = "Your Name")))
|
||||
}
|
||||
|
||||
#[get("/hello/<name>")]
|
||||
|
@ -35,7 +35,7 @@ pub fn about() -> Template {
|
|||
#[catch(404)]
|
||||
pub fn not_found(req: &Request<'_>) -> Template {
|
||||
let mut map = HashMap::new();
|
||||
map.insert("path", req.uri().path());
|
||||
map.insert("path", req.uri().path().raw());
|
||||
Template::render("tera/error/404", &map)
|
||||
}
|
||||
|
||||
|
|
|
@ -492,23 +492,23 @@ URIs to `person` can be created as follows:
|
|||
# fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
||||
|
||||
// with unnamed parameters, in route path declaration order
|
||||
let mike = uri!(person: 101, "Mike Smith", Some(28));
|
||||
let mike = uri!(person(101, "Mike Smith", Some(28)));
|
||||
assert_eq!(mike.to_string(), "/101/Mike%20Smith?age=28");
|
||||
|
||||
// with named parameters, order irrelevant
|
||||
let mike = uri!(person: name = "Mike", id = 101, age = Some(28));
|
||||
let mike = uri!(person(name = "Mike", id = 101, age = Some(28)));
|
||||
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
||||
let mike = uri!(person: id = 101, age = Some(28), name = "Mike");
|
||||
let mike = uri!(person(id = 101, age = Some(28), name = "Mike"));
|
||||
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
||||
|
||||
// with a specific mount-point
|
||||
let mike = uri!("/api", person: id = 101, name = "Mike", age = Some(28));
|
||||
let mike = uri!("/api", person(id = 101, name = "Mike", age = Some(28)));
|
||||
assert_eq!(mike.to_string(), "/api/101/Mike?age=28");
|
||||
|
||||
// with optional (defaultable) query parameters ignored
|
||||
let mike = uri!(person: 101, "Mike", _);
|
||||
let mike = uri!(person(101, "Mike", _));
|
||||
assert_eq!(mike.to_string(), "/101/Mike");
|
||||
let mike = uri!(person: id = 101, name = "Mike", age = _);
|
||||
let mike = uri!(person(id = 101, name = "Mike", age = _));
|
||||
assert_eq!(mike.to_string(), "/101/Mike");
|
||||
```
|
||||
|
||||
|
@ -518,7 +518,7 @@ Rocket informs you of any mismatched parameters at compile-time:
|
|||
error: `person` route uri expects 3 parameters but 1 was supplied
|
||||
--> examples/uri/main.rs:7:26
|
||||
|
|
||||
7 | let x = uri!(person: "Mike Smith");
|
||||
7 | let x = uri!(person("Mike Smith"));
|
||||
| ^^^^^^^^^^^^
|
||||
|
|
||||
= note: expected parameters: id: Option <usize>, name: &str, age: Option <u8>
|
||||
|
@ -529,7 +529,7 @@ Rocket also informs you of any type errors at compile-time:
|
|||
```rust,ignore
|
||||
--> examples/uri/src/main.rs:7:31
|
||||
|
|
||||
7 | let x = uri!(person: id = "10", name = "Mike Smith", age = Some(10));
|
||||
7 | let x = uri!(person(id = "10", name = "Mike Smith", age = Some(10)));
|
||||
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
|
||||
```
|
||||
|
||||
|
@ -585,17 +585,17 @@ automatically generated, allowing for URIs to `add_user` to be generated using
|
|||
# #[post("/user/<id>?<details..>")]
|
||||
# fn add_user(id: usize, details: UserDetails) { /* .. */ }
|
||||
|
||||
let link = uri!(add_user: 120, UserDetails { age: Some(20), nickname: "Bob".into() });
|
||||
let link = uri!(add_user(120, UserDetails { age: Some(20), nickname: "Bob".into() }));
|
||||
assert_eq!(link.to_string(), "/user/120?age=20&nickname=Bob");
|
||||
```
|
||||
|
||||
### Typed URI Parts
|
||||
|
||||
The [`UriPart`] trait categorizes types that mark a part of the URI as either a
|
||||
[`Path`] or a [`Query`]. Said another way, types that implement `UriPart` are
|
||||
The [`Part`] trait categorizes types that mark a part of the URI as either a
|
||||
[`Path`] or a [`Query`]. Said another way, types that implement `Part` are
|
||||
marker types that represent a part of a URI at the type-level. Traits such as
|
||||
[`UriDisplay`] and [`FromUriParam`] bound a generic parameter by `UriPart`: `P:
|
||||
UriPart`. This creates two instances of each trait: `UriDisplay<Query>` and
|
||||
[`UriDisplay`] and [`FromUriParam`] bound a generic parameter by `Part`: `P:
|
||||
Part`. This creates two instances of each trait: `UriDisplay<Query>` and
|
||||
`UriDisplay<Path>`, and `FromUriParam<Query>` and `FromUriParam<Path>`.
|
||||
|
||||
As the names might imply, the `Path` version of the traits is used when
|
||||
|
@ -624,7 +624,7 @@ generated.
|
|||
/// Note that `id` is `Option<usize>` in the route, but `id` in `uri!` _cannot_
|
||||
/// be an `Option`. `age`, on the other hand, _must_ be an `Option` (or `Result`
|
||||
/// or `_`) as its in the query part and is allowed to be ignored.
|
||||
let mike = uri!(person: id = 101, name = "Mike", age = Some(28));
|
||||
let mike = uri!(person(id = 101, name = "Mike", age = Some(28)));
|
||||
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
||||
```
|
||||
|
||||
|
@ -639,10 +639,10 @@ Rocket, allows an `&str` to be used in a `uri!` invocation for route URI
|
|||
parameters declared as `String`:
|
||||
|
||||
```rust
|
||||
# use rocket::http::uri::{FromUriParam, UriPart};
|
||||
# use rocket::http::uri::fmt::{FromUriParam, Part};
|
||||
# struct S;
|
||||
# type String = S;
|
||||
impl<'a, P: UriPart> FromUriParam<P, &'a str> for String {
|
||||
impl<'a, P: Part> FromUriParam<P, &'a str> for String {
|
||||
type Target = &'a str;
|
||||
# fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
|
||||
}
|
||||
|
@ -679,13 +679,13 @@ use std::path::PathBuf;
|
|||
#[get("/person/<id>/<details..>")]
|
||||
fn person(id: usize, details: Option<PathBuf>) { /* .. */ }
|
||||
|
||||
uri!(person: id = 100, details = "a/b/c");
|
||||
uri!(person(id = 100, details = "a/b/c"));
|
||||
```
|
||||
|
||||
See the [`FromUriParam`] documentation for further details.
|
||||
|
||||
[`Origin`]: @api/rocket/http/uri/struct.Origin.html
|
||||
[`UriPart`]: @api/rocket/http/uri/trait.UriPart.html
|
||||
[`Part`]: @api/rocket/http/uri/trait.Part.html
|
||||
[`Uri`]: @api/rocket/http/uri/enum.Uri.html
|
||||
[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to
|
||||
[`uri!`]: @api/rocket/macro.uri.html
|
||||
|
|
Loading…
Reference in New Issue