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:
Sergio Benitez 2021-05-19 18:20:40 -07:00
parent 15b1cf59dd
commit fa3e0334c1
78 changed files with 4767 additions and 2549 deletions

View File

@ -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>();
//! ```
//!

View File

@ -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));

View File

@ -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\"");
});
}

View File

@ -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)?)
}
}

View File

@ -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());

View File

@ -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<_>>(),

View File

@ -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
}))
}

View File

@ -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 = &params.route_path;
Ok(quote!(#path!(#input2)))
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,41 +140,30 @@ 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 = internal.route_uri.path().as_str();
quote!(#_fmt::UriArgumentsKind::Static(#path))
} else {
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)
},
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::<fmt::Path>(bindings, &ident, &ty, &expr);
quote_spanned!(expr.span() => &#ident as &dyn #uri_display)
}
Parameter::Ignored(_) => {
let expr = exprs.next().expect("one expr per dynamic arg");
quote_spanned!(expr.span() => &#expr as &dyn #uri_display)
}
}
});
let path = full_uri.path().as_str();
return quote!(#_uri::UriArgumentsKind::Static(#path));
} else {
let path = route_uri.path().as_str();
return quote!(#_uri::UriArgumentsKind::Static(#path));
}
quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
}
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| {
match param {
Parameter::Static(name) => {
quote!(&#name as &dyn #uri_display)
},
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);
quote_spanned!(expr.span() => &#ident as &dyn #uri_display)
}
Parameter::Ignored(_) => {
let expr = exprs.next().expect("one expr per dynamic arg");
quote_spanned!(expr.span() => &#expr as &dyn #uri_display)
}
}
});
quote!(#_uri::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!({
#(#bindings)*
#uri_mod::UriArguments { path: #path, query: #query, }.into_origin()
}))
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)*
let __builder = #_fmt::RouteUriBuilder::new(#path, #query);
__builder #prefix #suffix .render()
}
))
}

View File

@ -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 `:`");
}
// 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()),
// 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");
}
} 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 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)
.map(|p| p.expect("internal error: invalid query parameter"))
.collect::<Vec<_>>()
}
None => vec![]
};
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::<fmt::Query>(query.as_str(), span)
.map(|p| p.expect("internal error: invalid query parameter"))
.collect::<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 {

View File

@ -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;

View File

@ -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,

View File

@ -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));
}
}

View File

@ -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

View File

@ -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()))

View File

@ -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");
}

View File

@ -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("/")))
}

View File

@ -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",
}
}

View File

@ -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>>() { }
| ------------ required by this bound in `assert_ignorable`
| 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>>() { }
| ------------ required by this bound in `assert_ignorable`
| 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<'_>`

View File

@ -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);

View File

@ -1,65 +1,153 @@
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
7 | uri!(simple: id = 100, "Hello");
| ^^^^^^^^^^^^^^^^^
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:8:18
|
8 | uri!(simple: "Hello", id = 100);
| ^^^^^^^^^^^^^^^^^
error: expected `:`
--> $DIR/typed-uris-invalid-syntax.rs:9:16
|
9 | uri!(simple,);
| ^
error: expected argument list after `:`
--> $DIR/typed-uris-invalid-syntax.rs:10:16
error: expected identifier
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
10 | uri!(simple:);
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:12:17
|
12 | uri!(simple("Hello", id = 100));
| ^^^^^^^^^^^^^^^^^
error: unexpected token
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
14 | uri!(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:16:16
|
11 | uri!("/mount");
| ^^^^^^^^
16 | uri!("mount", simple);
| ^
error: unexpected end of input, expected identifier
--> $DIR/typed-uris-invalid-syntax.rs:12:5
error: invalid URI: unexpected EOF: expected token ':' at index 5
--> $DIR/typed-uris-invalid-syntax.rs:17:16
|
12 | uri!("/mount",);
| ^^^^^^^^^^^^^^^^
|
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
17 | 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: URI suffix must contain only query and/or fragment
--> $DIR/typed-uris-invalid-syntax.rs:18:28
|
13 | uri!("mount", simple);
| ^^^^^^^
18 | uri!("/mount", simple, "http://");
| ^^^^^^^^^
error: invalid mount point; mount points must be static, absolute URIs: `/example`
--> $DIR/typed-uris-invalid-syntax.rs:14:10
error: expected 1, 2, or 3 arguments, found 4
--> $DIR/typed-uris-invalid-syntax.rs:19:36
|
14 | uri!("/mount/<id>", simple);
| ^^^^^^^^^^^^^
19 | uri!("/mount", simple, "#foo", "?foo");
| ^^^^^^
error: unexpected end of input, call to `uri!` cannot be empty
--> $DIR/typed-uris-invalid-syntax.rs:15:5
error: invalid URI: unexpected EOF: expected token ':' at index 5
--> $DIR/typed-uris-invalid-syntax.rs:20:16
|
15 | uri!();
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: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>"

View File

@ -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`

View File

@ -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>>() { }
| ------------ required by this bound in `assert_ignorable`
| 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>>() { }
| ------------ required by this bound in `assert_ignorable`
| 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<'_>`

View File

@ -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
|

View File

@ -1,65 +1,152 @@
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
7 | uri!(simple: id = 100, "Hello");
| ^^
error: named and unnamed parameters cannot be mixed
--> $DIR/typed-uris-invalid-syntax.rs:8:18
|
8 | uri!(simple: "Hello", id = 100);
| ^^^^^^^
error: expected `:`
--> $DIR/typed-uris-invalid-syntax.rs:9:16
|
9 | uri!(simple,);
| ^
error: expected argument list after `:`
--> $DIR/typed-uris-invalid-syntax.rs:10:16
error: expected identifier
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
10 | uri!(simple:);
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:12:17
|
12 | uri!(simple("Hello", id = 100));
| ^^^^^^^
error: unexpected token
--> $DIR/typed-uris-invalid-syntax.rs:14:16
|
14 | uri!(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:16: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,);
| ^^^^^^

View File

@ -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`

View File

@ -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"));
}

View File

@ -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"));
}

View File

@ -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");
}

View File

@ -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) => (

View File

@ -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"

View File

@ -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 {

View File

@ -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());

View File

@ -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()))?)
}

View File

@ -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)?)
}

View File

@ -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 / ":" / "@"

View File

@ -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(&[
&REG_NAME_CHARS, &[b':']
]);
pub const PATH_CHARS: [u8; 256] = char_table(&[
UNRESERVED, PCT_ENCODED, SUB_DELIMS, &[b':', b'@', b'/']
&REG_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 }

View File

@ -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",
}
}

View File

@ -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;

View File

@ -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(())

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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),
}

View File

@ -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

View File

@ -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,66 +419,221 @@ 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);
}
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 {
Raw(v) => f.write_raw(v),
NameValue(n, v) => f.write_named_value(n, v),
Value(v) => f.write_value(v),
};
}
let mut f = Formatter::<Query>::new(&mut string);
for arg in args {
let _ = match arg {
Raw(v) => f.write_raw(v),
NameValue(n, v) => f.write_named_value(n, v),
Value(v) => f.write_value(v),
};
}
match string.is_empty() {
false => Some(string.into()),
true => None,
}
(!string.is_empty()).then(|| string.into())
}
});
};
Origin::new(path, query)
RouteUriBuilder { path, query }
}
pub fn with_prefix<P: ValidRoutePrefix>(self, p: P) -> PrefixedRouteUri<P::Output> {
PrefixedRouteUri(p.append(self.path, self.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;

View File

@ -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)]

View File

@ -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::*;

View File

@ -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>);
}

View File

@ -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>>>();

View File

@ -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::*;

View File

@ -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()
});
pub fn normalize(&mut self) {
if !self.path().is_normalized(true) {
self.path = self.path().to_normalized(true);
}
Segments { source: self.path(), segments: cached, pos: 0 }
if let Some(query) = self.query() {
if !query.is_normalized() {
self.query = query.to_normalized();
}
}
}
/// 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.
/// 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&param").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]

View File

@ -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&param").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);

View File

@ -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(())
}
}

View File

@ -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,74 +222,63 @@ 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)?;
self.pos += 1;
Some(item)
}
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 size_hint(&self) -> (usize, Option<usize>) {
(self.len(), Some(self.len()))
}
fn count(self) -> usize {
self.len()
}
fn count(self) -> usize {
self.len()
}
}
)
}
impl_iterator!(Path => &'a str);
impl_iterator!(Query => (&'a str, &'a str));

View File

@ -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))
}
/// 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()
pub fn reference(&self) -> Option<&Reference<'a>> {
match self {
Uri::Reference(ref inner) => Some(inner),
_ => None
}
}
}
@ -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);

View File

@ -164,7 +164,7 @@ impl Catcher {
Catcher {
name: None,
base: uri::Origin::new("/", None::<&str>),
base: uri::Origin::ROOT,
handler: Box::new(handler),
code,
}

View File

@ -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.
///

View File

@ -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...

View File

@ -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.
///

View File

@ -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);

View File

@ -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)

View File

@ -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);

View File

@ -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,34 +59,35 @@ 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())
}
/// Construct a "temporary" (307) redirect response. This response instructs
/// the client to reissue the current request to a different URL,
/// maintaining the contents of the request identically. This means that,
/// for example, a `POST` request will be resent, contents included, to the
/// requested URL.
///
/// # Examples
///
/// ```rust
/// use rocket::response::Redirect;
///
/// # let query = "foo";
/// let redirect = Redirect::temporary("/other_url");
/// let redirect = Redirect::temporary(format!("https://google.com/{}", query));
/// ```
pub fn temporary<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
Redirect(Status::TemporaryRedirect, uri.try_into().ok())
}
/// Construct a "temporary" (307) redirect response. This response instructs
/// the client to reissue the current request to a different URL,
/// maintaining the contents of the request identically. This means that,
/// for example, a `POST` request will be resent, contents included, to the
/// requested URL.
///
/// # Examples
///
/// ```rust
/// # #[macro_use] extern crate rocket;
/// use rocket::response::Redirect;
///
/// 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<Reference<'static>>>(uri: U) -> Redirect {
Redirect(Status::TemporaryRedirect, uri.try_into().ok())
}
/// Construct a "permanent" (308) redirect response. This redirect must only
/// be used for permanent redirects as it is cached by clients. This
@ -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())
}
}

View File

@ -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));
}

View File

@ -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))

View File

@ -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,8 +145,13 @@ 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) {
trace_!("request {} missing static query {:?}", req, 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;
}
}
@ -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())
}
}

View File

@ -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)

View File

@ -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;
}

View File

@ -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"));
}

View File

@ -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> {

View File

@ -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("/")
}

View File

@ -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));
}

View File

@ -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])
}

View File

@ -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 {

View File

@ -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);
}

View File

@ -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.

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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,8 +518,8 @@ 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,8 +529,8 @@ 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));
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
7 | let x = uri!(person(id = "10", name = "Mike Smith", age = Some(10)));
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
```
We recommend that you use `uri!` exclusively when constructing URIs to your
@ -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