mirror of https://github.com/rwf2/Rocket.git
Overhaul URI types, parsers, 'uri!' macro.
This commit entirely rewrites Rocket's URI parsing routines and overhauls the 'uri!' macro resolving all known issues and removing any potential limitations for compile-time URI creation. This commit: * Introduces a new 'Reference' URI variant for URI-references. * Modifies 'Redirect' to accept 'TryFrom<Reference>'. * Introduces a new 'Asterisk' URI variant for parity. * Allows creation of any URI type from a string literal via 'uri!'. * Enables dynamic/static prefixing/suffixing of route URIs in 'uri!'. * Unifies 'Segments' and 'QuerySegments' into one generic 'Segments'. * Consolidates URI formatting types/traits into a 'uri::fmt' module. * Makes APIs more symmetric across URI types. It also includes the following less-relevant changes: * Implements 'FromParam' for a single-segment 'PathBuf'. * Adds 'FileName::is_safe()'. * No longer reparses upstream request URIs. Resolves #842. Resolves #853. Resolves #998.
This commit is contained in:
parent
15b1cf59dd
commit
fa3e0334c1
|
@ -76,17 +76,17 @@
|
||||||
//! `SpaceHelmet`:
|
//! `SpaceHelmet`:
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate rocket;
|
//! # #[macro_use] extern crate rocket;
|
||||||
//! # extern crate rocket_contrib;
|
//! # extern crate rocket_contrib;
|
||||||
//! use rocket::http::uri::Uri;
|
//! use rocket::http::uri::Uri;
|
||||||
//! use rocket_contrib::helmet::{SpaceHelmet, Frame, XssFilter, Hsts, NoSniff};
|
//! use rocket_contrib::helmet::{SpaceHelmet, Frame, XssFilter, Hsts, NoSniff};
|
||||||
//!
|
//!
|
||||||
//! let site_uri = Uri::parse("https://mysite.example.com").unwrap();
|
//! let site_uri = uri!("https://mysite.example.com");
|
||||||
//! let report_uri = Uri::parse("https://report.example.com").unwrap();
|
//! let report_uri = uri!("https://report.example.com");
|
||||||
//! let helmet = SpaceHelmet::default()
|
//! let helmet = SpaceHelmet::default()
|
||||||
//! .enable(Hsts::default())
|
//! .enable(Hsts::default())
|
||||||
//! .enable(Frame::AllowFrom(site_uri))
|
//! .enable(Frame::AllowFrom(site_uri.into()))
|
||||||
//! .enable(XssFilter::EnableReport(report_uri))
|
//! .enable(XssFilter::EnableReport(report_uri.into()))
|
||||||
//! .disable::<NoSniff>();
|
//! .disable::<NoSniff>();
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
|
|
@ -357,10 +357,12 @@ impl Into<Vec<Route>> for StaticFiles {
|
||||||
#[rocket::async_trait]
|
#[rocket::async_trait]
|
||||||
impl Handler for StaticFiles {
|
impl Handler for StaticFiles {
|
||||||
async fn handle<'r>(&self, req: &'r Request<'_>, data: Data) -> Outcome<'r> {
|
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.
|
// Get the segments as a `PathBuf`, allowing dotfiles requested.
|
||||||
let options = self.options;
|
let options = self.options;
|
||||||
let allow_dotfiles = options.contains(Options::DotFiles);
|
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())
|
.and_then(|segments| segments.to_path_buf(allow_dotfiles).ok())
|
||||||
.map(|path| self.root.join(path));
|
.map(|path| self.root.join(path));
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ extern crate rocket;
|
||||||
|
|
||||||
#[cfg(feature = "helmet")]
|
#[cfg(feature = "helmet")]
|
||||||
mod helmet_tests {
|
mod helmet_tests {
|
||||||
use rocket::http::{Status, uri::Uri};
|
use rocket::http::Status;
|
||||||
use rocket::local::blocking::{Client, LocalResponse};
|
use rocket::local::blocking::{Client, LocalResponse};
|
||||||
|
|
||||||
use rocket_contrib::helmet::*;
|
use rocket_contrib::helmet::*;
|
||||||
|
@ -108,24 +108,22 @@ mod helmet_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uri_test() {
|
fn uri_test() {
|
||||||
let allow_uri = Uri::parse("https://www.google.com").unwrap();
|
let allow_uri = uri!("https://rocket.rs");
|
||||||
let report_uri = Uri::parse("https://www.google.com").unwrap();
|
let report_uri = uri!("https://rocket.rs");
|
||||||
let enforce_uri = Uri::parse("https://www.google.com").unwrap();
|
let enforce_uri = uri!("https://rocket.rs");
|
||||||
|
|
||||||
let helmet = SpaceHelmet::default()
|
let helmet = SpaceHelmet::default()
|
||||||
.enable(Frame::AllowFrom(allow_uri))
|
.enable(Frame::AllowFrom(allow_uri.into()))
|
||||||
.enable(XssFilter::EnableReport(report_uri))
|
.enable(XssFilter::EnableReport(report_uri.into()))
|
||||||
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri));
|
.enable(ExpectCt::ReportAndEnforce(Duration::seconds(30), enforce_uri.into()));
|
||||||
|
|
||||||
dispatch!(helmet, |response: LocalResponse<'_>| {
|
dispatch!(helmet, |response: LocalResponse<'_>| {
|
||||||
assert_header!(response, "X-Frame-Options",
|
assert_header!(response, "X-Frame-Options", "ALLOW-FROM https://rocket.rs");
|
||||||
"ALLOW-FROM https://www.google.com");
|
|
||||||
|
|
||||||
assert_header!(response, "X-XSS-Protection",
|
assert_header!(response, "X-XSS-Protection", "1; report=https://rocket.rs");
|
||||||
"1; report=https://www.google.com");
|
|
||||||
|
|
||||||
assert_header!(response, "Expect-CT",
|
assert_header!(response, "Expect-CT",
|
||||||
"max-age=30, enforce, report-uri=\"https://www.google.com\"");
|
"max-age=30, enforce, report-uri=\"https://rocket.rs\"");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,8 +19,8 @@ impl FromMeta for Dynamic {
|
||||||
let string = StringLit::from_meta(meta)?;
|
let string = StringLit::from_meta(meta)?;
|
||||||
let span = string.subspan(1..string.len() + 1);
|
let span = string.subspan(1..string.len() + 1);
|
||||||
|
|
||||||
// We don't allow `_`. We abuse `uri::Query` to enforce this.
|
// We don't allow `_`. We abuse `fmt::Query` to enforce this.
|
||||||
Ok(Dynamic::parse::<uri::Query>(&string, span)?)
|
Ok(Dynamic::parse::<fmt::Query>(&string, span)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,10 +2,10 @@ use unicode_xid::UnicodeXID;
|
||||||
use devise::{Diagnostic, ext::SpanDiagnosticExt};
|
use devise::{Diagnostic, ext::SpanDiagnosticExt};
|
||||||
|
|
||||||
use crate::name::Name;
|
use crate::name::Name;
|
||||||
use crate::http::uri::{self, UriPart};
|
|
||||||
use crate::proc_macro_ext::StringLit;
|
use crate::proc_macro_ext::StringLit;
|
||||||
use crate::proc_macro2::Span;
|
use crate::proc_macro2::Span;
|
||||||
use crate::attribute::param::{Parameter, Dynamic};
|
use crate::attribute::param::{Parameter, Dynamic};
|
||||||
|
use crate::http::uri::fmt::{Part, Kind, Path};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Error<'a> {
|
pub struct Error<'a> {
|
||||||
|
@ -27,7 +27,7 @@ pub enum ErrorKind {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dynamic {
|
impl Dynamic {
|
||||||
pub fn parse<P: UriPart>(
|
pub fn parse<P: Part>(
|
||||||
segment: &str,
|
segment: &str,
|
||||||
span: Span,
|
span: Span,
|
||||||
) -> Result<Self, Error<'_>> {
|
) -> Result<Self, Error<'_>> {
|
||||||
|
@ -40,7 +40,7 @@ impl Dynamic {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parameter {
|
impl Parameter {
|
||||||
pub fn parse<P: UriPart>(
|
pub fn parse<P: Part>(
|
||||||
segment: &str,
|
segment: &str,
|
||||||
source_span: Span,
|
source_span: Span,
|
||||||
) -> Result<Self, Error<'_>> {
|
) -> Result<Self, Error<'_>> {
|
||||||
|
@ -62,7 +62,7 @@ impl Parameter {
|
||||||
}
|
}
|
||||||
|
|
||||||
let dynamic = Dynamic { name: Name::new(name, span), trailing, index: 0 };
|
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));
|
return Err(Error::new(name, span, ErrorKind::Ignored));
|
||||||
} else if dynamic.is_wild() {
|
} else if dynamic.is_wild() {
|
||||||
return Ok(Parameter::Ignored(dynamic));
|
return Ok(Parameter::Ignored(dynamic));
|
||||||
|
@ -84,7 +84,7 @@ impl Parameter {
|
||||||
Ok(Parameter::Static(Name::new(segment, source_span)))
|
Ok(Parameter::Static(Name::new(segment, source_span)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_many<P: uri::UriPart>(
|
pub fn parse_many<P: Part>(
|
||||||
source: &str,
|
source: &str,
|
||||||
source_span: Span,
|
source_span: Span,
|
||||||
) -> impl Iterator<Item = Result<Self, Error<'_>>> {
|
) -> impl Iterator<Item = Result<Self, Error<'_>>> {
|
||||||
|
@ -182,7 +182,7 @@ impl devise::FromMeta for Dynamic {
|
||||||
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
||||||
let string = StringLit::from_meta(meta)?;
|
let string = StringLit::from_meta(meta)?;
|
||||||
let span = string.subspan(1..string.len() + 1);
|
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() {
|
if param.is_wild() {
|
||||||
return Err(Error::new(&string, span, ErrorKind::Ignored).into());
|
return Err(Error::new(&string, span, ErrorKind::Ignored).into());
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::syn_ext::FnArgExt;
|
||||||
use crate::name::Name;
|
use crate::name::Name;
|
||||||
use crate::proc_macro2::Span;
|
use crate::proc_macro2::Span;
|
||||||
use crate::http::ext::IntoOwned;
|
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.
|
/// This structure represents the parsed `route` attribute and associated items.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -159,14 +159,14 @@ impl Route {
|
||||||
|
|
||||||
// Parse and collect the path parameters.
|
// Parse and collect the path parameters.
|
||||||
let (source, span) = (attr.uri.path(), attr.uri.path_span);
|
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))
|
.map(|p| Route::upgrade_param(p?, &arguments))
|
||||||
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
// Parse and collect the query parameters.
|
// Parse and collect the query parameters.
|
||||||
let query_params = match (attr.uri.query(), attr.uri.query_span) {
|
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))
|
.map(|p| Route::upgrade_param(p?, &arguments))
|
||||||
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
.filter_map(|p| p.map_err(|e| diags.push(e.into())).ok())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
|
|
@ -46,19 +46,19 @@ pub fn catchers_macro(input: proc_macro::TokenStream) -> TokenStream {
|
||||||
pub fn uri_macro(input: proc_macro::TokenStream) -> TokenStream {
|
pub fn uri_macro(input: proc_macro::TokenStream) -> TokenStream {
|
||||||
uri::_uri_macro(input.into())
|
uri::_uri_macro(input.into())
|
||||||
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
|
.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 {
|
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
|
// assist in propoagate further errors. Alas, we can't set the span to the
|
||||||
// invocation of `uri!` without access to `span.parent()`, and
|
// invocation of `uri!` without access to `span.parent()`, and
|
||||||
// `Span::call_site()` here points to the `#[route]`, immediate caller,
|
// `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())
|
uri::_uri_internal_macro(input.into())
|
||||||
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
|
.unwrap_or_else(|diag| diag.emit_as_expr_tokens_or(quote! {
|
||||||
rocket::http::uri::Origin::dummy()
|
rocket::http::uri::Origin::ROOT
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,15 +3,14 @@ use std::fmt::Display;
|
||||||
use devise::{syn, Result};
|
use devise::{syn, Result};
|
||||||
use devise::ext::{SpanDiagnosticExt, quote_respanned};
|
use devise::ext::{SpanDiagnosticExt, quote_respanned};
|
||||||
|
|
||||||
use crate::http::uri;
|
use crate::http::uri::fmt;
|
||||||
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
|
||||||
use crate::http_codegen::Optional;
|
use crate::http_codegen::Optional;
|
||||||
|
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
||||||
use crate::syn_ext::IdentExt;
|
use crate::syn_ext::IdentExt;
|
||||||
use crate::bang::uri_parsing::*;
|
use crate::bang::uri_parsing::*;
|
||||||
use crate::proc_macro2::TokenStream;
|
use crate::proc_macro2::TokenStream;
|
||||||
use crate::attribute::param::Parameter;
|
use crate::attribute::param::Parameter;
|
||||||
use crate::exports::_uri;
|
use crate::exports::*;
|
||||||
|
|
||||||
use crate::URI_MACRO_PREFIX;
|
use crate::URI_MACRO_PREFIX;
|
||||||
|
|
||||||
macro_rules! p {
|
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> {
|
pub fn _uri_macro(input: TokenStream) -> Result<TokenStream> {
|
||||||
let input2: TokenStream = input.clone().into();
|
let input2: TokenStream = input.clone().into();
|
||||||
let mut params = syn::parse2::<UriParams>(input)?;
|
match syn::parse2::<UriMacro>(input)? {
|
||||||
prefix_last_segment(&mut params.route_path, URI_MACRO_PREFIX);
|
UriMacro::Routed(ref mut mac) => {
|
||||||
|
prefix_last_segment(&mut mac.route.path, URI_MACRO_PREFIX);
|
||||||
let path = ¶ms.route_path;
|
let path = &mac.route.path;
|
||||||
Ok(quote!(#path!(#input2)))
|
Ok(quote!(#path!(#input2)))
|
||||||
|
},
|
||||||
|
UriMacro::Literal(uri) => Ok(quote!(#uri)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_exprs<'a>(internal: &'a InternalUriParams) -> Result<(
|
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
|
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() {
|
match internal.validate() {
|
||||||
Validation::Ok(exprs) => {
|
Validation::Ok(exprs) => {
|
||||||
let path_params = internal.dynamic_path_params();
|
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();
|
let mut route_name = quote!(#route_name).to_string();
|
||||||
route_name.retain(|c| !c.is_whitespace());
|
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")
|
.error("expected unnamed arguments due to ignored parameters")
|
||||||
.note(format!("uri for route `{}` ignores path parameters: \"{}\"",
|
.note(format!("uri for route `{}` ignores path parameters: \"{}\"",
|
||||||
route_name, internal.route_uri));
|
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();
|
let mut route_name = quote!(#route_name).to_string();
|
||||||
route_name.retain(|c| !c.is_whitespace());
|
route_name.retain(|c| !c.is_whitespace());
|
||||||
|
|
||||||
let diag = internal.uri_params.args_span()
|
let diag = internal.uri_mac.args_span()
|
||||||
.error(format!("expected {} but {} supplied",
|
.error(format!("route expects {} but {} supplied",
|
||||||
p!(expected, "parameter"), p!(actual, "was")))
|
p!(expected, "parameter"), p!(actual, "was")))
|
||||||
.note(format!("route `{}` has uri \"{}\"",
|
.note(format!("route `{}` has uri \"{}\"", route_name, internal.route_uri));
|
||||||
route_name, internal.route_uri));
|
|
||||||
|
|
||||||
Err(diag)
|
Err(diag)
|
||||||
}
|
}
|
||||||
Validation::Named(missing, extra, dup) => {
|
Validation::Named(missing, extra, dup) => {
|
||||||
let e = format!("invalid parameters for `{}` route uri", quote!(#route_name));
|
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()));
|
.note(format!("uri parameters are: {}", internal.fn_args_str()));
|
||||||
|
|
||||||
fn join<S: Display, T: Iterator<Item = S>>(iter: T) -> (&'static str, String) {
|
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 span = expr.span();
|
||||||
let part = match P::KIND {
|
let part = match P::KIND {
|
||||||
uri::Kind::Path => quote_spanned!(span => #_uri::Path),
|
fmt::Kind::Path => quote_spanned!(span => #_fmt::Path),
|
||||||
uri::Kind::Query => quote_spanned!(span => #_uri::Query),
|
fmt::Kind::Query => quote_spanned!(span => #_fmt::Query),
|
||||||
};
|
};
|
||||||
|
|
||||||
let tmp_ident = ident.clone().with_span(expr.span());
|
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 =>
|
to.push(quote_spanned!(span =>
|
||||||
#[allow(non_snake_case)] #let_stmt;
|
#[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)>,
|
mut args: impl Iterator<Item = (&'a Ident, &'a Type)>,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
if internal.dynamic_path_params().count() == 0 {
|
if internal.dynamic_path_params().count() == 0 {
|
||||||
let route_uri = &internal.route_uri;
|
let path = internal.route_uri.path().as_str();
|
||||||
if let Some(ref mount) = internal.uri_params.mount_point {
|
quote!(#_fmt::UriArgumentsKind::Static(#path))
|
||||||
let full_uri = route_uri.map_path(|p| format!("{}/{}", mount, p))
|
} else {
|
||||||
.expect("origin from path")
|
let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Path>);
|
||||||
.into_normalized();
|
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();
|
quote!(#_fmt::UriArgumentsKind::Dynamic(&[#(#dyn_exprs),*]))
|
||||||
return quote!(#_uri::UriArgumentsKind::Static(#path));
|
|
||||||
} else {
|
|
||||||
let path = route_uri.path().as_str();
|
|
||||||
return quote!(#_uri::UriArgumentsKind::Static(#path));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let uri_display = quote!(#_uri::UriDisplay<#_uri::Path>);
|
|
||||||
let all_path_params = internal.mount_params.iter().chain(internal.path_params.iter());
|
|
||||||
let dyn_exprs = all_path_params.map(|param| {
|
|
||||||
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>(
|
fn explode_query<'a>(
|
||||||
|
@ -184,11 +174,11 @@ fn explode_query<'a>(
|
||||||
) -> Option<TokenStream> {
|
) -> Option<TokenStream> {
|
||||||
let query = internal.route_uri.query()?.as_str();
|
let query = internal.route_uri.query()?.as_str();
|
||||||
if internal.dynamic_query_params().count() == 0 {
|
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 query_arg = quote!(#_fmt::UriQueryArgument);
|
||||||
let uri_display = quote!(#_uri::UriDisplay<#_uri::Query>);
|
let uri_display = quote!(#_fmt::UriDisplay<#_fmt::Query>);
|
||||||
let dyn_exprs = internal.query_params.iter().filter_map(|param| {
|
let dyn_exprs = internal.query_params.iter().filter_map(|param| {
|
||||||
if let Parameter::Static(source) = param {
|
if let Parameter::Static(source) = param {
|
||||||
return Some(quote!(#query_arg::Raw(#source)));
|
return Some(quote!(#query_arg::Raw(#source)));
|
||||||
|
@ -208,10 +198,9 @@ fn explode_query<'a>(
|
||||||
let expr = match arg_expr.as_expr() {
|
let expr = match arg_expr.as_expr() {
|
||||||
Some(expr) => expr,
|
Some(expr) => expr,
|
||||||
None => {
|
None => {
|
||||||
// Force a typecheck for the `Ignoreable` trait. Note that write
|
// Force a typecheck for the `Ignoreable` trait.
|
||||||
// out the path to `is_ignorable` to get the right span.
|
|
||||||
bindings.push(quote_respanned! { arg_expr.span() =>
|
bindings.push(quote_respanned! { arg_expr.span() =>
|
||||||
rocket::http::uri::assert_ignorable::<#_uri::Query, #ty>();
|
#_fmt::assert_ignorable::<#_fmt::Query, #ty>();
|
||||||
});
|
});
|
||||||
|
|
||||||
return None;
|
return None;
|
||||||
|
@ -219,7 +208,7 @@ fn explode_query<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let name = &dynamic.name;
|
let name = &dynamic.name;
|
||||||
add_binding::<uri::Query>(bindings, &ident, &ty, &expr);
|
add_binding::<fmt::Query>(bindings, &ident, &ty, &expr);
|
||||||
Some(match dynamic.trailing {
|
Some(match dynamic.trailing {
|
||||||
false => quote_spanned! { expr.span() =>
|
false => quote_spanned! { expr.span() =>
|
||||||
#query_arg::NameValue(#name, &#ident as &dyn #uri_display)
|
#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> {
|
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 (path_exprs, query_exprs, mut fn_args) = extract_exprs(&internal)?;
|
||||||
|
|
||||||
let mut bindings = vec![];
|
let mut bindings = vec![];
|
||||||
let uri_mod = quote!(rocket::http::uri);
|
|
||||||
let path = explode_path(&internal, &mut bindings, path_exprs, &mut fn_args);
|
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(explode_query(&internal, &mut bindings, query_exprs, fn_args));
|
||||||
let query = Optional(query);
|
|
||||||
|
|
||||||
Ok(quote!({
|
let prefix = internal.uri_mac.prefix.as_ref()
|
||||||
#(#bindings)*
|
.map(|prefix| quote_spanned!(prefix.span() => .with_prefix(#prefix)));
|
||||||
#uri_mod::UriArguments { path: #path, query: #query, }.into_origin()
|
|
||||||
}))
|
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()
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,17 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use devise::{Spanned, ext::TypeExt};
|
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::{http_codegen, syn::{self, Expr, Ident, LitStr, Path, Token, Type}};
|
||||||
use crate::syn::parse::{self, Parse, ParseStream};
|
use crate::syn::parse::{self, Parse, ParseStream, Parser};
|
||||||
use crate::syn::punctuated::Punctuated;
|
use crate::syn::punctuated::Punctuated;
|
||||||
|
|
||||||
use crate::http::{uri, uri::Origin, ext::IntoOwned};
|
use crate::http::uri::{Uri, Origin, Absolute, fmt};
|
||||||
use crate::proc_macro2::{TokenStream, Span};
|
use crate::http::ext::IntoOwned;
|
||||||
|
use crate::proc_macro2::{TokenStream, TokenTree, Span};
|
||||||
use crate::proc_macro_ext::StringLit;
|
use crate::proc_macro_ext::StringLit;
|
||||||
use crate::attribute::param::{Parameter, Dynamic};
|
use crate::attribute::param::{Parameter, Dynamic};
|
||||||
use crate::name::Name;
|
use crate::name::Name;
|
||||||
|
@ -32,24 +36,54 @@ pub enum Args {
|
||||||
Named(Punctuated<Arg, Token![,]>),
|
Named(Punctuated<Arg, Token![,]>),
|
||||||
}
|
}
|
||||||
|
|
||||||
// For an invocation that looks like:
|
/// A string literal parsed as a URI.
|
||||||
// uri!("/mount/point", this::route: e1, e2, e3);
|
|
||||||
// ^-------------| ^----------| ^---------|
|
|
||||||
// uri_params.mount_point | uri_params.arguments
|
|
||||||
// uri_params.route_path
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct UriParams {
|
pub struct UriLit(Uri<'static>, Span);
|
||||||
pub mount_point: Option<Origin<'static>>,
|
|
||||||
pub route_path: Path,
|
/// An expression in a URI slot (prefix, suffix, or literal).
|
||||||
pub arguments: Args,
|
#[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)]
|
#[derive(Debug)]
|
||||||
pub struct FnArg {
|
|
||||||
pub ident: Ident,
|
|
||||||
pub ty: Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Validation<'a> {
|
pub enum Validation<'a> {
|
||||||
// Parameters that were ignored in a named argument setting.
|
// Parameters that were ignored in a named argument setting.
|
||||||
NamedIgnored(Vec<&'a Dynamic>),
|
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
|
// 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!`,
|
// 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
|
// 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
|
// route (from the route-specific macro) with the user's parameters (by
|
||||||
// forwarding them to the internal_uri! call).
|
// forwarding them to the internal_uri! call).
|
||||||
//
|
//
|
||||||
// `fn_args` are the URI arguments (excluding request guards and ignored path
|
// `fn_args` are the URI arguments (excluding request guards) from the original
|
||||||
// parts) from the original handler in the order they were declared in the URI
|
// handler in the order they were declared in the URI (`<first>/<second>`).
|
||||||
// (`<first>/<second>`). `route_uri` is the URI itself.
|
// `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)*);
|
// 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 ------^
|
// ^------ route_uri ------^
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct InternalUriParams {
|
pub struct InternalUriParams {
|
||||||
pub route_uri: Origin<'static>,
|
pub route_uri: Origin<'static>,
|
||||||
pub mount_params: Vec<Parameter>,
|
|
||||||
pub path_params: Vec<Parameter>,
|
pub path_params: Vec<Parameter>,
|
||||||
pub query_params: Vec<Parameter>,
|
pub query_params: Vec<Parameter>,
|
||||||
pub fn_args: Vec<FnArg>,
|
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 {
|
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> {
|
impl Parse for Args {
|
||||||
Err(parse::Error::new(span.into(), s.as_ref()))
|
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
|
||||||
}
|
|
||||||
|
|
||||||
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>()?;
|
|
||||||
|
|
||||||
// If there are no arguments, finish early.
|
// If there are no arguments, finish early.
|
||||||
if !input.peek(Token![:]) && input.cursor().eof() {
|
if input.cursor().eof() {
|
||||||
let arguments = Args::Unnamed(Punctuated::new());
|
return Ok(Args::Unnamed(Punctuated::new()));
|
||||||
return Ok(Self { mount_point, route_path, arguments });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse arguments
|
// Parse arguments. Ensure both types of args were not used at once.
|
||||||
let colon = input.parse::<Token![:]>()?;
|
let args: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
||||||
let arguments: Punctuated<Arg, Token![,]> = input.parse_terminated(Arg::parse)?;
|
let mut first_is_named = None;
|
||||||
|
for arg in &args {
|
||||||
// A 'colon' was used but there are no arguments.
|
if let Some(first_is_named) = first_is_named {
|
||||||
if arguments.is_empty() {
|
if first_is_named != arg.is_named() {
|
||||||
return err(colon.span(), "expected argument list after `:`");
|
return err(args.span(), "named and unnamed parameters cannot be mixed");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
// Ensure that both types of arguments were not used at once.
|
first_is_named = Some(arg.is_named());
|
||||||
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()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// Create the `Args` enum, which properly record one-kind-of-argument-ness.
|
||||||
let arguments = match prev_named {
|
match first_is_named {
|
||||||
Some(true) => Args::Named(arguments),
|
Some(true) => Ok(Args::Named(args)),
|
||||||
_ => Args::Unnamed(arguments)
|
_ => 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 {
|
impl Parse for InternalUriParams {
|
||||||
fn parse(input: ParseStream<'_>) -> parse::Result<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![,]>()?;
|
input.parse::<Token![,]>()?;
|
||||||
|
|
||||||
// Validation should always succeed since this macro can only be called
|
// Validation should always succeed since this macro can only be called
|
||||||
// if the route attribute succeeded, implying a valid route URI.
|
// 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)
|
let route_uri = Origin::parse_route(&route_uri_str)
|
||||||
.map(|o| o.into_normalized().into_owned())
|
.map(|o| o.into_normalized().into_owned())
|
||||||
.map_err(|_| input.error("internal error: invalid route URI"))?;
|
.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();
|
let fn_args = fn_args.into_iter().collect();
|
||||||
|
|
||||||
input.parse::<Token![,]>()?;
|
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 span = route_uri_str.subspan(1..route_uri.path().len() + 1);
|
||||||
let mount_params = match uri_params.mount_point.as_ref() {
|
let path_params = Parameter::parse_many::<fmt::Path>(route_uri.path().as_str(), span)
|
||||||
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)
|
|
||||||
.map(|p| p.expect("internal error: invalid path parameter"))
|
.map(|p| p.expect("internal error: invalid path parameter"))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let query_params = match route_uri.query() {
|
let query = route_uri.query();
|
||||||
Some(query) => {
|
let query_params = query.map(|query| {
|
||||||
let i = route_uri.path().len() + 2;
|
let i = route_uri.path().len() + 2;
|
||||||
let span = route_uri_str.subspan(i..(i + query.len()));
|
let span = route_uri_str.subspan(i..(i + query.len()));
|
||||||
Parameter::parse_many::<uri::Query>(query.as_str(), span)
|
Parameter::parse_many::<fmt::Query>(query.as_str(), span)
|
||||||
.map(|p| p.expect("internal error: invalid query parameter"))
|
.map(|p| p.expect("internal error: invalid query parameter"))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}).unwrap_or_default();
|
||||||
None => vec![]
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(InternalUriParams {
|
Ok(InternalUriParams {
|
||||||
route_uri,
|
route_uri,
|
||||||
mount_params,
|
|
||||||
path_params,
|
path_params,
|
||||||
query_params,
|
query_params,
|
||||||
fn_args,
|
fn_args,
|
||||||
uri_params
|
uri_mac: uri_params
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,7 +368,7 @@ impl InternalUriParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate(&self) -> Validation<'_> {
|
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());
|
let all_params = self.dynamic_path_params().chain(self.dynamic_query_params());
|
||||||
match args {
|
match args {
|
||||||
Args::Unnamed(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.
|
/// The Span to use when referring to all of the arguments.
|
||||||
pub fn args_span(&self) -> Span {
|
pub fn args_span(&self) -> Span {
|
||||||
match self.arguments.num() {
|
match self.route.args.num() {
|
||||||
0 => self.route_path.span(),
|
0 => self.route.path.span(),
|
||||||
_ => self.arguments.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 {
|
impl ToTokens for ArgExpr {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream) {
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
|
|
||||||
use devise::{*, ext::SpanDiagnosticExt};
|
use devise::{*, ext::SpanDiagnosticExt};
|
||||||
use rocket_http::uri;
|
|
||||||
|
|
||||||
use crate::exports::*;
|
use crate::exports::*;
|
||||||
use crate::derive::form_field::{FieldExt, VariantExt};
|
use crate::derive::form_field::{FieldExt, VariantExt};
|
||||||
use crate::proc_macro2::TokenStream;
|
use crate::proc_macro2::TokenStream;
|
||||||
|
use crate::http::uri::fmt;
|
||||||
|
|
||||||
const NO_EMPTY_FIELDS: &str = "fieldless structs are not supported";
|
const NO_EMPTY_FIELDS: &str = "fieldless structs are not supported";
|
||||||
const NO_NULLARY: &str = "nullary items 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";
|
const EXACTLY_ONE_FIELD: &str = "struct must have exactly one field";
|
||||||
|
|
||||||
pub fn derive_uri_display_query(input: proc_macro::TokenStream) -> TokenStream {
|
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 URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Query>);
|
||||||
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Query>);
|
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_fmt::Query>);
|
||||||
|
|
||||||
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
||||||
.support(Support::Struct | Support::Enum | Support::Type | Support::Lifetime)
|
.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()
|
Err(diag) => return diag.emit_as_item_tokens()
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_self = from_uri_param::<Query>(input.clone(), quote!(Self));
|
let from_self = from_uri_param::<fmt::Query>(input.clone(), quote!(Self));
|
||||||
let from_ref = from_uri_param::<Query>(input.clone(), quote!(&'__r Self));
|
let from_ref = from_uri_param::<fmt::Query>(input.clone(), quote!(&'__r Self));
|
||||||
let from_mut = from_uri_param::<Query>(input.clone(), quote!(&'__r mut Self));
|
let from_mut = from_uri_param::<fmt::Query>(input.clone(), quote!(&'__r mut Self));
|
||||||
|
|
||||||
let mut ts = TokenStream::from(uri_display);
|
let mut ts = TokenStream::from(uri_display);
|
||||||
ts.extend(TokenStream::from(from_self));
|
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)]
|
#[allow(non_snake_case)]
|
||||||
pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
||||||
use crate::http::uri::Path;
|
const URI_DISPLAY: StaticTokens = quote_static!(#_fmt::UriDisplay<#_fmt::Path>);
|
||||||
|
const FORMATTER: StaticTokens = quote_static!(#_fmt::Formatter<#_fmt::Path>);
|
||||||
const URI_DISPLAY: StaticTokens = quote_static!(#_uri::UriDisplay<#_uri::Path>);
|
|
||||||
const FORMATTER: StaticTokens = quote_static!(#_uri::Formatter<#_uri::Path>);
|
|
||||||
|
|
||||||
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
let uri_display = DeriveGenerator::build_for(input.clone(), quote!(impl #URI_DISPLAY))
|
||||||
.support(Support::TupleStruct | Support::Type | Support::Lifetime)
|
.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()
|
Err(diag) => return diag.emit_as_item_tokens()
|
||||||
};
|
};
|
||||||
|
|
||||||
let from_self = from_uri_param::<Path>(input.clone(), quote!(Self));
|
let from_self = from_uri_param::<fmt::Path>(input.clone(), quote!(Self));
|
||||||
let from_ref = from_uri_param::<Path>(input.clone(), quote!(&'__r Self));
|
let from_ref = from_uri_param::<fmt::Path>(input.clone(), quote!(&'__r Self));
|
||||||
let from_mut = from_uri_param::<Path>(input.clone(), quote!(&'__r mut Self));
|
let from_mut = from_uri_param::<fmt::Path>(input.clone(), quote!(&'__r mut Self));
|
||||||
|
|
||||||
let mut ts = TokenStream::from(uri_display);
|
let mut ts = TokenStream::from(uri_display);
|
||||||
ts.extend(TokenStream::from(from_self));
|
ts.extend(TokenStream::from(from_self));
|
||||||
|
@ -141,10 +138,10 @@ pub fn derive_uri_display_path(input: proc_macro::TokenStream) -> TokenStream {
|
||||||
ts.into()
|
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 {
|
let part = match P::KIND {
|
||||||
uri::Kind::Path => quote!(#_uri::Path),
|
fmt::Kind::Path => quote!(#_fmt::Path),
|
||||||
uri::Kind::Query => quote!(#_uri::Query),
|
fmt::Kind::Query => quote!(#_fmt::Query),
|
||||||
};
|
};
|
||||||
|
|
||||||
let ty: syn::Type = syn::parse2(ty).expect("valid type");
|
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
|
_ => 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)
|
DeriveGenerator::build_for(input, param_trait)
|
||||||
.support(Support::All)
|
.support(Support::All)
|
||||||
.type_bound(quote!(#_uri::UriDisplay<#part>))
|
.type_bound(quote!(#_fmt::UriDisplay<#part>))
|
||||||
.inner_mapper(MapperBuild::new()
|
.inner_mapper(MapperBuild::new()
|
||||||
.with_output(move |_, _| quote! {
|
.with_output(move |_, _| quote! {
|
||||||
type Target = #ty;
|
type Target = #ty;
|
||||||
|
|
|
@ -76,6 +76,7 @@ define_exported_paths! {
|
||||||
_form => ::rocket::form::prelude,
|
_form => ::rocket::form::prelude,
|
||||||
_http => ::rocket::http,
|
_http => ::rocket::http,
|
||||||
_uri => ::rocket::http::uri,
|
_uri => ::rocket::http::uri,
|
||||||
|
_fmt => ::rocket::http::uri::fmt,
|
||||||
_Option => ::std::option::Option,
|
_Option => ::std::option::Option,
|
||||||
_Result => ::std::result::Result,
|
_Result => ::std::result::Result,
|
||||||
_Some => ::std::option::Option::Some,
|
_Some => ::std::option::Option::Some,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use quote::ToTokens;
|
use quote::ToTokens;
|
||||||
use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}};
|
use devise::{FromMeta, MetaItem, Result, ext::{Split2, PathExt, SpanDiagnosticExt}};
|
||||||
|
|
||||||
use crate::proc_macro2::TokenStream;
|
|
||||||
use crate::http;
|
use crate::http;
|
||||||
|
use crate::proc_macro2::{TokenStream, Span};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContentType(pub http::ContentType);
|
pub struct ContentType(pub http::ContentType);
|
||||||
|
@ -19,6 +19,21 @@ pub struct Method(pub http::Method);
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Optional<T>(pub Option<T>);
|
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 {
|
impl FromMeta for Status {
|
||||||
fn from_meta(meta: &MetaItem) -> Result<Self> {
|
fn from_meta(meta: &MetaItem) -> Result<Self> {
|
||||||
let num = usize::from_meta(meta)?;
|
let num = usize::from_meta(meta)?;
|
||||||
|
@ -145,3 +160,71 @@ impl<T: ToTokens> ToTokens for Optional<T> {
|
||||||
tokens.extend(opt_tokens);
|
tokens.extend(opt_tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Origin<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let (origin, span) = (self.0, self.1);
|
||||||
|
let origin = origin.clone().into_normalized();
|
||||||
|
define_spanned_export!(span => _uri);
|
||||||
|
|
||||||
|
let path = origin.path().as_str();
|
||||||
|
let query = Optional(origin.query().map(|q| q.as_str()));
|
||||||
|
tokens.extend(quote_spanned! { span =>
|
||||||
|
#_uri::Origin::const_new(#path, #query)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Absolute<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let (absolute, span) = (self.0, self.1);
|
||||||
|
define_spanned_export!(span => _uri);
|
||||||
|
let absolute = absolute.clone().into_normalized();
|
||||||
|
|
||||||
|
let scheme = absolute.scheme();
|
||||||
|
let auth = Optional(absolute.authority().map(|a| Authority(a, span)));
|
||||||
|
let path = absolute.path().as_str();
|
||||||
|
let query = Optional(absolute.query().map(|q| q.as_str()));
|
||||||
|
tokens.extend(quote_spanned! { span =>
|
||||||
|
#_uri::Absolute::const_new(#scheme, #auth, #path, #query)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Authority<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let (authority, span) = (self.0, self.1);
|
||||||
|
define_spanned_export!(span => _uri);
|
||||||
|
|
||||||
|
let user_info = Optional(authority.user_info());
|
||||||
|
let host = authority.host();
|
||||||
|
let port = Optional(authority.port());
|
||||||
|
tokens.extend(quote_spanned! { span =>
|
||||||
|
#_uri::Authority::const_new(#user_info, #host, #port)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Reference<'_> {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
let (reference, span) = (self.0, self.1);
|
||||||
|
define_spanned_export!(span => _uri);
|
||||||
|
let reference = reference.clone().into_normalized();
|
||||||
|
|
||||||
|
let scheme = Optional(reference.scheme());
|
||||||
|
let auth = Optional(reference.authority().map(|a| Authority(a, span)));
|
||||||
|
let path = reference.path().as_str();
|
||||||
|
let query = Optional(reference.query().map(|q| q.as_str()));
|
||||||
|
let frag = Optional(reference.fragment().map(|f| f.as_str()));
|
||||||
|
tokens.extend(quote_spanned! { span =>
|
||||||
|
#_uri::Reference::const_new(#scheme, #auth, #path, #query, #frag)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Asterisk {
|
||||||
|
fn to_tokens(&self, tokens: &mut TokenStream) {
|
||||||
|
define_spanned_export!(self.1 => _uri);
|
||||||
|
tokens.extend(quote_spanned!(self.1 => #_uri::Asterisk));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1028,77 +1028,221 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
||||||
emit!(bang::catchers_macro(input))
|
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
|
/// The `uri!` macro creates type-safe, URL-safe URIs given a route and concrete
|
||||||
/// for the route's URI parameters. The inputs to the macro are the path to a
|
/// parameters for its URI or a URI string literal.
|
||||||
/// route, a colon, and one argument for each dynamic parameter (parameters in
|
|
||||||
/// `<>`) in the route's path and query.
|
|
||||||
///
|
///
|
||||||
/// For example, for the following route:
|
/// # String Literal Parsing
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// Given a string literal as input, `uri!` parses the string using
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// [`Uri::parse_any()`] and emits a `'static`, `const` value whose type is one
|
||||||
/// #
|
/// of [`Asterisk`], [`Origin`], [`Authority`], [`Absolute`], or [`Reference`],
|
||||||
/// #[get("/person/<name>?<age>")]
|
/// reflecting the parsed value. If the type allows normalization, the value is
|
||||||
/// fn person(name: String, age: Option<u8>) -> String {
|
/// normalized before being emitted. Parse errors are caught and emitted at
|
||||||
/// # "".into() /*
|
/// 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
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// #
|
|
||||||
/// # #[get("/person/<name>?<age>")]
|
/// # #[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
|
/// // 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");
|
/// assert_eq!(mike.to_string(), "/person/Mike%20Smith?age=28");
|
||||||
///
|
///
|
||||||
/// // with named parameters, order irrelevant
|
/// // with named parameters, order irrelevant
|
||||||
/// let mike = uri!(person: name = "Mike", age = Some(28));
|
/// let mike = uri!(person(name = "Mike", age = Some(28)));
|
||||||
/// let mike = uri!(person: age = Some(28), name = "Mike");
|
/// let mike = uri!(person(age = Some(28), name = "Mike"));
|
||||||
/// assert_eq!(mike.to_string(), "/person/Mike?age=28");
|
/// 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`.
|
/// // with unnamed values, explicitly `None`.
|
||||||
/// let option: Option<u8> = None;
|
/// let mike = uri!(person("Mike", None::<u8>));
|
||||||
/// let mike = uri!(person: "Mike", option);
|
|
||||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
|
||||||
///
|
|
||||||
/// // with named values ignored
|
|
||||||
/// let mike = uri!(person: name = "Mike", age = _);
|
|
||||||
/// assert_eq!(mike.to_string(), "/person/Mike");
|
/// assert_eq!(mike.to_string(), "/person/Mike");
|
||||||
///
|
///
|
||||||
/// // with named values, explicitly `None`
|
/// // with named values, explicitly `None`
|
||||||
/// let option: Option<u8> = 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");
|
/// 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
|
/// ## Grammar
|
||||||
///
|
///
|
||||||
/// The grammar for the `uri!` macro is:
|
/// The grammar for this variant of the `uri!` macro is:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```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 := EXPR | '_'
|
||||||
///
|
///
|
||||||
/// EXPR := a valid Rust expression (examples: `foo()`, `12`, `"hey"`)
|
/// 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`)
|
/// 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
|
/// The returned value is that of the prefix (minus any query part) concatenated
|
||||||
/// supplied route interpolated with the given values. Note that `Origin`
|
/// with the route URI concatenated with the query (if the route has no query
|
||||||
/// implements `Into<Uri>` (and by extension, `TryInto<Uri>`), so it can be
|
/// part) and fragment parts of the suffix. The route URI is generated by
|
||||||
/// converted into a [`Uri`] using `.into()` as needed.
|
/// 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
|
/// Each route value is rendered in its appropriate place in the URI using the
|
||||||
/// invocation matches the type declared for the parameter in the given route,
|
/// [`UriDisplay`] implementation for the value's type. The `UriDisplay`
|
||||||
/// after conversion with [`FromUriParam`], or if a value is ignored using `_`
|
/// implementation ensures that the rendered value is URL-safe.
|
||||||
/// and the corresponding route type implements [`Ignorable`].
|
|
||||||
///
|
///
|
||||||
/// Each value passed into `uri!` is rendered in its appropriate place in the
|
/// A `uri!()` invocation allocated at-most once.
|
||||||
/// URI using the [`UriDisplay`] implementation for the value's type. The
|
|
||||||
/// `UriDisplay` implementation ensures that the rendered value is URI-safe.
|
|
||||||
///
|
///
|
||||||
/// If a mount-point is provided, the mount-point is prepended to the route's
|
/// ## Static Semantics
|
||||||
/// URI.
|
///
|
||||||
|
/// 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
|
/// ### Conversion
|
||||||
///
|
///
|
||||||
/// The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
/// The [`FromUriParam`] trait is used to typecheck and perform a conversion for
|
||||||
/// each value passed to `uri!`. If a `FromUriParam<P, S>` implementation exists
|
/// each value passed to `uri!`. If a `FromUriParam<P, S> for T` implementation
|
||||||
/// for a type `T` for part URI part `P`, then a value of type `S` can be used
|
/// exists for a type `T` for part URI part `P`, then a value of type `S` can be
|
||||||
/// in `uri!` macro for a route URI parameter declared with a type of `T` in
|
/// used in `uri!` macro for a route URI parameter declared with a type of `T`
|
||||||
/// part `P`. For example, the following implementation, provided by Rocket,
|
/// 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
|
/// allows an `&str` to be used in a `uri!` invocation for route URI parameters
|
||||||
/// declared as `String`:
|
/// declared as `String`:
|
||||||
///
|
///
|
||||||
/// ```rust,ignore
|
/// ```rust,ignore
|
||||||
/// impl<P: UriPart, 'a> FromUriParam<P, &'a str> for String { .. }
|
/// impl<P: Part, 'a> FromUriParam<P, &'a str> for String { .. }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// ### Ignorables
|
/// ### Ignorables
|
||||||
|
@ -1149,6 +1311,10 @@ pub fn catchers(input: TokenStream) -> TokenStream {
|
||||||
///
|
///
|
||||||
/// [`Uri`]: ../rocket/http/uri/enum.Uri.html
|
/// [`Uri`]: ../rocket/http/uri/enum.Uri.html
|
||||||
/// [`Origin`]: ../rocket/http/uri/struct.Origin.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
|
/// [`FromUriParam`]: ../rocket/http/uri/trait.FromUriParam.html
|
||||||
/// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html
|
/// [`UriDisplay`]: ../rocket/http/uri/trait.UriDisplay.html
|
||||||
/// [`Ignorable`]: ../rocket/http/uri/trait.Ignorable.html
|
/// [`Ignorable`]: ../rocket/http/uri/trait.Ignorable.html
|
||||||
|
|
|
@ -70,6 +70,13 @@ impl StringLit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl crate::syn::parse::Parse for StringLit {
|
||||||
|
fn parse(input: devise::syn::parse::ParseStream<'_>) -> devise::syn::Result<Self> {
|
||||||
|
let lit = input.parse::<crate::syn::LitStr>()?;
|
||||||
|
Ok(StringLit::new(lit.value(), lit.span()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl devise::FromMeta for StringLit {
|
impl devise::FromMeta for StringLit {
|
||||||
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
fn from_meta(meta: &devise::MetaItem) -> devise::Result<Self> {
|
||||||
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
|
Ok(StringLit::new(String::from_meta(meta)?, meta.value_span()))
|
||||||
|
|
|
@ -30,14 +30,14 @@ fn test_raw_ident() {
|
||||||
let response = client.get("/example?type=1").dispatch();
|
let response = client.get("/example?type=1").dispatch();
|
||||||
assert_eq!(response.into_string().unwrap(), "example is 1");
|
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");
|
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");
|
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");
|
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");
|
assert_eq!(uri_bare.to_string(), "/swap/1/2");
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use rocket::request::Request;
|
||||||
use rocket::http::ext::Normalize;
|
use rocket::http::ext::Normalize;
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::data::{self, Data, FromData};
|
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.
|
// Use all of the code generation available at once.
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ fn post1(
|
||||||
let string = format!("{}, {}, {}, {}, {}, {}",
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||||
sky, name, a, query.field, path.normalized_str(), simple.0);
|
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())
|
format!("({}) ({})", string, uri.to_string())
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,7 @@ fn post2(
|
||||||
let string = format!("{}, {}, {}, {}, {}, {}",
|
let string = format!("{}, {}, {}, {}, {}, {}",
|
||||||
sky, name, a, query.field, path.normalized_str(), simple.0);
|
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())
|
format!("({}) ({})", string, uri.to_string())
|
||||||
}
|
}
|
||||||
|
@ -307,7 +307,7 @@ struct PathString(String);
|
||||||
impl FromSegments<'_> for PathString {
|
impl FromSegments<'_> for PathString {
|
||||||
type Error = std::convert::Infallible;
|
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("/")))
|
Ok(PathString(segments.collect::<Vec<_>>().join("/")))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,16 +5,17 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rocket::http::CookieJar;
|
use rocket::http::CookieJar;
|
||||||
use rocket::http::uri::{FromUriParam, Query};
|
use rocket::http::uri::fmt::{FromUriParam, Query};
|
||||||
use rocket::form::{Form, error::{Errors, ErrorKind}};
|
use rocket::form::{Form, error::{Errors, ErrorKind}};
|
||||||
|
|
||||||
macro_rules! assert_uri_eq {
|
macro_rules! assert_uri_eq {
|
||||||
($($uri:expr => $expected:expr,)+) => {
|
($($uri:expr => $expected:expr,)+) => {
|
||||||
$(
|
$(
|
||||||
let actual = $uri;
|
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 {
|
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,
|
nickname: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[post("/")]
|
||||||
|
fn index() { }
|
||||||
|
|
||||||
#[post("/<id>")]
|
#[post("/<id>")]
|
||||||
fn simple(id: i32) { }
|
fn simple(id: i32) { }
|
||||||
|
|
||||||
|
@ -96,187 +100,244 @@ fn guarded_segments(cookies: &CookieJar<'_>, path: PathBuf, id: usize) { }
|
||||||
#[test]
|
#[test]
|
||||||
fn check_simple_unnamed() {
|
fn check_simple_unnamed() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple: 100) => "/100",
|
uri!(simple(100)) => "/100",
|
||||||
uri!(simple: -23) => "/-23",
|
uri!(simple(-23)) => "/-23",
|
||||||
uri!(unused_param: 1, 2) => "/1/2",
|
uri!(unused_param(1, 2)) => "/1/2",
|
||||||
}
|
}
|
||||||
|
|
||||||
// The "flipped" test ensures that the order of parameters depends on the
|
// The "flipped" test ensures that the order of parameters depends on the
|
||||||
// route's URI, not on the order in the function signature.
|
// route's URI, not on the order in the function signature.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: 100, "hello".to_string()) => "/100/hello",
|
uri!(simple2(100, "hello".to_string())) => "/100/hello",
|
||||||
uri!(simple2: 1349, "hey".to_string()) => "/1349/hey",
|
uri!(simple2(1349, "hey".to_string())) => "/1349/hey",
|
||||||
uri!(simple2_flipped: 100, "hello".to_string()) => "/100/hello",
|
uri!(simple2_flipped(100, "hello".to_string())) => "/100/hello",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that `.from_uri_param()` is called.
|
// Ensure that `.from_uri_param()` is called.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: 100, "hello") => "/100/hello",
|
uri!(simple2(100, "hello")) => "/100/hello",
|
||||||
uri!(simple2_flipped: 1349, "hey") => "/1349/hey",
|
uri!(simple2_flipped(1349, "hey")) => "/1349/hey",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the `UriDisplay` trait is being used.
|
// Ensure that the `UriDisplay` trait is being used.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: 100, "hello there") => "/100/hello%20there",
|
uri!(simple2(100, "hello there")) => "/100/hello%20there",
|
||||||
uri!(simple2_flipped: 100, "hello there") => "/100/hello%20there",
|
uri!(simple2_flipped(100, "hello there")) => "/100/hello%20there",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that query parameters are handled properly.
|
// Ensure that query parameters are handled properly.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple3: 100) => "/?id=100",
|
uri!(simple3(100)) => "/?id=100",
|
||||||
uri!(simple3: 1349) => "/?id=1349",
|
uri!(simple3(1349)) => "/?id=1349",
|
||||||
uri!(simple4: 100, "bob") => "/?id=100&name=bob",
|
uri!(simple4(100, "bob")) => "/?id=100&name=bob",
|
||||||
uri!(simple4: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
|
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(-2, "@M+s&OU=")) => "/?id=-2&name=@M%2Bs%26OU%3D",
|
||||||
uri!(simple4_flipped: 100, "bob") => "/?id=100&name=bob",
|
uri!(simple4_flipped(100, "bob")) => "/?id=100&name=bob",
|
||||||
uri!(simple4_flipped: 1349, "Bob Anderson") => "/?id=1349&name=Bob%20Anderson",
|
uri!(simple4_flipped(1349, "Bob Anderson")) => "/?id=1349&name=Bob%20Anderson",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_simple_named() {
|
fn check_simple_named() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple: id = 100) => "/100",
|
uri!(simple(id = 100)) => "/100",
|
||||||
uri!(simple: id = -23) => "/-23",
|
uri!(simple(id = -23)) => "/-23",
|
||||||
uri!(unused_param: used = 1, _unused = 2) => "/1/2",
|
uri!(unused_param(used = 1, _unused = 2)) => "/1/2",
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: id = 100, name = "hello".to_string()) => "/100/hello",
|
uri!(simple2(id = 100, name = "hello".to_string())) => "/100/hello",
|
||||||
uri!(simple2: name = "hi".to_string(), id = 123) => "/123/hi",
|
uri!(simple2(name = "hi".to_string(), id = 123)) => "/123/hi",
|
||||||
uri!(simple2_flipped: id = 1349, name = "hey".to_string()) => "/1349/hey",
|
uri!(simple2_flipped(id = 1349, name = "hey".to_string())) => "/1349/hey",
|
||||||
uri!(simple2_flipped: name = "hello".to_string(), id = 100) => "/100/hello",
|
uri!(simple2_flipped(name = "hello".to_string(), id = 100)) => "/100/hello",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that `.from_uri_param()` is called.
|
// Ensure that `.from_uri_param()` is called.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: id = 100, name = "hello") => "/100/hello",
|
uri!(simple2(id = 100, name = "hello")) => "/100/hello",
|
||||||
uri!(simple2: id = 100, name = "hi") => "/100/hi",
|
uri!(simple2(id = 100, name = "hi")) => "/100/hi",
|
||||||
uri!(simple2: id = 1349, name = "hey") => "/1349/hey",
|
uri!(simple2(id = 1349, name = "hey")) => "/1349/hey",
|
||||||
uri!(simple2: name = "hello", id = 100) => "/100/hello",
|
uri!(simple2(name = "hello", id = 100)) => "/100/hello",
|
||||||
uri!(simple2: name = "hi", id = 100) => "/100/hi",
|
uri!(simple2(name = "hi", id = 100)) => "/100/hi",
|
||||||
uri!(simple2_flipped: id = 1349, name = "hey") => "/1349/hey",
|
uri!(simple2_flipped(id = 1349, name = "hey")) => "/1349/hey",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the `UriDisplay` trait is being used.
|
// Ensure that the `UriDisplay` trait is being used.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: id = 100, name = "hello there") => "/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(name = "hello there", id = 100)) => "/100/hello%20there",
|
||||||
uri!(simple2_flipped: id = 100, name = "hello there") => "/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_flipped(name = "hello there", id = 100)) => "/100/hello%20there",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that query parameters are handled properly.
|
// Ensure that query parameters are handled properly.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple3: id = 100) => "/?id=100",
|
uri!(simple3(id = 100)) => "/?id=100",
|
||||||
uri!(simple3: id = 1349) => "/?id=1349",
|
uri!(simple3(id = 1349)) => "/?id=1349",
|
||||||
uri!(simple4: id = 100, name = "bob") => "/?id=100&name=bob",
|
uri!(simple4(id = 100, name = "bob")) => "/?id=100&name=bob",
|
||||||
uri!(simple4: id = 1349, name = "Bob A") => "/?id=1349&name=Bob%20A",
|
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(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(id = 1349, name = "Bob A")) => "/?id=1349&name=Bob%20A",
|
||||||
uri!(simple4_flipped: name = "Bob A", id = 1349) => "/?id=1349&name=Bob%20A",
|
uri!(simple4_flipped(name = "Bob A", id = 1349)) => "/?id=1349&name=Bob%20A",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_mount_point() {
|
fn check_route_prefix_suffix() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!("/mount", simple: 100) => "/mount/100",
|
uri!(index) => "/",
|
||||||
uri!("/mount", simple: id = 23) => "/mount/23",
|
uri!("/", index) => "/",
|
||||||
uri!("/another", simple: 100) => "/another/100",
|
uri!("/hi", index) => "/hi",
|
||||||
uri!("/another", simple: id = 23) => "/another/23",
|
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! {
|
assert_uri_eq! {
|
||||||
uri!("/a", simple2: 100, "hey") => "/a/100/hey",
|
uri!("http://rocket.rs", index) => "http://rocket.rs",
|
||||||
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://", 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]
|
#[test]
|
||||||
fn check_guards_ignored() {
|
fn check_guards_ignored() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!("/mount", guard_1: 100) => "/mount/100",
|
uri!("/mount", guard_1(100)) => "/mount/100",
|
||||||
uri!("/mount", guard_2: 2938, "boo") => "/mount/2938/boo",
|
uri!("/mount", guard_2(2938, "boo")) => "/mount/2938/boo",
|
||||||
uri!("/mount", guard_3: 340, "Bob") => "/mount/a/340/hi/Bob/hey",
|
uri!("/mount", guard_3(340, "Bob")) => "/mount/a/340/hi/Bob/hey",
|
||||||
uri!(guard_1: 100) => "/100",
|
uri!(guard_1(100)) => "/100",
|
||||||
uri!(guard_2: 2938, "boo") => "/2938/boo",
|
uri!(guard_2(2938, "boo")) => "/2938/boo",
|
||||||
uri!(guard_3: 340, "Bob") => "/a/340/hi/Bob/hey",
|
uri!(guard_3(340, "Bob")) => "/a/340/hi/Bob/hey",
|
||||||
uri!("/mount", guard_1: id = 100) => "/mount/100",
|
uri!("/mount", guard_1(id = 100)) => "/mount/100",
|
||||||
uri!("/mount", guard_2: id = 2938, name = "boo") => "/mount/2938/boo",
|
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!("/mount", guard_3(id = 340, name = "Bob")) => "/mount/a/340/hi/Bob/hey",
|
||||||
uri!(guard_1: id = 100) => "/100",
|
uri!(guard_1(id = 100)) => "/100",
|
||||||
uri!(guard_2: name = "boo", id = 2938) => "/2938/boo",
|
uri!(guard_2(name = "boo", id = 2938)) => "/2938/boo",
|
||||||
uri!(guard_3: name = "Bob", id = 340) => "/a/340/hi/Bob/hey",
|
uri!(guard_3(name = "Bob", id = 340)) => "/a/340/hi/Bob/hey",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_with_segments() {
|
fn check_with_segments() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(segments: PathBuf::from("one/two/three")) => "/a/one/two/three",
|
uri!(segments(PathBuf::from("one/two/three"))) => "/a/one/two/three",
|
||||||
uri!(segments: path = 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(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!("/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!(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(10, PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||||
uri!(param_and_segments: id = 10, path = PathBuf::from("a/b"))
|
uri!(param_and_segments(id = 10, path = PathBuf::from("a/b"))) => "/a/10/then/a/b",
|
||||||
=> "/a/10/then/a/b",
|
uri!(guarded_segments(10, 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!(guarded_segments: id = 10, path = PathBuf::from("a/b"))
|
|
||||||
=> "/a/10/then/a/b",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check the `from_uri_param()` conversions for `PathBuf`.
|
// Now check the `from_uri_param()` conversions for `PathBuf`.
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(segments: "one/two/three") => "/a/one/two/three",
|
uri!(segments("one/two/three")) => "/a/one/two/three",
|
||||||
uri!("/oh", segments: path = "one/two/three") => "/oh/a/one/two/three",
|
uri!("/", segments(path = "one/two/three")) => "/a/one/two/three",
|
||||||
uri!(segments: "one/ tw?o/") => "/a/one/%20tw%3Fo",
|
uri!("/oh", segments(path = "one/two/three")) => "/oh/a/one/two/three",
|
||||||
uri!(param_and_segments: id = 10, path = "a/b") => "/a/10/then/a/b",
|
uri!(segments("one/ tw?o/")) => "/a/one/%20tw%3Fo",
|
||||||
uri!(guarded_segments: 10, "a/b") => "/a/10/then/a/b",
|
uri!(param_and_segments(id = 10, path = "a/b")) => "/a/10/then/a/b",
|
||||||
uri!(guarded_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]
|
#[test]
|
||||||
fn check_complex() {
|
fn check_complex() {
|
||||||
assert_uri_eq! {
|
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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
"/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",
|
uri!(complex(foo = 3, name = "hi", r#type = "b",
|
||||||
query = &User { name: "A B C".into(), nickname: "a b".into() }) =>
|
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",
|
"/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() },
|
uri!(complex(query = &User { name: "A B C".into(), nickname: "a b".into() },
|
||||||
foo = 3, name = "hi", r#type = "b") =>
|
foo = 3, name = "hi", r#type = "b")) =>
|
||||||
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
|
"/name/hi?foo=3&type=10&type=b&name=A%20B%20C&nickname=a%20b",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure variables are correctly processed.
|
// Ensure variables are correctly processed.
|
||||||
let user = User { name: "Robert".into(), nickname: "Bob".into() };
|
let user = User { name: "Robert".into(), nickname: "Bob".into() };
|
||||||
assert_uri_eq! {
|
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",
|
"/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",
|
"/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",
|
"/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() };
|
let s2 = S2 { name: "Bob".into() };
|
||||||
|
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: 1, &S1("A".into()).0) => "/1/A",
|
uri!(simple2(1, &S1("A".into()).0)) => "/1/A",
|
||||||
uri!(simple2: 1, &mut 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, S1("A".into()).0)) => "/1/A",
|
||||||
uri!(simple2: 1, &S2 { name: "A".into() }.name) => "/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, &mut S2 { name: "A".into() }.name)) => "/1/A",
|
||||||
uri!(simple2: 1, 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, &s1.0)) => "/1/Bob",
|
||||||
uri!(simple2: 1, &s2.name) => "/1/Bob",
|
uri!(simple2(1, &s2.name)) => "/1/Bob",
|
||||||
uri!(simple2: 2, &s1.0) => "/2/Bob",
|
uri!(simple2(2, &s1.0)) => "/2/Bob",
|
||||||
uri!(simple2: 2, &s2.name) => "/2/Bob",
|
uri!(simple2(2, &s2.name)) => "/2/Bob",
|
||||||
uri!(simple2: 2, s1.0) => "/2/Bob",
|
uri!(simple2(2, s1.0)) => "/2/Bob",
|
||||||
uri!(simple2: 2, s2.name) => "/2/Bob",
|
uri!(simple2(2, s2.name)) => "/2/Bob",
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut s1 = S1("Bob".into());
|
let mut s1 = S1("Bob".into());
|
||||||
let mut s2 = S2 { name: "Bob".into() };
|
let mut s2 = S2 { name: "Bob".into() };
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple2: 1, &mut 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, S1("A".into()).0)) => "/1/A",
|
||||||
uri!(simple2: 1, &mut 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, S2 { name: "A".into() }.name)) => "/1/A",
|
||||||
uri!(simple2: 1, &mut s1.0) => "/1/Bob",
|
uri!(simple2(1, &mut s1.0)) => "/1/Bob",
|
||||||
uri!(simple2: 1, &mut s2.name) => "/1/Bob",
|
uri!(simple2(1, &mut s2.name)) => "/1/Bob",
|
||||||
uri!(simple2: 2, &mut s1.0) => "/2/Bob",
|
uri!(simple2(2, &mut s1.0)) => "/2/Bob",
|
||||||
uri!(simple2: 2, &mut s2.name) => "/2/Bob",
|
uri!(simple2(2, &mut s2.name)) => "/2/Bob",
|
||||||
uri!(simple2: 2, s1.0) => "/2/Bob",
|
uri!(simple2(2, s1.0)) => "/2/Bob",
|
||||||
uri!(simple2: 2, s2.name) => "/2/Bob",
|
uri!(simple2(2, s2.name)) => "/2/Bob",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_scoped() {
|
fn check_scoped() {
|
||||||
assert_uri_eq!{
|
assert_uri_eq!{
|
||||||
uri!(typed_uris::simple: 100) => "/typed_uris/100",
|
uri!(typed_uris::simple(100)) => "/typed_uris/100",
|
||||||
uri!(typed_uris::simple: id = 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::deeper::simple(100)) => "/typed_uris/deeper/100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,10 +397,10 @@ mod typed_uris {
|
||||||
#[test]
|
#[test]
|
||||||
fn check_simple_scoped() {
|
fn check_simple_scoped() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(simple: id = 100) => "/typed_uris/100",
|
uri!(simple(id = 100)) => "/typed_uris/100",
|
||||||
uri!(crate::simple: id = 100) => "/100",
|
uri!(crate::simple(id = 100)) => "/100",
|
||||||
uri!("/mount", crate::simple: id = 100) => "/mount/100",
|
uri!("/mount", crate::simple(id = 100)) => "/mount/100",
|
||||||
uri!(crate::simple2: id = 100, name = "hello") => "/100/hello",
|
uri!(crate::simple2(id = 100, name = "hello")) => "/100/hello",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -350,8 +411,8 @@ mod typed_uris {
|
||||||
#[test]
|
#[test]
|
||||||
fn check_deep_scoped() {
|
fn check_deep_scoped() {
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(super::simple: id = 100) => "/typed_uris/100",
|
uri!(super::simple(id = 100)) => "/typed_uris/100",
|
||||||
uri!(crate::simple: id = 100) => "/100",
|
uri!(crate::simple(id = 100)) => "/100",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,68 +437,68 @@ fn test_optional_uri_parameters() {
|
||||||
let mut some_10 = Some(10);
|
let mut some_10 = Some(10);
|
||||||
let mut third = Third { one: "hi there".into(), two: "a b".into() };
|
let mut third = Third { one: "hi there".into(), two: "a b".into() };
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = Some(10),
|
q1 = Some(10),
|
||||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() })
|
rest = Some(Third { one: "hi there".into(), two: "a b".into() }),
|
||||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = &10,
|
foo = &10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = Some(&10),
|
q1 = Some(&10),
|
||||||
rest = Some(&third)
|
rest = Some(&third),
|
||||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = &mut 10,
|
foo = &mut 10,
|
||||||
bar = &mut "hi there",
|
bar = &mut "hi there",
|
||||||
q1 = some_10.as_mut(),
|
q1 = some_10.as_mut(),
|
||||||
rest = Some(&mut third)
|
rest = Some(&mut third),
|
||||||
) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
)) => "/10/hi%20there?q1=10&one=hi%20there&two=a%20b",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = _,
|
q1 = _,
|
||||||
rest = Some(Third { one: "hi there".into(), two: "a b".into() })
|
rest = Some(Third { one: "hi there".into(), two: "a b".into() }),
|
||||||
) => "/10/hi%20there?one=hi%20there&two=a%20b",
|
)) => "/10/hi%20there?one=hi%20there&two=a%20b",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = Some(10),
|
q1 = Some(10),
|
||||||
rest = _
|
rest = _,
|
||||||
) => "/10/hi%20there?q1=10",
|
)) => "/10/hi%20there?q1=10",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = Err(ErrorKind::Missing.into()) as Result<usize, _>,
|
q1 = Err(ErrorKind::Missing.into()) as Result<usize, _>,
|
||||||
rest = _
|
rest = _,
|
||||||
) => "/10/hi%20there",
|
)) => "/10/hi%20there",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = None as Option<usize>,
|
q1 = None as Option<usize>,
|
||||||
rest = _
|
rest = _
|
||||||
) => "/10/hi%20there",
|
)) => "/10/hi%20there",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = _,
|
q1 = _,
|
||||||
rest = None as Option<Third<'_>>,
|
rest = None as Option<Third<'_>>,
|
||||||
) => "/10/hi%20there",
|
)) => "/10/hi%20there",
|
||||||
|
|
||||||
uri!(optionals:
|
uri!(optionals(
|
||||||
foo = 10,
|
foo = 10,
|
||||||
bar = &"hi there",
|
bar = &"hi there",
|
||||||
q1 = _,
|
q1 = _,
|
||||||
rest = _,
|
rest = _,
|
||||||
) => "/10/hi%20there",
|
)) => "/10/hi%20there",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,15 +506,15 @@ fn test_optional_uri_parameters() {
|
||||||
fn test_simple_ignored() {
|
fn test_simple_ignored() {
|
||||||
#[get("/<_>")] fn ignore_one() { }
|
#[get("/<_>")] fn ignore_one() { }
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(ignore_one: 100) => "/100",
|
uri!(ignore_one(100)) => "/100",
|
||||||
uri!(ignore_one: "hello") => "/hello",
|
uri!(ignore_one("hello")) => "/hello",
|
||||||
uri!(ignore_one: "cats r us") => "/cats%20r%20us",
|
uri!(ignore_one("cats r us")) => "/cats%20r%20us",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<_>/<_>")] fn ignore_two() { }
|
#[get("/<_>/<_>")] fn ignore_two() { }
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(ignore_two: 100, "boop") => "/100/boop",
|
uri!(ignore_two(100, "boop")) => "/100/boop",
|
||||||
uri!(ignore_two: &"hi", "bop") => "/hi/bop",
|
uri!(ignore_two(&"hi", "bop")) => "/hi/bop",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<_>/foo/<_>")] fn ignore_inner_two() { }
|
#[get("/<_>/foo/<_>")] fn ignore_inner_two() { }
|
||||||
|
@ -461,9 +522,9 @@ fn test_simple_ignored() {
|
||||||
#[get("/hey/hi/<_>/foo/<_>")] fn ignore_inner_two_b() { }
|
#[get("/hey/hi/<_>/foo/<_>")] fn ignore_inner_two_b() { }
|
||||||
|
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(ignore_inner_two: 100, "boop") => "/100/foo/boop",
|
uri!(ignore_inner_two(100, "boop")) => "/100/foo/boop",
|
||||||
uri!(ignore_inner_one_a: "!?") => "/hi/!%3F/foo",
|
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_b(&mut 5, "boo")) => "/hey/hi/5/foo/boo",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<_>/foo/<_>?hi")] fn ignore_with_q() { }
|
#[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) { }
|
#[get("/hi/<_>/foo/<_>?<hi>&<hey>")] fn ignore_with_q3(hi: &str, hey: &str) { }
|
||||||
|
|
||||||
assert_uri_eq! {
|
assert_uri_eq! {
|
||||||
uri!(ignore_with_q: 100, "boop") => "/100/foo/boop?hi",
|
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_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_q3(&mut 5, "boo", "hi b", "ho")) => "/hi/5/foo/boo?hi=hi%20b&hey=ho",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:45:23
|
--> $DIR/typed-uri-bad-type.rs:45:22
|
||||||
|
|
|
|
||||||
45 | uri!(simple: id = "hi");
|
45 | uri!(simple(id = "hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:47:18
|
--> $DIR/typed-uri-bad-type.rs:47:17
|
||||||
|
|
|
|
||||||
47 | uri!(simple: "hello");
|
47 | uri!(simple("hello"));
|
||||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:49:23
|
--> $DIR/typed-uri-bad-type.rs:49:22
|
||||||
|
|
|
|
||||||
49 | uri!(simple: id = 239239i64);
|
49 | uri!(simple(id = 239239i64));
|
||||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, i64>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:51:31
|
--> $DIR/typed-uri-bad-type.rs:51:30
|
||||||
|
|
|
|
||||||
51 | uri!(not_uri_display: 10, S);
|
51 | uri!(not_uri_display(10, S));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Path, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
|
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:26
|
--> $DIR/typed-uri-bad-type.rs:56:25
|
||||||
|
|
|
|
||||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
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`
|
| ^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<i32 as FromUriParam<P, &'x i32>>
|
<i32 as FromUriParam<P, &'x i32>>
|
||||||
<i32 as FromUriParam<P, &'x mut i32>>
|
<i32 as FromUriParam<P, &'x mut i32>>
|
||||||
<i32 as FromUriParam<P, 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`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not satisfied
|
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:43
|
--> $DIR/typed-uri-bad-type.rs:56:42
|
||||||
|
|
|
|
||||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
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`
|
| ^^^^^^^^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<std::string::String as FromUriParam<P, &'a str>>
|
<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 &'a str>>
|
||||||
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
||||||
and 2 others
|
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`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:58:20
|
--> $DIR/typed-uri-bad-type.rs:58:19
|
||||||
|
|
|
|
||||||
58 | uri!(simple_q: "hi");
|
58 | uri!(simple_q("hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<isize as FromUriParam<P, &'x isize>>
|
<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>>
|
<isize as FromUriParam<P, isize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:60:25
|
--> $DIR/typed-uri-bad-type.rs:60:24
|
||||||
|
|
|
|
||||||
60 | uri!(simple_q: id = "hi");
|
60 | uri!(simple_q(id = "hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<isize as FromUriParam<P, &'x isize>>
|
<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>>
|
<isize as FromUriParam<P, isize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:62:24
|
--> $DIR/typed-uri-bad-type.rs:62:23
|
||||||
|
|
|
|
||||||
62 | uri!(other_q: 100, S);
|
62 | uri!(other_q(100, S));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:64:26
|
--> $DIR/typed-uri-bad-type.rs:64:25
|
||||||
|
|
|
|
||||||
64 | uri!(other_q: rest = S, id = 100);
|
64 | uri!(other_q(rest = S, id = 100));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:66:26
|
--> $DIR/typed-uri-bad-type.rs:66:25
|
||||||
|
|
|
|
||||||
66 | uri!(other_q: rest = _, id = 100);
|
66 | uri!(other_q(rest = _, id = 100));
|
||||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||||
|
|
|
|
||||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||||
| ------------ required by this bound in `assert_ignorable`
|
| ------------ required by this bound in `assert_ignorable`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:68:34
|
--> $DIR/typed-uri-bad-type.rs:68:33
|
||||||
|
|
|
|
||||||
68 | uri!(other_q: rest = S, id = _);
|
68 | uri!(other_q(rest = S, id = _));
|
||||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||||
|
|
|
|
||||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||||
| ------------ required by this bound in `assert_ignorable`
|
| ------------ required by this bound in `assert_ignorable`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:68:26
|
--> $DIR/typed-uri-bad-type.rs:68:25
|
||||||
|
|
|
|
||||||
68 | uri!(other_q: rest = S, id = _);
|
68 | uri!(other_q(rest = S, id = _));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:77:40
|
||||||
|
|
|
||||||
|
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:77:15
|
||||||
|
|
|
||||||
|
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||||
|
| ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:78:33
|
||||||
|
|
|
||||||
|
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:78:15
|
||||||
|
|
|
||||||
|
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||||
|
| ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:81:25
|
||||||
|
|
|
||||||
|
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:81:37
|
||||||
|
|
|
||||||
|
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||||
|
| ^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Asterisk`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:82:25
|
||||||
|
|
|
||||||
|
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:82:37
|
||||||
|
|
|
||||||
|
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||||
|
| ^^^^^^^^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Origin<'_>`
|
||||||
|
|
|
@ -1,271 +1,279 @@
|
||||||
error: expected identifier
|
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
|
||||||
--> $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"));
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: route `ignored` has uri "/<_>"
|
= note: route `ignored` has uri "/<_>"
|
||||||
|
|
||||||
error: expected unnamed arguments due to ignored parameters
|
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: "/<_>"
|
= note: uri for route `ignored` ignores path parameters: "/<_>"
|
||||||
|
|
||||||
error: expected 1 parameter but 2 were supplied
|
error: route expects 1 parameter but 2 were supplied
|
||||||
--> $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));
|
||||||
| ^^^^^^
|
| ^^^^^^
|
||||||
|
|
|
|
||||||
= note: route `ignored` has uri "/<_>"
|
= note: route `ignored` has uri "/<_>"
|
||||||
|
|
||||||
error: path parameters cannot be ignored
|
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
|
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
|
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
|
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
|
= note: uri parameters are: id: i32, name: String
|
||||||
= help: missing parameter: `name`
|
= help: missing parameter: `name`
|
||||||
help: unknown parameter: `cookies`
|
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
|
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
|
= note: uri parameters are: id: i32, name: String
|
||||||
= help: missing parameter: `name`
|
= help: missing parameter: `name`
|
||||||
help: unknown parameter: `cookies`
|
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`
|
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
|
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
|
= note: uri parameters are: id: i32, name: String
|
||||||
= help: missing parameter: `id`
|
= help: missing parameter: `id`
|
||||||
|
|
||||||
error: invalid parameters for `has_two` route uri
|
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
|
= note: uri parameters are: id: i32, name: String
|
||||||
= help: missing parameter: `name`
|
= help: missing parameter: `name`
|
||||||
help: duplicate parameter: `id`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameter: `cookies`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameter: `cookies`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
= help: missing parameter: `id`
|
= help: missing parameter: `id`
|
||||||
help: unknown parameter: `name`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: duplicate parameter: `id`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: duplicate parameter: `id`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameters: `name`, `age`
|
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`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameters: `name`, `age`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameter: `name`
|
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
|
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
|
= note: uri parameters are: id: i32
|
||||||
help: unknown parameter: `name`
|
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
|
||||||
--> $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));
|
||||||
| ^^
|
| ^^
|
||||||
|
|
|
|
||||||
= note: route `has_two` has uri "/<id>?<name>"
|
= note: route `has_two` has uri "/<id>?<name>"
|
||||||
|
|
||||||
error: expected 2 parameters but 3 were supplied
|
error: route expects 2 parameters but 3 were supplied
|
||||||
--> $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"));
|
||||||
| ^^^^^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: route `has_two` has uri "/<id>?<name>"
|
= note: route `has_two` has uri "/<id>?<name>"
|
||||||
|
|
||||||
error: expected 1 parameter but 2 were supplied
|
error: route expects 1 parameter but 2 were supplied
|
||||||
--> $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));
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: route `has_one_guarded` has uri "/<id>"
|
= note: route `has_one_guarded` has uri "/<id>"
|
||||||
|
|
||||||
error: expected 1 parameter but 2 were supplied
|
error: route expects 1 parameter but 2 were supplied
|
||||||
--> $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, ));
|
||||||
| ^^^^^^^^^^^^
|
| ^^^^^^^^^^^^
|
||||||
|
|
|
|
||||||
= note: route `has_one` has uri "/<id>"
|
= note: route `has_one` has uri "/<id>"
|
||||||
|
|
||||||
error: expected 1 parameter but 2 were supplied
|
error: route expects 1 parameter but 2 were supplied
|
||||||
--> $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));
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
|
|
||||||
= note: route `has_one` has uri "/<id>"
|
= 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
|
--> $DIR/typed-uris-bad-params.rs:21:10
|
||||||
|
|
|
|
||||||
21 | uri!(has_one);
|
21 | uri!(has_one);
|
||||||
|
|
|
@ -1,65 +1,153 @@
|
||||||
error: named and unnamed parameters cannot be mixed
|
error: expected identifier
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
||||||
|
|
|
||||||
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
|
|
||||||
|
|
|
|
||||||
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
|
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:11:10
|
--> $DIR/typed-uris-invalid-syntax.rs:16:16
|
||||||
|
|
|
|
||||||
11 | uri!("/mount");
|
16 | uri!("mount", simple);
|
||||||
| ^^^^^^^^
|
| ^
|
||||||
|
|
||||||
error: unexpected end of input, expected identifier
|
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:12:5
|
--> $DIR/typed-uris-invalid-syntax.rs:17:16
|
||||||
|
|
|
|
||||||
12 | uri!("/mount",);
|
17 | uri!("mount", simple, "http://");
|
||||||
| ^^^^^^^^^^^^^^^^
|
| ^
|
||||||
|
|
|
||||||
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
|
||||||
|
|
||||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
error: URI suffix must contain only query and/or fragment
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
--> $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`
|
error: expected 1, 2, or 3 arguments, found 4
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:14:10
|
--> $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
|
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:15: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)
|
= 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
|
error: unexpected end of input, expected expression
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
--> $DIR/typed-uris-invalid-syntax.rs:28:22
|
||||||
|
|
|
|
||||||
16 | uri!(simple: id = );
|
28 | uri!(simple(id = ));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
| ^
|
||||||
|
|
||||||
|
error: invalid URI: unexpected EOF: expected some token at index 0
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:29:11
|
||||||
|
|
|
|
||||||
= note: this error originates in the macro `uri` (in Nightly builds, run with -Z macro-backtrace for more info)
|
29 | uri!("*", simple(10), "hi");
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:30:40
|
||||||
|
|
|
||||||
|
30 | uri!("some.host:8088", simple(10), "hi");
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: expected identifier
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:33:18
|
||||||
|
|
|
||||||
|
33 | uri!("/foo", "bar");
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: unexpected token
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:34:17
|
||||||
|
|
|
||||||
|
34 | uri!("/foo" ("bar"));
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: URI prefix cannot contain query part
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:35:10
|
||||||
|
|
|
||||||
|
35 | uri!("ftp:?", index);
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:36:25
|
||||||
|
|
|
||||||
|
36 | uri!("ftp:", index, "foo#bar");
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:37:25
|
||||||
|
|
|
||||||
|
37 | uri!("ftp:", index, "foo?bar");
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
error: route expects 2 parameters but 0 were supplied
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||||
|
|
|
||||||
|
13 | uri!(simple,);
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: route `simple` has uri "/<id>/<name>"
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/uri_display_type_errors.rs:6:13
|
--> $DIR/uri_display_type_errors.rs:6:13
|
||||||
|
|
|
|
||||||
6 | struct Bar1(BadType);
|
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
|
--> $DIR/uri_display_type_errors.rs:10:5
|
||||||
|
|
|
|
||||||
10 | field: BadType,
|
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
|
--> $DIR/uri_display_type_errors.rs:16:5
|
||||||
|
|
|
|
||||||
16 | bad: BadType,
|
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
|
--> $DIR/uri_display_type_errors.rs:21:11
|
||||||
|
|
|
|
||||||
21 | Inner(BadType),
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:27:9
|
||||||
|
|
|
|
||||||
27 | field: BadType,
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:35:9
|
||||||
|
|
|
|
||||||
35 | other: BadType,
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:40:12
|
||||||
|
|
|
|
||||||
40 | struct Baz(BadType);
|
40 | struct Baz(BadType);
|
||||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Path>` is not implemented for `BadType`
|
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Path>` is not implemented for `BadType`
|
||||||
|
|
|
|
||||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Path>` for `&BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Path>` for `&BadType`
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:45:23
|
--> $DIR/typed-uri-bad-type.rs:45:22
|
||||||
|
|
|
|
||||||
45 | uri!(simple: id = "hi");
|
45 | uri!(simple(id = "hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, &str>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:47:18
|
--> $DIR/typed-uri-bad-type.rs:47:17
|
||||||
|
|
|
|
||||||
47 | uri!(simple: "hello");
|
47 | uri!(simple("hello"));
|
||||||
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, &str>` is not implemented for `usize`
|
| ^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::Path, i64>` is not satisfied
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, i64>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:49:23
|
--> $DIR/typed-uri-bad-type.rs:49:22
|
||||||
|
|
|
|
||||||
49 | uri!(simple: id = 239239i64);
|
49 | uri!(simple(id = 239239i64));
|
||||||
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::Path, i64>` is not implemented for `usize`
|
| ^^^^^^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, i64>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<usize as FromUriParam<P, &'x mut usize>>
|
<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>>
|
<usize as FromUriParam<P, usize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Path, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Path, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:51:31
|
--> $DIR/typed-uri-bad-type.rs:51:30
|
||||||
|
|
|
|
||||||
51 | uri!(not_uri_display: 10, S);
|
51 | uri!(not_uri_display(10, S));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Path, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Path, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `i32: FromUriParam<rocket::http::uri::Path, std::option::Option<{integer}>>` is not satisfied
|
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:26
|
--> $DIR/typed-uri-bad-type.rs:56:25
|
||||||
|
|
|
|
||||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
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`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, std::option::Option<{integer}>>` is not implemented for `i32`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<i32 as FromUriParam<P, &'x i32>>
|
<i32 as FromUriParam<P, &'x i32>>
|
||||||
<i32 as FromUriParam<P, &'x mut i32>>
|
<i32 as FromUriParam<P, &'x mut i32>>
|
||||||
<i32 as FromUriParam<P, 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`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `std::string::String: FromUriParam<rocket::http::uri::Path, Result<_, _>>` is not satisfied
|
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:43
|
--> $DIR/typed-uri-bad-type.rs:56:42
|
||||||
|
|
|
|
||||||
56 | uri!(optionals: id = Some(10), name = Ok("bob".into()));
|
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`
|
| ^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, Result<_, _>>` is not implemented for `std::string::String`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<std::string::String as FromUriParam<P, &'a str>>
|
<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 &'a str>>
|
||||||
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
<std::string::String as FromUriParam<P, &'x mut std::string::String>>
|
||||||
and 2 others
|
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`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:58:20
|
--> $DIR/typed-uri-bad-type.rs:58:19
|
||||||
|
|
|
|
||||||
58 | uri!(simple_q: "hi");
|
58 | uri!(simple_q("hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<isize as FromUriParam<P, &'x isize>>
|
<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>>
|
<isize as FromUriParam<P, isize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::Query, &str>` is not satisfied
|
error[E0277]: the trait bound `isize: FromUriParam<rocket::http::uri::fmt::Query, &str>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:60:25
|
--> $DIR/typed-uri-bad-type.rs:60:24
|
||||||
|
|
|
|
||||||
60 | uri!(simple_q: id = "hi");
|
60 | uri!(simple_q(id = "hi"));
|
||||||
| ^^^^ the trait `FromUriParam<rocket::http::uri::Query, &str>` is not implemented for `isize`
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Query, &str>` is not implemented for `isize`
|
||||||
|
|
|
|
||||||
= help: the following implementations were found:
|
= help: the following implementations were found:
|
||||||
<isize as FromUriParam<P, &'x isize>>
|
<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>>
|
<isize as FromUriParam<P, isize>>
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:62:24
|
--> $DIR/typed-uri-bad-type.rs:62:23
|
||||||
|
|
|
|
||||||
62 | uri!(other_q: 100, S);
|
62 | uri!(other_q(100, S));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:64:26
|
--> $DIR/typed-uri-bad-type.rs:64:25
|
||||||
|
|
|
|
||||||
64 | uri!(other_q: rest = S, id = 100);
|
64 | uri!(other_q(rest = S, id = 100));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `S: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:66:26
|
--> $DIR/typed-uri-bad-type.rs:66:25
|
||||||
|
|
|
|
||||||
66 | uri!(other_q: rest = _, id = 100);
|
66 | uri!(other_q(rest = _, id = 100));
|
||||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `S`
|
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||||
|
|
|
|
||||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||||
| ------------ required by this bound in `assert_ignorable`
|
| ------------ required by this bound in `assert_ignorable`
|
||||||
|
|
||||||
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `usize: Ignorable<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:68:34
|
--> $DIR/typed-uri-bad-type.rs:68:33
|
||||||
|
|
|
|
||||||
68 | uri!(other_q: rest = S, id = _);
|
68 | uri!(other_q(rest = S, id = _));
|
||||||
| ^ the trait `Ignorable<rocket::http::uri::Query>` is not implemented for `usize`
|
| ^ the trait `Ignorable<rocket::http::uri::fmt::Query>` is not implemented for `usize`
|
||||||
|
|
|
|
||||||
::: $WORKSPACE/core/http/src/uri/uri_display.rs
|
::: $WORKSPACE/core/http/src/uri/fmt/uri_display.rs
|
||||||
|
|
|
|
||||||
| pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
| pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||||
| ------------ required by this bound in `assert_ignorable`
|
| ------------ required by this bound in `assert_ignorable`
|
||||||
|
|
||||||
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::Query, _>` is not satisfied
|
error[E0277]: the trait bound `S: FromUriParam<rocket::http::uri::fmt::Query, _>` is not satisfied
|
||||||
--> $DIR/typed-uri-bad-type.rs:68:26
|
--> $DIR/typed-uri-bad-type.rs:68:25
|
||||||
|
|
|
|
||||||
68 | uri!(other_q: rest = S, id = _);
|
68 | uri!(other_q(rest = S, id = _));
|
||||||
| ^ the trait `FromUriParam<rocket::http::uri::Query, _>` is not implemented for `S`
|
| ^ the trait `FromUriParam<rocket::http::uri::fmt::Query, _>` is not implemented for `S`
|
||||||
|
|
|
|
||||||
= note: required by `from_uri_param`
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:77:40
|
||||||
|
|
|
||||||
|
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Reference<'_>: ValidRoutePrefix` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:77:15
|
||||||
|
|
|
||||||
|
77 | uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||||
|
| ^^^^^^^^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Reference<'_>`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:78:33
|
||||||
|
|
|
||||||
|
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRoutePrefix` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:78:15
|
||||||
|
|
|
||||||
|
78 | uri!(uri!("*"), simple(id = "hi"));
|
||||||
|
| ^^^ the trait `ValidRoutePrefix` is not implemented for `rocket::http::uri::Asterisk`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:81:25
|
||||||
|
|
|
||||||
|
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Asterisk: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:81:37
|
||||||
|
|
|
||||||
|
81 | uri!(_, simple(id = "hi"), uri!("*"));
|
||||||
|
| ^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Asterisk`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `usize: FromUriParam<rocket::http::uri::fmt::Path, &str>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:82:25
|
||||||
|
|
|
||||||
|
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||||
|
| ^^^^ the trait `FromUriParam<rocket::http::uri::fmt::Path, &str>` is not implemented for `usize`
|
||||||
|
|
|
||||||
|
= help: the following implementations were found:
|
||||||
|
<usize as FromUriParam<P, &'x mut usize>>
|
||||||
|
<usize as FromUriParam<P, &'x usize>>
|
||||||
|
<usize as FromUriParam<P, usize>>
|
||||||
|
= note: required by `from_uri_param`
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `rocket::http::uri::Origin<'_>: ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not satisfied
|
||||||
|
--> $DIR/typed-uri-bad-type.rs:82:37
|
||||||
|
|
|
||||||
|
82 | uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||||
|
| ^^^^^^^^^^ the trait `ValidRouteSuffix<rocket::http::uri::Origin<'static>>` is not implemented for `rocket::http::uri::Origin<'_>`
|
||||||
|
|
|
@ -1,264 +1,271 @@
|
||||||
error: expected identifier
|
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 "/<_>"
|
--- 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
|
error: expected unnamed arguments due to ignored parameters
|
||||||
--- note: uri for route `ignored` ignores path 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 "/<_>"
|
--- 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
|
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
|
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
|
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
|
error: invalid parameters for `has_two` route uri
|
||||||
--- note: uri parameters are: id: i32, name: String
|
--- note: uri parameters are: id: i32, name: String
|
||||||
--- help: missing parameter: `name`
|
--- 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`
|
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
|
error: invalid parameters for `has_two` route uri
|
||||||
--- note: uri parameters are: id: i32, name: String
|
--- note: uri parameters are: id: i32, name: String
|
||||||
--- help: missing parameter: `name`
|
--- 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`
|
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`
|
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
|
error: invalid parameters for `has_two` route uri
|
||||||
--- note: uri parameters are: id: i32, name: String
|
--- note: uri parameters are: id: i32, name: String
|
||||||
--- help: missing parameter: `id`
|
--- 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
|
error: invalid parameters for `has_two` route uri
|
||||||
--- note: uri parameters are: id: i32, name: String
|
--- note: uri parameters are: id: i32, name: String
|
||||||
--- help: missing parameter: `name`
|
--- 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`
|
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
|
error: invalid parameters for `has_one_guarded` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one_guarded` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- note: uri parameters are: id: i32
|
||||||
--- help: missing parameter: `id`
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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
|
error: invalid parameters for `has_one` route uri
|
||||||
--- note: uri parameters are: id: i32
|
--- 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`
|
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>"
|
--- 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>"
|
--- 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>"
|
--- 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>"
|
--- 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>"
|
--- 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>"
|
--- note: route `has_one` has uri "/<id>"
|
||||||
--> $DIR/typed-uris-bad-params.rs:21:10
|
--> $DIR/typed-uris-bad-params.rs:21:10
|
||||||
|
|
|
|
||||||
|
|
|
@ -1,65 +1,152 @@
|
||||||
error: named and unnamed parameters cannot be mixed
|
error: expected identifier
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:7:18
|
--> $DIR/typed-uris-invalid-syntax.rs:10:28
|
||||||
|
|
|
||||||
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
|
|
||||||
|
|
|
|
||||||
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
|
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:11:10
|
--> $DIR/typed-uris-invalid-syntax.rs:16:10
|
||||||
|
|
|
|
||||||
11 | uri!("/mount");
|
16 | uri!("mount", simple);
|
||||||
| ^^^^^^^^
|
|
||||||
|
|
||||||
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);
|
|
||||||
| ^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
error: invalid mount point; mount points must be static, absolute URIs: `/example`
|
error: invalid URI: unexpected EOF: expected token ':' at index 5
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:14:10
|
--> $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
|
error: expected at least 1 argument, found none
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:15:5
|
--> $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)
|
= 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
|
error: unexpected end of input, expected expression
|
||||||
--> $DIR/typed-uris-invalid-syntax.rs:16:5
|
--> $DIR/typed-uris-invalid-syntax.rs:28:16
|
||||||
|
|
|
|
||||||
16 | uri!(simple: id = );
|
28 | uri!(simple(id = ));
|
||||||
| ^^^^^^^^^^^^^^^^^^^^
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: invalid URI: unexpected EOF: expected some token at index 0
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:29:10
|
||||||
|
|
|
|
||||||
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
29 | uri!("*", simple(10), "hi");
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:30:40
|
||||||
|
|
|
||||||
|
30 | uri!("some.host:8088", simple(10), "hi");
|
||||||
|
| ^^^^
|
||||||
|
|
||||||
|
error: expected identifier
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:33:18
|
||||||
|
|
|
||||||
|
33 | uri!("/foo", "bar");
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: unexpected token
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:34:17
|
||||||
|
|
|
||||||
|
34 | uri!("/foo" ("bar"));
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: URI prefix cannot contain query part
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:35:10
|
||||||
|
|
|
||||||
|
35 | uri!("ftp:?", index);
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:36:25
|
||||||
|
|
|
||||||
|
36 | uri!("ftp:", index, "foo#bar");
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
error: URI suffix must contain only query and/or fragment
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:37:25
|
||||||
|
|
|
||||||
|
37 | uri!("ftp:", index, "foo?bar");
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
||||||
|
error: route expects 2 parameters but 0 were supplied
|
||||||
|
--- note: route `simple` has uri "/<id>/<name>"
|
||||||
|
--> $DIR/typed-uris-invalid-syntax.rs:13:10
|
||||||
|
|
|
||||||
|
13 | uri!(simple,);
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
@ -1,61 +1,61 @@
|
||||||
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::Query>` is not satisfied
|
error[E0277]: the trait bound `BadType: UriDisplay<rocket::http::uri::fmt::Query>` is not satisfied
|
||||||
--> $DIR/uri_display_type_errors.rs:6:13
|
--> $DIR/uri_display_type_errors.rs:6:13
|
||||||
|
|
|
|
||||||
6 | struct Bar1(BadType);
|
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
|
--> $DIR/uri_display_type_errors.rs:10:5
|
||||||
|
|
|
|
||||||
10 | field: BadType,
|
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
|
--> $DIR/uri_display_type_errors.rs:16:5
|
||||||
|
|
|
|
||||||
16 | bad: BadType,
|
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
|
--> $DIR/uri_display_type_errors.rs:21:11
|
||||||
|
|
|
|
||||||
21 | Inner(BadType),
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:27:9
|
||||||
|
|
|
|
||||||
27 | field: BadType,
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:35:9
|
||||||
|
|
|
|
||||||
35 | other: BadType,
|
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: 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
|
--> $DIR/uri_display_type_errors.rs:40:12
|
||||||
|
|
|
|
||||||
40 | struct Baz(BadType);
|
40 | struct Baz(BadType);
|
||||||
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::Path>` is not implemented for `BadType`
|
| ^^^^^^^ the trait `UriDisplay<rocket::http::uri::fmt::Path>` is not implemented for `BadType`
|
||||||
|
|
|
|
||||||
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::Path>` for `&BadType`
|
= note: required because of the requirements on the impl of `UriDisplay<rocket::http::uri::fmt::Path>` for `&BadType`
|
||||||
|
|
|
@ -42,34 +42,42 @@ fn other_q(id: usize, rest: S) { }
|
||||||
fn optionals_q(id: Option<i32>, name: Result<String, Errors<'_>>) { }
|
fn optionals_q(id: Option<i32>, name: Result<String, Errors<'_>>) { }
|
||||||
|
|
||||||
fn main() {
|
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.
|
// 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.
|
// These are all okay.
|
||||||
uri!(optionals_q: _, _);
|
uri!(optionals_q(_, _));
|
||||||
uri!(optionals_q: id = Some(10), name = Some("Bob".to_string()));
|
uri!(optionals_q(id = Some(10), name = Some("Bob".to_string())));
|
||||||
uri!(optionals_q: _, Some("Bob".into()));
|
uri!(optionals_q(_, Some("Bob".into())));
|
||||||
uri!(optionals_q: id = _, name = _);
|
uri!(optionals_q(id = _, name = _));
|
||||||
|
|
||||||
|
// Invalid prefixes.
|
||||||
|
uri!(uri!("?foo#bar"), simple(id = "hi"));
|
||||||
|
uri!(uri!("*"), simple(id = "hi"));
|
||||||
|
|
||||||
|
// Invalid suffix.
|
||||||
|
uri!(_, simple(id = "hi"), uri!("*"));
|
||||||
|
uri!(_, simple(id = "hi"), uri!("/foo/bar"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,51 +19,52 @@ fn ignored() { }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
uri!(has_one);
|
uri!(has_one);
|
||||||
|
uri!(has_one());
|
||||||
|
|
||||||
uri!(has_one: 1, 23);
|
uri!(has_one(1, 23));
|
||||||
uri!(has_one: "Hello", 23, );
|
uri!(has_one("Hello", 23, ));
|
||||||
uri!(has_one_guarded: "hi", 100);
|
uri!(has_one_guarded("hi", 100));
|
||||||
|
|
||||||
uri!(has_two: 10, "hi", "there");
|
uri!(has_two(10, "hi", "there"));
|
||||||
uri!(has_two: 10);
|
uri!(has_two(10));
|
||||||
|
|
||||||
uri!(has_one: id = 100, name = "hi");
|
uri!(has_one(id = 100, name = "hi"));
|
||||||
|
|
||||||
uri!(has_one: name = 100, id = 100);
|
uri!(has_one(name = 100, id = 100));
|
||||||
|
|
||||||
uri!(has_one: name = 100, age = 50, id = 100);
|
uri!(has_one(name = 100, age = 50, id = 100));
|
||||||
|
|
||||||
uri!(has_one: name = 100, age = 50, id = 100, id = 50);
|
uri!(has_one(name = 100, age = 50, id = 100, id = 50));
|
||||||
|
|
||||||
uri!(has_one: id = 100, id = 100);
|
uri!(has_one(id = 100, id = 100));
|
||||||
|
|
||||||
uri!(has_one: id = 100, id = 100, );
|
uri!(has_one(id = 100, id = 100, ));
|
||||||
|
|
||||||
uri!(has_one: name = "hi");
|
uri!(has_one(name = "hi"));
|
||||||
|
|
||||||
uri!(has_one_guarded: cookies = "hi", id = 100);
|
uri!(has_one_guarded(cookies = "hi", id = 100));
|
||||||
|
|
||||||
uri!(has_one_guarded: id = 100, cookies = "hi");
|
uri!(has_one_guarded(id = 100, cookies = "hi"));
|
||||||
|
|
||||||
uri!(has_two: id = 100, id = 100, );
|
uri!(has_two(id = 100, id = 100, ));
|
||||||
|
|
||||||
uri!(has_two: name = "hi");
|
uri!(has_two(name = "hi"));
|
||||||
|
|
||||||
uri!(has_two: cookies = "hi", id = 100, id = 10, id = 10);
|
uri!(has_two(cookies = "hi", id = 100, id = 10, id = 10));
|
||||||
|
|
||||||
uri!(has_two: id = 100, cookies = "hi");
|
uri!(has_two(id = 100, cookies = "hi"));
|
||||||
|
|
||||||
uri!(optionals: id = _, name = "bob".into());
|
uri!(optionals(id = _, name = "bob".into()));
|
||||||
|
|
||||||
uri!(optionals: id = 10, name = _);
|
uri!(optionals(id = 10, name = _));
|
||||||
|
|
||||||
uri!(ignored: _);
|
uri!(ignored(_));
|
||||||
|
|
||||||
uri!(ignored: _ = 10);
|
uri!(ignored(_ = 10));
|
||||||
|
|
||||||
uri!(ignored: 10, 20);
|
uri!(ignored(10, 20));
|
||||||
|
|
||||||
uri!(ignored: num = 10);
|
uri!(ignored(num = 10));
|
||||||
|
|
||||||
uri!(ignored: 10, "10");
|
uri!(ignored(10, "10"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,38 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
fn index() { }
|
||||||
|
|
||||||
#[post("/<id>/<name>")]
|
#[post("/<id>/<name>")]
|
||||||
fn simple(id: i32, name: String) -> &'static str { "" }
|
fn simple(id: i32, name: String) -> &'static str { "" }
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
uri!(simple: id = 100, "Hello");
|
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!(simple:);
|
uri!(simple:);
|
||||||
uri!("/mount");
|
|
||||||
uri!("/mount",);
|
uri!("/mount",);
|
||||||
uri!("mount", simple);
|
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!("/mount/<id>", simple);
|
||||||
uri!();
|
uri!();
|
||||||
uri!(simple: id = );
|
uri!(simple: id = );
|
||||||
|
uri!(simple(id = ));
|
||||||
|
uri!("*", simple(10), "hi");
|
||||||
|
uri!("some.host:8088", simple(10), "hi");
|
||||||
|
uri!("?foo");
|
||||||
|
uri!("");
|
||||||
|
uri!("/foo", "bar");
|
||||||
|
uri!("/foo" ("bar"));
|
||||||
|
uri!("ftp:?", index);
|
||||||
|
uri!("ftp:", index, "foo#bar");
|
||||||
|
uri!("ftp:", index, "foo?bar");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[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 {
|
macro_rules! assert_uri_display_query {
|
||||||
($v:expr, $s:expr) => (
|
($v:expr, $s:expr) => (
|
||||||
|
|
|
@ -34,7 +34,7 @@ ref-cast = "1.0"
|
||||||
uncased = "0.9.6"
|
uncased = "0.9.6"
|
||||||
parking_lot = "0.11"
|
parking_lot = "0.11"
|
||||||
either = "1"
|
either = "1"
|
||||||
pear = "0.2.1"
|
pear = "0.2.3"
|
||||||
pin-project-lite = "0.2"
|
pin-project-lite = "0.2"
|
||||||
memchr = "2"
|
memchr = "2"
|
||||||
stable-pattern = "0.1"
|
stable-pattern = "0.1"
|
||||||
|
|
|
@ -164,17 +164,32 @@ impl<'a, T: ?Sized + ToOwned + 'a> Indexed<'a, T>
|
||||||
self.len() == 0
|
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
|
/// Retrieves the string `self` corresponds to. If `self` is derived from
|
||||||
/// indexes, the corresponding subslice of `source` is returned. Otherwise,
|
/// indexes, the corresponding subslice of `source` is returned. Otherwise,
|
||||||
/// the concrete string is returned.
|
/// the concrete string is returned.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if `self` is an indexed string and `string` is None.
|
/// Panics if `self` is an indexed string and `source` is None.
|
||||||
// pub fn to_source(&self, source: Option<&'a T>) -> &T {
|
|
||||||
pub fn from_cow_source<'s>(&'s self, source: &'s Option<Cow<'_, T>>) -> &'s T {
|
pub fn from_cow_source<'s>(&'s self, source: &'s Option<Cow<'_, T>>) -> &'s T {
|
||||||
if self.is_indexed() && source.is_none() {
|
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 {
|
match *self {
|
||||||
|
|
|
@ -18,6 +18,7 @@ pub struct Error<'a> {
|
||||||
pub(crate) index: usize,
|
pub(crate) index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
impl<'a> From<ParseError<RawInput<'a>>> for Error<'a> {
|
impl<'a> From<ParseError<RawInput<'a>>> for Error<'a> {
|
||||||
fn from(inner: ParseError<RawInput<'a>>) -> Self {
|
fn from(inner: ParseError<RawInput<'a>>) -> Self {
|
||||||
let expected = inner.error.map(|t| t.into(), |v| v.values.into());
|
let expected = inner.error.map(|t| t.into(), |v| v.values.into());
|
||||||
|
|
|
@ -4,9 +4,9 @@ pub(crate) mod tables;
|
||||||
|
|
||||||
#[cfg(test)] mod tests;
|
#[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;
|
pub use self::error::Error;
|
||||||
|
|
||||||
|
@ -24,10 +24,15 @@ pub fn origin_from_str(s: &str) -> Result<Origin<'_>, Error<'_>> {
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn authority_from_str(s: &str) -> Result<Authority<'_>, Error<'_>> {
|
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]
|
#[inline]
|
||||||
pub fn absolute_from_str(s: &str) -> Result<Absolute<'_>, Error<'_>> {
|
pub fn absolute_from_str(s: &str) -> Result<Absolute<'_>, Error<'_>> {
|
||||||
Ok(parse!(absolute_only: RawInput::new(s.as_bytes()))?)
|
Ok(parse!(absolute: RawInput::new(s.as_bytes()))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn reference_from_str(s: &str) -> Result<Reference<'_>, Error<'_>> {
|
||||||
|
Ok(parse!(reference: RawInput::new(s.as_bytes()))?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,67 +1,168 @@
|
||||||
use pear::parsers::*;
|
use pear::parsers::*;
|
||||||
use pear::input::{Extent, Rewind};
|
use pear::combinators::*;
|
||||||
use pear::macros::{parser, switch, parse_current_marker, parse_error, parse_try};
|
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::uri::{Uri, Origin, Authority, Absolute, Reference, Asterisk};
|
||||||
use crate::parse::uri::tables::{is_reg_name_char, is_pchar, is_qchar};
|
use crate::parse::uri::tables::*;
|
||||||
use crate::parse::uri::RawInput;
|
use crate::parse::uri::RawInput;
|
||||||
|
|
||||||
type Result<'a, T> = pear::input::Result<T, RawInput<'a>>;
|
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]
|
#[parser]
|
||||||
pub fn uri<'a>(input: &mut RawInput<'a>) -> Result<'a, Uri<'a>> {
|
pub fn uri<'a>(input: &mut RawInput<'a>) -> Result<'a, Uri<'a>> {
|
||||||
match input.items.len() {
|
// To resolve all ambiguities with preference, we might need to look at the
|
||||||
0 => parse_error!("empty URI")?,
|
// complete string twice: origin/ref, asterisk/ref, authority/absolute.
|
||||||
1 => switch! {
|
switch! {
|
||||||
eat(b'*') => Uri::Asterisk,
|
complete(|i| eat(i, b'*')) => Uri::Asterisk(Asterisk),
|
||||||
eat(b'/') => Uri::Origin(Origin::new::<_, &str>("/", None)),
|
origin@complete(origin) => Uri::Origin(origin),
|
||||||
eat(b'%') => parse_error!("'%' is not a valid URI")?,
|
authority@complete(authority) => Uri::Authority(authority),
|
||||||
_ => unsafe {
|
absolute@complete(absolute) => Uri::Absolute(absolute),
|
||||||
// the `is_reg_name_char` guarantees ASCII
|
_ => Uri::Reference(reference()?)
|
||||||
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()?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
pub fn origin<'a>(input: &mut RawInput<'a>) -> Result<'a, Origin<'a>> {
|
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]
|
#[parser]
|
||||||
fn path_and_query<'a, F, Q>(
|
pub fn authority<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
||||||
input: &mut RawInput<'a>,
|
let prefix = take_while(is_reg_name_char)?;
|
||||||
is_path_char: F,
|
let (user_info, host, port) = switch! {
|
||||||
is_query_char: Q
|
peek(b'[') if prefix.is_empty() => (None, host()?, port()?),
|
||||||
) -> Result<'a, Origin<'a>>
|
eat(b':') => {
|
||||||
where F: Fn(&u8) -> bool + Copy, Q: Fn(&u8) -> bool + Copy
|
let suffix = take_while(is_reg_name_char)?;
|
||||||
{
|
switch! {
|
||||||
let path = take_while(is_path_char)?;
|
peek(b':') => {
|
||||||
let query = parse_try!(eat(b'?') => take_while(is_query_char)?);
|
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() {
|
unsafe { Authority::raw(input.start.into(), user_info, host, port) }
|
||||||
parse_error!("expected path or query, found neither")?
|
}
|
||||||
} else {
|
|
||||||
// We know the string is ASCII because of the `is_char` checks above.
|
#[parser]
|
||||||
Ok(unsafe { Origin::raw(input.start.into(), path.into(), query.map(|q| q.into())) })
|
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]
|
#[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;
|
let mut port_num: u32 = 0;
|
||||||
for (b, i) in bytes.iter().rev().zip(&[1, 10, 100, 1000, 10000]) {
|
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;
|
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)?;
|
parse_error!("port out of range: {}", port_num)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(port_num as u16)
|
Some(port_num as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
fn port<'a>(input: &mut RawInput<'a>) -> Result<'a, u16> {
|
fn path<'a>(input: &mut RawInput<'a>) -> Result<'a, Extent<&'a [u8]>> {
|
||||||
let port = take_n_while(5, |c| c.is_ascii_digit())?;
|
take_while(is_pchar)?
|
||||||
port_from(&port)?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
fn authority<'a>(
|
fn query<'a>(input: &mut RawInput<'a>) -> Result<'a, Option<Extent<&'a [u8]>>> {
|
||||||
input: &mut RawInput<'a>,
|
parse_try!(eat(b'?') => take_while(is_qchar)?)
|
||||||
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) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[parser]
|
#[parser]
|
||||||
pub fn authority_only<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
fn fragment<'a>(input: &mut RawInput<'a>) -> Result<'a, Option<Extent<&'a [u8]>>> {
|
||||||
if let Uri::Authority(authority) = absolute_or_authority()? {
|
parse_try!(eat(b'#') => take_while(is_qchar)?)
|
||||||
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)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
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-form = absolute-URI
|
||||||
|
|
||||||
absolute-URI = scheme ":" hier-part [ "?" query ]
|
absolute-URI = scheme ":" hier-part [ "?" query ]
|
||||||
|
@ -60,6 +69,8 @@ path-empty = 0<pchar>
|
||||||
|
|
||||||
segment = *pchar
|
segment = *pchar
|
||||||
segment-nz = 1*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 / ":" / "@"
|
pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
|
||||||
|
|
||||||
|
|
|
@ -20,13 +20,16 @@ const fn char_table(sets: &[&[u8]]) -> [u8; 256] {
|
||||||
table
|
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'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'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'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'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'w', b'x', b'y', b'z'
|
||||||
b'8', b'9', b'-', b'.', b'_', b'~',
|
];
|
||||||
|
|
||||||
|
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] = &[
|
const PCT_ENCODED: &[u8] = &[
|
||||||
|
@ -38,8 +41,24 @@ const SUB_DELIMS: &[u8] = &[
|
||||||
b'!', b'$', b'&', b'\'', b'(', b')', b'*', b'+', b',', b';', b'='
|
b'!', b'$', b'&', b'\'', b'(', b')', b'*', b'+', b',', b';', b'='
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SCHEME_CHAR: [u8; 256] = char_table(&[
|
||||||
|
ALPHA, DIGIT, &[b'+', b'-', b'.']
|
||||||
|
]);
|
||||||
|
|
||||||
|
const UNRESERVED: [u8; 256] = char_table(&[
|
||||||
|
ALPHA, DIGIT, &[b'-', b'.', b'_', b'~']
|
||||||
|
]);
|
||||||
|
|
||||||
|
const REG_NAME_CHARS: [u8; 256] = char_table(&[
|
||||||
|
&UNRESERVED, PCT_ENCODED, SUB_DELIMS
|
||||||
|
]);
|
||||||
|
|
||||||
|
const USER_INFO_CHARS: [u8; 256] = char_table(&[
|
||||||
|
®_NAME_CHARS, &[b':']
|
||||||
|
]);
|
||||||
|
|
||||||
pub const PATH_CHARS: [u8; 256] = char_table(&[
|
pub const PATH_CHARS: [u8; 256] = char_table(&[
|
||||||
UNRESERVED, PCT_ENCODED, SUB_DELIMS, &[b':', b'@', b'/']
|
®_NAME_CHARS, &[b':', b'@', b'/']
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const QUERY_CHARS: [u8; 256] = char_table(&[
|
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'|'],
|
&[b'{', b'}', b'[', b']', b'\\', b'^', b'`', b'|'],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const REG_NAME_CHARS: [u8; 256] = char_table(&[
|
|
||||||
UNRESERVED, PCT_ENCODED, SUB_DELIMS
|
|
||||||
]);
|
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub const fn is_pchar(&c: &u8) -> bool { PATH_CHARS[c as usize] != 0 }
|
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)]
|
#[inline(always)]
|
||||||
pub const fn is_qchar(&c: &u8) -> bool { QUERY_CHARS[c as usize] != 0 }
|
pub const fn is_qchar(&c: &u8) -> bool { QUERY_CHARS[c as usize] != 0 }
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,15 @@
|
||||||
use crate::uri::{Uri, Origin, Authority, Absolute};
|
use crate::uri::{Origin, Authority, Absolute, Asterisk};
|
||||||
use crate::parse::uri::*;
|
use crate::parse::uri::*;
|
||||||
use crate::uri::Host::*;
|
|
||||||
|
|
||||||
macro_rules! assert_parse_eq {
|
macro_rules! assert_parse_eq {
|
||||||
($($from:expr => $to:expr),+) => (
|
($($from:expr => $to:expr),+ $(,)?) => (
|
||||||
$(
|
$(
|
||||||
let expected = $to.into();
|
let expected = $to;
|
||||||
match from_str($from) {
|
match from_str($from) {
|
||||||
Ok(output) => {
|
Ok(output) => {
|
||||||
if output != expected {
|
if output != expected {
|
||||||
println!("Failure on: {:?}", $from);
|
println!("Failure on: {:?}", $from);
|
||||||
assert_eq!(output, expected);
|
assert_eq!(output, expected, "{} != {}", output, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
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 {
|
macro_rules! assert_no_parse {
|
||||||
($($from:expr),+) => (
|
($($from:expr),+ $(,)?) => (
|
||||||
$(
|
$(
|
||||||
if let Ok(uri) = from_str($from) {
|
if let Ok(uri) = from_str($from) {
|
||||||
println!("{:?} parsed unexpectedly!", $from);
|
println!("{:?} parsed unexpectedly!", $from);
|
||||||
|
@ -38,7 +35,7 @@ macro_rules! assert_no_parse {
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! assert_parse {
|
macro_rules! assert_parse {
|
||||||
($($from:expr),+) => (
|
($($from:expr),+ $(,)?) => (
|
||||||
$(
|
$(
|
||||||
if let Err(e) = from_str($from) {
|
if let Err(e) = from_str($from) {
|
||||||
println!("{:?} failed to parse", $from);
|
println!("{:?} failed to parse", $from);
|
||||||
|
@ -46,12 +43,10 @@ macro_rules! assert_parse {
|
||||||
}
|
}
|
||||||
)+
|
)+
|
||||||
);
|
);
|
||||||
|
|
||||||
($($from:expr),+,) => (assert_parse!($($from),+))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! assert_displays_eq {
|
macro_rules! assert_displays_eq {
|
||||||
($($string:expr),+) => (
|
($($string:expr),+ $(,)?) => (
|
||||||
$(
|
$(
|
||||||
let string = $string.into();
|
let string = $string.into();
|
||||||
match from_str(string) {
|
match from_str(string) {
|
||||||
|
@ -75,20 +70,19 @@ macro_rules! assert_displays_eq {
|
||||||
($($string:expr),+,) => (assert_parse_eq!($($string),+))
|
($($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]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_assert_parse_eq() {
|
fn test_assert_parse_eq() {
|
||||||
assert_parse_eq!("*" => uri_origin("*", None));
|
assert_parse_eq!("*" => Origin::path_only("*"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_assert_parse_eq_consecutive() {
|
fn test_assert_parse_eq_consecutive() {
|
||||||
assert_parse_eq!("/" => uri_origin("/", None), "/" => Uri::Asterisk);
|
assert_parse_eq! {
|
||||||
|
"/" => Origin::ROOT,
|
||||||
|
"/" => Asterisk
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -103,13 +97,14 @@ fn bad_parses() {
|
||||||
"://z7:77777777777777777777777777777`77777777777",
|
"://z7:77777777777777777777777777777`77777777777",
|
||||||
|
|
||||||
// from #1621
|
// from #1621
|
||||||
"://@example.com/test",
|
":/",
|
||||||
"://example.com:/test",
|
|
||||||
"://@example.com:/test",
|
// almost URIs
|
||||||
"://example.com/test?",
|
":/",
|
||||||
"://example.com:/test?",
|
"://",
|
||||||
"://@example.com/test?",
|
"::",
|
||||||
"://@example.com:/test?"
|
":::",
|
||||||
|
"a://a::",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,144 +124,153 @@ fn test_parse_issue_924_samples() {
|
||||||
"/rocket/?query=%3E5",
|
"/rocket/?query=%3E5",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_no_parse!("/rocket/?query=>5", "/?#foo");
|
assert_no_parse!("/rocket/?query=>5");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn single_byte() {
|
fn single_byte() {
|
||||||
assert_parse_eq!(
|
assert_parse_eq!(
|
||||||
"*" => Uri::Asterisk,
|
"*" => Asterisk,
|
||||||
"/" => uri_origin("/", None),
|
"/" => Origin::ROOT,
|
||||||
"." => Authority::new(None, Raw("."), None),
|
"." => Authority::new(None, ".", None),
|
||||||
"_" => Authority::new(None, Raw("_"), None),
|
"_" => Authority::new(None, "_", None),
|
||||||
"1" => Authority::new(None, Raw("1"), None),
|
"1" => Authority::new(None, "1", None),
|
||||||
"b" => Authority::new(None, Raw("b"), 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]
|
#[test]
|
||||||
fn origin() {
|
fn origin() {
|
||||||
assert_parse_eq!(
|
assert_parse_eq!(
|
||||||
"/a/b/c" => uri_origin("/a/b/c", None),
|
"/a/b/c" => Origin::path_only("/a/b/c"),
|
||||||
"/a/b/c?" => uri_origin("/a/b/c", Some("")),
|
"//" => Origin::path_only("//"),
|
||||||
"/a/b/c?abc" => uri_origin("/a/b/c", Some("abc")),
|
"///" => Origin::path_only("///"),
|
||||||
"/a/b/c???" => uri_origin("/a/b/c", Some("??")),
|
"////" => Origin::path_only("////"),
|
||||||
"/a/b/c?a?b?" => uri_origin("/a/b/c", Some("a?b?")),
|
"/a/b/c?" => Origin::new("/a/b/c", Some("")),
|
||||||
"/a/b/c?a?b?/c" => uri_origin("/a/b/c", Some("a?b?/c")),
|
"/a/b/c?abc" => Origin::new("/a/b/c", Some("abc")),
|
||||||
"/?abc" => uri_origin("/", Some("abc")),
|
"/a/b/c???" => Origin::new("/a/b/c", Some("??")),
|
||||||
"/hi%20there?a=b&c=d" => uri_origin("/hi%20there", Some("a=b&c=d")),
|
"/a/b/c?a?b?" => Origin::new("/a/b/c", Some("a?b?")),
|
||||||
"/c/d/fa/b/c?abc" => uri_origin("/c/d/fa/b/c", Some("abc")),
|
"/a/b/c?a?b?/c" => Origin::new("/a/b/c", Some("a?b?/c")),
|
||||||
"/xn--ls8h?emoji=poop" => uri_origin("/xn--ls8h", Some("emoji=poop")),
|
"/?abc" => Origin::new("/", Some("abc")),
|
||||||
"/?t=[rocket|is\\here^`]&{ok}" => uri_origin("/", Some("t=[rocket|is\\here^`]&{ok}")),
|
"/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]
|
#[test]
|
||||||
fn authority() {
|
fn authority() {
|
||||||
assert_parse_eq!(
|
assert_parse_eq!(
|
||||||
"abc" => Authority::new(None, Raw("abc"), None),
|
"@:" => Authority::new("", "", 0),
|
||||||
"@abc" => Authority::new(Some(""), Raw("abc"), None),
|
"abc" => Authority::new(None, "abc", None),
|
||||||
"sergio:benitez@spark" => Authority::new(Some("sergio:benitez"), Raw("spark"), None),
|
"@abc" => Authority::new("", "abc", None),
|
||||||
"a:b:c@1.2.3:12121" => Authority::new(Some("a:b:c"), Raw("1.2.3"), Some(12121)),
|
"a@b" => Authority::new("a", "b", None),
|
||||||
"sergio@spark" => Authority::new(Some("sergio"), Raw("spark"), None),
|
"a@" => Authority::new("a", "", None),
|
||||||
"sergio@spark:230" => Authority::new(Some("sergio"), Raw("spark"), Some(230)),
|
":@" => Authority::new(":", "", None),
|
||||||
"sergio@[1::]:230" => Authority::new(Some("sergio"), Bracketed("1::"), Some(230)),
|
":@:" => Authority::new(":", "", 0),
|
||||||
"google.com:8000" => Authority::new(None, Raw("google.com"), Some(8000)),
|
"sergio:benitez@spark" => Authority::new("sergio:benitez", "spark", None),
|
||||||
"[1::2::3]:80" => Authority::new(None, Bracketed("1::2::3"), Some(80)),
|
"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]
|
#[test]
|
||||||
fn absolute() {
|
fn absolute() {
|
||||||
assert_parse_eq! {
|
assert_parse_eq! {
|
||||||
"http://foo.com:8000" => Absolute::new(
|
"http:/" => Absolute::new("http", None, "/", None),
|
||||||
"http",
|
"http://" => Absolute::new("http", Authority::new(None, "", None), "", None),
|
||||||
Some(Authority::new(None, Raw("foo.com"), Some(8000))),
|
"http:///" => Absolute::new("http", Authority::new(None, "", 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),
|
||||||
"http://foo:8000" => Absolute::new(
|
"foo:bar" => Absolute::new("foo", None, "bar", None),
|
||||||
"http",
|
"ftp:::" => Absolute::new("ftp", None, "::", None),
|
||||||
Some(Authority::new(None, Raw("foo"), Some(8000))),
|
"ftp:::?bar" => Absolute::new("ftp", None, "::", "bar"),
|
||||||
None,
|
"http://:::@a.b.c.:8000" =>
|
||||||
),
|
Absolute::new("http", Authority::new(":::", "a.b.c.", 8000), "", None),
|
||||||
"foo:bar" => Absolute::new(
|
"http://sergio:pass@foo.com:8000" =>
|
||||||
"foo",
|
Absolute::new("http", Authority::new("sergio:pass", "foo.com", 8000), "", None),
|
||||||
None,
|
"foo:/sergio/pass?hi" => Absolute::new("foo", None, "/sergio/pass", "hi"),
|
||||||
Some(Origin::new::<_, &str>("bar", None)),
|
"foo:?hi" => Absolute::new("foo", None, "", "hi"),
|
||||||
),
|
"foo:a/b" => Absolute::new("foo", None, "a/b", None),
|
||||||
"http://sergio:pass@foo.com:8000" => Absolute::new(
|
"foo:a/b?" => Absolute::new("foo", None, "a/b", ""),
|
||||||
"http",
|
"foo:a/b?hi" => Absolute::new("foo", None, "a/b", "hi"),
|
||||||
Some(Authority::new(Some("sergio:pass"), Raw("foo.com"), Some(8000))),
|
"foo:/a/b" => Absolute::new("foo", None, "/a/b", None),
|
||||||
None,
|
"abc://u:p@foo.com:123/a/b?key=value&key2=value2" =>
|
||||||
),
|
Absolute::new("abc",
|
||||||
"foo:/sergio/pass?hi" => Absolute::new(
|
Authority::new("u:p", "foo.com", 123),
|
||||||
"foo",
|
"/a/b", "key=value&key2=value2"),
|
||||||
None,
|
"ftp://foo.com:21/abc" =>
|
||||||
Some(Origin::new("/sergio/pass", Some("hi"))),
|
Absolute::new("ftp", Authority::new(None, "foo.com", 21), "/abc", None),
|
||||||
),
|
"http://rocket.rs/abc" =>
|
||||||
"bar:" => Absolute::new(
|
Absolute::new("http", Authority::new(None, "rocket.rs", None), "/abc", None),
|
||||||
"bar",
|
"http://s:b@rocket.rs/abc" =>
|
||||||
None,
|
Absolute::new("http", Authority::new("s:b", "rocket.rs", None), "/abc", None),
|
||||||
Some(Origin::new::<_, &str>("", None)),
|
"http://rocket.rs/abc?q" =>
|
||||||
),
|
Absolute::new("http", Authority::new(None, "rocket.rs", None), "/abc", "q"),
|
||||||
"foo:?hi" => Absolute::new(
|
"http://rocket.rs" =>
|
||||||
"foo",
|
Absolute::new("http", Authority::new(None, "rocket.rs", None), "", None),
|
||||||
None,
|
"git://s::@rocket.rs:443/abc?q" =>
|
||||||
Some(Origin::new("", Some("hi"))),
|
Absolute::new("git", Authority::new("s::", "rocket.rs", 443), "/abc", "q"),
|
||||||
),
|
"git://:@rocket.rs:443/abc?q" =>
|
||||||
"foo:a/b?hi" => Absolute::new(
|
Absolute::new("git", Authority::new(":", "rocket.rs", 443), "/abc", "q"),
|
||||||
"foo",
|
"a://b?test" => Absolute::new("a", Authority::new(None, "b", None), "", "test"),
|
||||||
None,
|
"a://b:?test" => Absolute::new("a", Authority::new(None, "b", 0), "", "test"),
|
||||||
Some(Origin::new("a/b", Some("hi"))),
|
"a://b:1?test" => Absolute::new("a", Authority::new(None, "b", 1), "", "test"),
|
||||||
),
|
|
||||||
"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"))),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn display() {
|
fn display() {
|
||||||
assert_displays_eq! {
|
assert_displays_eq! {
|
||||||
"abc", "@):0", "[a]"
|
"abc", "@):0", "[a]",
|
||||||
|
"http://rocket.rs", "http://a:b@rocket.rs", "git://a@b:800/foo?bar",
|
||||||
|
"git://a@b:800/foo?bar#baz",
|
||||||
|
"a:b", "a@b", "a?b", "a?b#c",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::fmt;
|
||||||
|
|
||||||
use ref_cast::RefCast;
|
use ref_cast::RefCast;
|
||||||
use stable_pattern::{Pattern, Searcher, ReverseSearcher, Split, SplitInternal};
|
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;
|
use crate::uncased::UncasedStr;
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::{self, Display};
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
use crate::ext::IntoOwned;
|
use crate::ext::IntoOwned;
|
||||||
use crate::parse::{Extent, IndexedStr};
|
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:
|
/// A URI with a scheme, authority, path, and query.
|
||||||
/// `http://user:pass@domain.com:4444/path?query`.
|
|
||||||
///
|
///
|
||||||
/// # Structure
|
/// # Structure
|
||||||
///
|
///
|
||||||
|
@ -14,19 +13,64 @@ use crate::uri::{Authority, Origin, Error, as_utf8_unchecked};
|
||||||
/// URI with all optional parts:
|
/// URI with all optional parts:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
/// http://user:pass@domain.com:4444/path?query
|
/// http://user:pass@domain.com:4444/foo/bar?some=query
|
||||||
/// |--| |-----------------------||---------|
|
/// |--| |------------------------||------| |--------|
|
||||||
/// scheme authority origin
|
/// scheme authority path query
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// The scheme part of the absolute URI and at least one of authority or origin
|
/// Only the scheme part of the URI is required.
|
||||||
/// are 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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Absolute<'a> {
|
pub struct Absolute<'a> {
|
||||||
source: Option<Cow<'a, str>>,
|
pub(crate) source: Option<Cow<'a, str>>,
|
||||||
scheme: IndexedStr<'a>,
|
pub(crate) scheme: IndexedStr<'a>,
|
||||||
authority: Option<Authority<'a>>,
|
pub(crate) authority: Option<Authority<'a>>,
|
||||||
origin: Option<Origin<'a>>,
|
pub(crate) path: Data<'a, fmt::Path>,
|
||||||
|
pub(crate) query: Option<Data<'a, fmt::Query>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoOwned for Absolute<'_> {
|
impl IntoOwned for Absolute<'_> {
|
||||||
|
@ -37,53 +81,35 @@ impl IntoOwned for Absolute<'_> {
|
||||||
source: self.source.into_owned(),
|
source: self.source.into_owned(),
|
||||||
scheme: self.scheme.into_owned(),
|
scheme: self.scheme.into_owned(),
|
||||||
authority: self.authority.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> {
|
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
|
/// Parses the string `string` into an `Absolute`. Parsing will never
|
||||||
/// allocate. Returns an `Error` if `string` is not a valid absolute URI.
|
/// allocate. Returns an `Error` if `string` is not a valid absolute URI.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Absolute;
|
/// use rocket::http::uri::Absolute;
|
||||||
///
|
///
|
||||||
/// // Parse a valid authority URI.
|
/// // Parse a valid authority URI.
|
||||||
/// let uri = Absolute::parse("http://google.com").expect("valid URI");
|
/// let uri = Absolute::parse("https://rocket.rs").expect("valid URI");
|
||||||
/// assert_eq!(uri.scheme(), "http");
|
/// assert_eq!(uri.scheme(), "https");
|
||||||
/// assert_eq!(uri.authority().unwrap().host(), "google.com");
|
/// assert_eq!(uri.authority().unwrap().host(), "rocket.rs");
|
||||||
/// assert_eq!(uri.origin(), None);
|
/// 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>> {
|
pub fn parse(string: &'a str) -> Result<Absolute<'a>, Error<'a>> {
|
||||||
crate::parse::uri::absolute_from_str(string)
|
crate::parse::uri::absolute_from_str(string)
|
||||||
|
@ -94,10 +120,8 @@ impl<'a> Absolute<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Absolute;
|
/// let uri = uri!("ftp://127.0.0.1");
|
||||||
///
|
|
||||||
/// let uri = Absolute::parse("ftp://127.0.0.1").expect("valid URI");
|
|
||||||
/// assert_eq!(uri.scheme(), "ftp");
|
/// assert_eq!(uri.scheme(), "ftp");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -110,16 +134,14 @@ impl<'a> Absolute<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Absolute;
|
/// let uri = uri!("https://rocket.rs:80");
|
||||||
///
|
|
||||||
/// let uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
|
||||||
/// assert_eq!(uri.scheme(), "https");
|
/// assert_eq!(uri.scheme(), "https");
|
||||||
/// let authority = uri.authority().unwrap();
|
/// let authority = uri.authority().unwrap();
|
||||||
/// assert_eq!(authority.host(), "rocket.rs");
|
/// assert_eq!(authority.host(), "rocket.rs");
|
||||||
/// assert_eq!(authority.port(), Some(80));
|
/// 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);
|
/// assert_eq!(uri.authority(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -127,50 +149,154 @@ impl<'a> Absolute<'a> {
|
||||||
self.authority.as_ref()
|
self.authority.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the origin part of the absolute URI, if there is one.
|
/// Returns the path part. May be empty.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Absolute;
|
/// 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");
|
/// let uri = uri!("ftp://rocket.rs");
|
||||||
/// assert_eq!(uri.scheme(), "file");
|
/// assert!(uri.path().is_empty());
|
||||||
/// 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);
|
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn origin(&self) -> Option<&Origin<'a>> {
|
pub fn path(&self) -> Path<'_> {
|
||||||
self.origin.as_ref()
|
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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::{Absolute, Authority};
|
/// 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 uri = uri!("ftp://rocket.rs");
|
||||||
/// let authority = uri.authority().unwrap();
|
/// assert!(uri.query().is_none());
|
||||||
/// 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);
|
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
|
pub fn query(&self) -> Option<Query<'_>> {
|
||||||
self.set_authority(authority);
|
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
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,18 +305,16 @@ impl<'a> Absolute<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::{Absolute, Authority};
|
/// let mut uri = uri!("https://rocket.rs:80");
|
||||||
///
|
|
||||||
/// let mut uri = Absolute::parse("https://rocket.rs:80").expect("valid URI");
|
|
||||||
/// let authority = uri.authority().unwrap();
|
/// let authority = uri.authority().unwrap();
|
||||||
/// assert_eq!(authority.host(), "rocket.rs");
|
/// assert_eq!(authority.host(), "rocket.rs");
|
||||||
/// assert_eq!(authority.port(), Some(80));
|
/// 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);
|
/// uri.set_authority(new_authority);
|
||||||
/// let authority = uri.authority().unwrap();
|
/// let authority = uri.authority().unwrap();
|
||||||
/// assert_eq!(authority.host(), "google.com");
|
/// assert_eq!(authority.host(), "rocket.rs");
|
||||||
/// assert_eq!(authority.port(), Some(443));
|
/// assert_eq!(authority.port(), Some(443));
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -198,53 +322,117 @@ impl<'a> Absolute<'a> {
|
||||||
self.authority = Some(authority);
|
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
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::{Absolute, Origin};
|
/// 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 new_authority = uri!("rocket.rs");
|
||||||
/// let origin = uri.origin().unwrap();
|
/// let uri = uri.with_authority(new_authority);
|
||||||
/// assert_eq!(origin.path(), "/web/");
|
/// let authority = uri.authority().unwrap();
|
||||||
/// assert_eq!(origin.query().unwrap(), "new");
|
/// assert_eq!(authority.host(), "rocket.rs");
|
||||||
///
|
/// assert_eq!(authority.port(), None);
|
||||||
/// 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);
|
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn with_origin(mut self, origin: Origin<'a>) -> Self {
|
pub fn with_authority(mut self, authority: Authority<'a>) -> Self {
|
||||||
self.set_origin(origin);
|
self.set_authority(authority);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the origin in `self` to `origin`.
|
/// PRIVATE API.
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<'a> Absolute<'a> {
|
||||||
|
/// PRIVATE. Used by parser.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// SAFETY: `source` must be valid UTF-8.
|
||||||
///
|
/// CORRECTNESS: `scheme` must be non-empty.
|
||||||
/// ```rust
|
#[inline]
|
||||||
/// # extern crate rocket;
|
pub(crate) unsafe fn raw(
|
||||||
/// use rocket::http::uri::{Absolute, Origin};
|
source: Cow<'a, [u8]>,
|
||||||
///
|
scheme: Extent<&'a [u8]>,
|
||||||
/// let mut uri = Absolute::parse("http://rocket.rs/web/?new").unwrap();
|
authority: Option<Authority<'a>>,
|
||||||
/// let origin = uri.origin().unwrap();
|
path: Extent<&'a [u8]>,
|
||||||
/// assert_eq!(origin.path(), "/web/");
|
query: Option<Extent<&'a [u8]>>,
|
||||||
/// assert_eq!(origin.query().unwrap(), "new");
|
) -> Absolute<'a> {
|
||||||
///
|
Absolute {
|
||||||
/// let new_origin = Origin::parse("/launch?when=now").unwrap();
|
source: Some(as_utf8_unchecked(source)),
|
||||||
/// uri.set_origin(new_origin);
|
scheme: scheme.into(),
|
||||||
/// let origin = uri.origin().unwrap();
|
authority,
|
||||||
/// assert_eq!(origin.path(), "/launch");
|
path: Data::raw(path),
|
||||||
/// assert_eq!(origin.query().unwrap(), "when=now");
|
query: query.map(Data::raw)
|
||||||
/// ```
|
}
|
||||||
#[inline(always)]
|
}
|
||||||
pub fn set_origin(&mut self, origin: Origin<'a>) {
|
|
||||||
self.origin = Some(origin);
|
/// 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 {
|
fn eq(&self, other: &Absolute<'b>) -> bool {
|
||||||
self.scheme() == other.scheme()
|
self.scheme() == other.scheme()
|
||||||
&& self.authority() == other.authority()
|
&& self.authority() == other.authority()
|
||||||
&& self.origin() == other.origin()
|
&& self.path() == other.path()
|
||||||
|
&& self.query() == other.query()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Absolute<'_> {
|
impl std::fmt::Display for Absolute<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.scheme())?;
|
write!(f, "{}:", self.scheme())?;
|
||||||
match self.authority {
|
if let Some(authority) = self.authority() {
|
||||||
Some(ref authority) => write!(f, "://{}", authority)?,
|
write!(f, "//{}", authority)?;
|
||||||
None => write!(f, ":")?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref origin) = self.origin {
|
write!(f, "{}", self.path())?;
|
||||||
write!(f, "{}", origin)?;
|
if let Some(query) = self.query() {
|
||||||
|
write!(f, "?{}", query)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/// The literal `*` URI.
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)]
|
||||||
|
pub struct Asterisk;
|
||||||
|
|
||||||
|
impl std::fmt::Display for Asterisk {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
"*".fmt(f)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,10 @@
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
|
use std::convert::TryFrom;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::ext::IntoOwned;
|
use crate::ext::IntoOwned;
|
||||||
use crate::parse::{Extent, IndexedStr};
|
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`.
|
/// 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.
|
/// Only the host part of the URI is required.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Authority<'a> {
|
pub struct Authority<'a> {
|
||||||
source: Option<Cow<'a, str>>,
|
pub(crate) source: Option<Cow<'a, str>>,
|
||||||
user_info: Option<IndexedStr<'a>>,
|
user_info: Option<IndexedStr<'a>>,
|
||||||
host: Host<IndexedStr<'a>>,
|
host: IndexedStr<'a>,
|
||||||
port: Option<u16>,
|
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<'_> {
|
impl IntoOwned for Authority<'_> {
|
||||||
type Owned = Authority<'static>;
|
type Owned = Authority<'static>;
|
||||||
|
|
||||||
|
@ -55,31 +42,42 @@ impl IntoOwned for Authority<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Authority<'a> {
|
impl<'a> Authority<'a> {
|
||||||
|
// SAFETY: `source` must be valid UTF-8.
|
||||||
|
// CORRECTNESS: `host` must be non-empty.
|
||||||
pub(crate) unsafe fn raw(
|
pub(crate) unsafe fn raw(
|
||||||
source: Cow<'a, [u8]>,
|
source: Cow<'a, [u8]>,
|
||||||
user_info: Option<Extent<&'a [u8]>>,
|
user_info: Option<Extent<&'a [u8]>>,
|
||||||
host: Host<Extent<&'a [u8]>>,
|
host: Extent<&'a [u8]>,
|
||||||
port: Option<u16>
|
port: Option<u16>
|
||||||
) -> Authority<'a> {
|
) -> Authority<'a> {
|
||||||
Authority {
|
Authority {
|
||||||
source: Some(as_utf8_unchecked(source)),
|
source: Some(as_utf8_unchecked(source)),
|
||||||
user_info: user_info.map(IndexedStr::from),
|
user_info: user_info.map(IndexedStr::from),
|
||||||
host: host.map_inner(IndexedStr::from),
|
host: IndexedStr::from(host),
|
||||||
port: port
|
port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn new(
|
pub fn new(
|
||||||
user_info: Option<&'a str>,
|
user_info: impl Into<Option<&'a str>>,
|
||||||
host: Host<&'a str>,
|
host: &'a str,
|
||||||
port: Option<u16>
|
port: impl Into<Option<u16>>,
|
||||||
) -> Authority<'a> {
|
) -> 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 {
|
Authority {
|
||||||
source: None,
|
source: None,
|
||||||
user_info: user_info.map(|u| Cow::Borrowed(u).into()),
|
user_info: match user_info {
|
||||||
host: host.map_inner(|inner| Cow::Borrowed(inner).into()),
|
Some(info) => Some(IndexedStr::Concrete(Cow::Borrowed(info))),
|
||||||
port: port
|
None => None
|
||||||
|
},
|
||||||
|
host: IndexedStr::Concrete(Cow::Borrowed(host)),
|
||||||
|
port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,7 +87,7 @@ impl<'a> Authority<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Authority;
|
/// use rocket::http::uri::Authority;
|
||||||
///
|
///
|
||||||
/// // Parse a valid authority URI.
|
/// // Parse a valid authority URI.
|
||||||
|
@ -99,7 +97,13 @@ impl<'a> Authority<'a> {
|
||||||
/// assert_eq!(uri.port(), None);
|
/// assert_eq!(uri.port(), None);
|
||||||
///
|
///
|
||||||
/// // Invalid authority URIs fail to parse.
|
/// // 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>> {
|
pub fn parse(string: &'a str) -> Result<Authority<'a>, Error<'a>> {
|
||||||
crate::parse::uri::authority_from_str(string)
|
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.
|
/// Returns the user info part of the authority URI, if there is one.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Authority;
|
/// let uri = uri!("username:password@host");
|
||||||
///
|
|
||||||
/// let uri = Authority::parse("username:password@host").unwrap();
|
|
||||||
/// assert_eq!(uri.user_info(), Some("username:password"));
|
/// assert_eq!(uri.user_info(), Some("username:password"));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn user_info(&self) -> Option<&str> {
|
pub fn user_info(&self) -> Option<&str> {
|
||||||
|
@ -127,23 +128,21 @@ impl<'a> Authority<'a> {
|
||||||
/// brackets will not be part of the returned string.
|
/// brackets will not be part of the returned string.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Authority;
|
|
||||||
///
|
///
|
||||||
/// let uri = Authority::parse("domain.com:123").unwrap();
|
/// let uri = uri!("domain.com:123");
|
||||||
/// assert_eq!(uri.host(), "domain.com");
|
/// 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");
|
/// assert_eq!(uri.host(), "host");
|
||||||
///
|
///
|
||||||
/// let uri = Authority::parse("username:password@[1::2]:123").unwrap();
|
/// let uri = uri!("username:password@[1::2]:123");
|
||||||
/// assert_eq!(uri.host(), "1::2");
|
/// assert_eq!(uri.host(), "[1::2]");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn host(&self) -> &str {
|
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.
|
/// Returns the port part of the authority URI, if there is one.
|
||||||
|
@ -151,18 +150,16 @@ impl<'a> Authority<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Authority;
|
|
||||||
///
|
|
||||||
/// // With a port.
|
/// // 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));
|
/// 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));
|
/// assert_eq!(uri.port(), Some(8181));
|
||||||
///
|
///
|
||||||
/// // Without a port.
|
/// // Without a port.
|
||||||
/// let uri = Authority::parse("username:password@host").unwrap();
|
/// let uri = uri!("username:password@host");
|
||||||
/// assert_eq!(uri.port(), None);
|
/// assert_eq!(uri.port(), None);
|
||||||
/// ```
|
/// ```
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -175,7 +172,6 @@ impl<'b> PartialEq<Authority<'b>> for Authority<'_> {
|
||||||
fn eq(&self, other: &Authority<'b>) -> bool {
|
fn eq(&self, other: &Authority<'b>) -> bool {
|
||||||
self.user_info() == other.user_info()
|
self.user_info() == other.user_info()
|
||||||
&& self.host() == other.host()
|
&& self.host() == other.host()
|
||||||
&& self.host.is_bracketed() == other.host.is_bracketed()
|
|
||||||
&& self.port() == other.port()
|
&& self.port() == other.port()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,11 +182,7 @@ impl Display for Authority<'_> {
|
||||||
write!(f, "{}@", user_info)?;
|
write!(f, "{}@", user_info)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.host {
|
self.host().fmt(f)?;
|
||||||
Host::Bracketed(_) => write!(f, "[{}]", self.host())?,
|
|
||||||
Host::Raw(_) => write!(f, "{}", self.host())?
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(port) = self.port {
|
if let Some(port) = self.port {
|
||||||
write!(f, ":{}", port)?;
|
write!(f, ":{}", port)?;
|
||||||
}
|
}
|
||||||
|
@ -199,29 +191,19 @@ impl Display for Authority<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Host<T> {
|
// Because inference doesn't take `&String` to `&str`.
|
||||||
#[inline]
|
impl<'a> TryFrom<&'a String> for Authority<'a> {
|
||||||
fn inner(&self) -> &T {
|
type Error = Error<'a>;
|
||||||
match *self {
|
|
||||||
Host::Bracketed(ref inner) | Host::Raw(ref inner) => inner
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
|
||||||
fn is_bracketed(&self) -> bool {
|
Authority::parse(value.as_str())
|
||||||
match *self {
|
}
|
||||||
Host::Bracketed(_) => true,
|
}
|
||||||
_ => false
|
|
||||||
}
|
impl<'a> TryFrom<&'a str> for Authority<'a> {
|
||||||
}
|
type Error = Error<'a>;
|
||||||
|
|
||||||
#[inline]
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
fn map_inner<F, U>(self, f: F) -> Host<U>
|
Authority::parse(value)
|
||||||
where F: FnOnce(T) -> U
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Host::Bracketed(inner) => Host::Bracketed(f(inner)),
|
|
||||||
Host::Raw(inner) => Host::Raw(f(inner))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
//! Errors arising from parsing invalid URIs.
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub use crate::parse::uri::Error;
|
||||||
|
|
||||||
|
/// The error type returned when a URI conversion fails.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct TryFromUriError(pub(crate) ());
|
||||||
|
|
||||||
|
impl fmt::Display for TryFromUriError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
"invalid conversion from general to specific URI variant".fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An error interpreting a segment as a [`PathBuf`] component in
|
||||||
|
/// [`Segments::to_path_buf()`].
|
||||||
|
///
|
||||||
|
/// [`PathBuf`]: std::path::PathBuf
|
||||||
|
/// [`Segments::to_path_buf()`]: crate::uri::Segments::to_path_buf()
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub enum PathError {
|
||||||
|
/// The segment started with the wrapped invalid character.
|
||||||
|
BadStart(char),
|
||||||
|
/// The segment contained the wrapped invalid character.
|
||||||
|
BadChar(char),
|
||||||
|
/// The segment ended with the wrapped invalid character.
|
||||||
|
BadEnd(char),
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ use std::borrow::Cow;
|
||||||
use percent_encoding::{AsciiSet, utf8_percent_encode};
|
use percent_encoding::{AsciiSet, utf8_percent_encode};
|
||||||
|
|
||||||
use crate::RawStr;
|
use crate::RawStr;
|
||||||
use crate::uri::{UriPart, Path, Query};
|
use crate::uri::fmt::{Part, Path, Query};
|
||||||
use crate::parse::uri::tables::PATH_CHARS;
|
use crate::parse::uri::tables::PATH_CHARS;
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[allow(non_camel_case_types)]
|
#[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 {
|
pub trait EncodeSet {
|
||||||
const SET: AsciiSet;
|
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);
|
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)]
|
#[inline(always)]
|
||||||
fn default() -> Self { UNSAFE_ENCODE_SET(PhantomData) }
|
fn default() -> Self { UNSAFE_ENCODE_SET(PhantomData) }
|
||||||
}
|
}
|
||||||
|
@ -51,7 +52,7 @@ impl EncodeSet for UNSAFE_ENCODE_SET<Query> {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
#[allow(non_camel_case_types)]
|
#[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> {
|
impl EncodeSet for ENCODE_SET<Path> {
|
||||||
const SET: AsciiSet = <UNSAFE_ENCODE_SET<Path>>::SET
|
const SET: AsciiSet = <UNSAFE_ENCODE_SET<Path>>::SET
|
|
@ -1,25 +1,23 @@
|
||||||
use std::fmt::{self, Write};
|
use std::fmt::{self, Write};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use smallvec::SmallVec;
|
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`].
|
/// A struct used to format strings for [`UriDisplay`].
|
||||||
///
|
///
|
||||||
/// # Marker Generic: `Formatter<Path>` vs. `Formatter<Query>`
|
/// # 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
|
/// either [`Path`] or [`Query`] resulting in either `Formatter<Path>` or
|
||||||
/// `Formatter<Query>`. The `Path` version is used when formatting parameters
|
/// `Formatter<Query>`. The `Path` version is used when formatting parameters
|
||||||
/// in the path part of the URI while the `Query` version is used when
|
/// in the path part of the URI while the `Query` version is used when
|
||||||
/// formatting parameters in the query part of the URI. The
|
/// formatting parameters in the query part of the URI. The
|
||||||
/// [`write_named_value()`] method is only available to `UriDisplay<Query>`.
|
/// [`write_named_value()`] method is only available to `UriDisplay<Query>`.
|
||||||
///
|
///
|
||||||
/// [`UriPart`]: crate::uri::UriPart
|
|
||||||
/// [`Path`]: crate::uri::Path
|
|
||||||
/// [`Query`]: crate::uri::Query
|
|
||||||
///
|
|
||||||
/// # Overview
|
/// # Overview
|
||||||
///
|
///
|
||||||
/// A mutable version of this struct is passed to [`UriDisplay::fmt()`]. This
|
/// 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 `.`,
|
/// calls `write_named_vlaue()`, the nested names are joined by a `.`,
|
||||||
/// written out followed by a `=`, followed by the value.
|
/// 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
|
||||||
///
|
///
|
||||||
/// Usage is fairly straightforward:
|
/// 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
|
/// called, after a call to `write_named_value` or `write_value`, or after a
|
||||||
/// call to [`refresh()`].
|
/// call to [`refresh()`].
|
||||||
///
|
///
|
||||||
/// [`refresh()`]: crate::uri::Formatter::refresh()
|
|
||||||
///
|
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// The following example uses all of the `write` methods in a varied order to
|
/// 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;
|
/// # extern crate rocket;
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
///
|
///
|
||||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query};
|
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query};
|
||||||
///
|
///
|
||||||
/// struct Outer {
|
/// struct Outer {
|
||||||
/// value: Inner,
|
/// value: Inner,
|
||||||
|
@ -105,7 +97,7 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
||||||
///
|
///
|
||||||
/// let inner = Inner { value: 0, extra: 1 };
|
/// let inner = Inner { value: 0, extra: 1 };
|
||||||
/// let outer = Outer { value: inner, another: 2, extra: 3 };
|
/// 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&\
|
/// assert_eq!(uri_string, "outer_field.inner_field=0&\
|
||||||
/// outer_field=1&\
|
/// outer_field=1&\
|
||||||
/// outer_field=inside&\
|
/// outer_field=inside&\
|
||||||
|
@ -123,17 +115,17 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use std::fmt::{self, Write};
|
/// 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);
|
/// 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 {
|
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||||
/// write!(f, "{}+{}", self.0, self.1)
|
/// 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");
|
/// assert_eq!(uri_string, "42+231");
|
||||||
///
|
///
|
||||||
/// #[derive(UriDisplayQuery)]
|
/// #[derive(UriDisplayQuery)]
|
||||||
|
@ -142,13 +134,15 @@ use crate::uri::{UriPart, Path, Query, UriDisplay, Origin, Kind};
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let message = Message { number: Complex(42, 47) };
|
/// 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");
|
/// assert_eq!(uri_string, "number=42+47");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`write_value()`]: crate::uri::Formatter::write_value()
|
/// [`write_named_value()`]: Formatter::write_value()
|
||||||
/// [`write_raw()`]: crate::uri::Formatter::write_raw()
|
/// [`write_value()`]: Formatter::write_value()
|
||||||
pub struct Formatter<'i, P: UriPart> {
|
/// [`write_raw()`]: Formatter::write_raw()
|
||||||
|
/// [`refresh()`]: Formatter::refresh()
|
||||||
|
pub struct Formatter<'i, P: Part> {
|
||||||
prefixes: SmallVec<[&'static str; 3]>,
|
prefixes: SmallVec<[&'static str; 3]>,
|
||||||
inner: &'i mut (dyn Write + 'i),
|
inner: &'i mut (dyn Write + 'i),
|
||||||
previous: bool,
|
previous: bool,
|
||||||
|
@ -156,7 +150,7 @@ pub struct Formatter<'i, P: UriPart> {
|
||||||
_marker: PhantomData<P>,
|
_marker: PhantomData<P>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'i, P: UriPart> Formatter<'i, P> {
|
impl<'i, P: Part> Formatter<'i, P> {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub(crate) fn new(inner: &'i mut (dyn Write + 'i)) -> Self {
|
pub(crate) fn new(inner: &'i mut (dyn Write + 'i)) -> Self {
|
||||||
Formatter {
|
Formatter {
|
||||||
|
@ -191,11 +185,11 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
///
|
///
|
||||||
/// use rocket::http::uri::{Formatter, UriDisplay, UriPart, Path};
|
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Part, Path};
|
||||||
///
|
///
|
||||||
/// struct Foo;
|
/// 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 {
|
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||||
/// f.write_raw("f")?;
|
/// f.write_raw("f")?;
|
||||||
/// f.write_raw("o")?;
|
/// f.write_raw("o")?;
|
||||||
|
@ -204,7 +198,7 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let foo = Foo;
|
/// 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");
|
/// assert_eq!(uri_string, "foo");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn write_raw<S: AsRef<str>>(&mut self, string: S) -> fmt::Result {
|
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;
|
/// # extern crate rocket;
|
||||||
/// use std::fmt;
|
/// 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);
|
/// 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 {
|
/// fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result {
|
||||||
/// f.write_value(&self.0)
|
/// f.write_value(&self.0)
|
||||||
/// }
|
/// }
|
||||||
|
@ -259,10 +253,10 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
||||||
///
|
///
|
||||||
/// let foo = Foo(123);
|
/// 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");
|
/// 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");
|
/// assert_eq!(uri_string, "123");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -283,7 +277,7 @@ impl<'i, P: UriPart> Formatter<'i, P> {
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
///
|
///
|
||||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query, Path};
|
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query, Path};
|
||||||
///
|
///
|
||||||
/// struct Foo;
|
/// 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");
|
/// 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 {
|
/// impl UriDisplay<Path> for Foo {
|
||||||
/// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
/// fn fmt(&self, f: &mut Formatter<Path>) -> fmt::Result {
|
||||||
/// f.write_raw("a")?;
|
/// 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");
|
/// 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)]
|
#[inline(always)]
|
||||||
pub fn refresh(&mut self) {
|
pub fn refresh(&mut self) {
|
||||||
|
@ -380,7 +374,7 @@ impl Formatter<'_, Query> {
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
///
|
///
|
||||||
/// use rocket::http::uri::{Formatter, UriDisplay, Query};
|
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, Query};
|
||||||
///
|
///
|
||||||
/// struct Foo {
|
/// struct Foo {
|
||||||
/// name: usize
|
/// name: usize
|
||||||
|
@ -395,7 +389,7 @@ impl Formatter<'_, Query> {
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let foo = Foo { name: 123 };
|
/// 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");
|
/// assert_eq!(uri_string, "name=123");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[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 {
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
self.write_raw(s)
|
self.write_raw(s)
|
||||||
}
|
}
|
||||||
|
@ -425,66 +419,221 @@ pub enum UriQueryArgument<'a> {
|
||||||
Value(&'a dyn UriDisplay<Query>)
|
Value(&'a dyn UriDisplay<Query>)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// No prefix at all.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct Void;
|
||||||
|
|
||||||
// Used by code generation.
|
// Used by code generation.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct UriArguments<'a> {
|
pub trait ValidRoutePrefix {
|
||||||
pub path: UriArgumentsKind<&'a [&'a dyn UriDisplay<Path>]>,
|
type Output;
|
||||||
pub query: Option<UriArgumentsKind<&'a [UriQueryArgument<'a>]>>,
|
|
||||||
|
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.
|
// Used by code generation.
|
||||||
impl UriArguments<'_> {
|
#[doc(hidden)]
|
||||||
#[doc(hidden)]
|
pub struct RouteUriBuilder {
|
||||||
pub fn into_origin(self) -> Origin<'static> {
|
pub path: Cow<'static, str>,
|
||||||
use std::borrow::Cow;
|
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::*};
|
use self::{UriArgumentsKind::*, UriQueryArgument::*};
|
||||||
|
|
||||||
let path: Cow<'static, str> = match self.path {
|
let path: Cow<'static, str> = match path_args {
|
||||||
Static(path) => path.into(),
|
Static(path) => path.into(),
|
||||||
Dynamic(args) => {
|
Dynamic(args) => {
|
||||||
let mut string = String::from("/");
|
let mut string = String::from("/");
|
||||||
{
|
let mut formatter = Formatter::<Path>::new(&mut string);
|
||||||
let mut formatter = Formatter::<Path>::new(&mut string);
|
for value in args {
|
||||||
for value in args {
|
let _ = formatter.write_value(value);
|
||||||
let _ = formatter.write_value(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
string.into()
|
string.into()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let query: Option<Cow<'_, str>> = self.query.and_then(|q| match q {
|
let query: Option<Cow<'_, str>> = match query_args {
|
||||||
Static(query) => Some(query.into()),
|
None => None,
|
||||||
Dynamic(args) if args.is_empty() => None,
|
Some(Static(query)) => Some(query.into()),
|
||||||
Dynamic(args) => {
|
Some(Dynamic(args)) => {
|
||||||
let mut string = String::new();
|
let mut string = String::new();
|
||||||
{
|
let mut f = Formatter::<Query>::new(&mut string);
|
||||||
let mut f = Formatter::<Query>::new(&mut string);
|
for arg in args {
|
||||||
for arg in args {
|
let _ = match arg {
|
||||||
let _ = match arg {
|
Raw(v) => f.write_raw(v),
|
||||||
Raw(v) => f.write_raw(v),
|
NameValue(n, v) => f.write_named_value(n, v),
|
||||||
NameValue(n, v) => f.write_named_value(n, v),
|
Value(v) => f.write_value(v),
|
||||||
Value(v) => f.write_value(v),
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match string.is_empty() {
|
(!string.is_empty()).then(|| string.into())
|
||||||
false => Some(string.into()),
|
|
||||||
true => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
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.
|
// See https://github.com/SergioBenitez/Rocket/issues/1534.
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod prefix_soundness_test {
|
mod prefix_soundness_test {
|
||||||
use crate::uri::{Formatter, Query, UriDisplay};
|
use crate::uri::fmt::{Formatter, UriDisplay, Query};
|
||||||
|
|
||||||
struct MyValue;
|
struct MyValue;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::path::{Path, PathBuf};
|
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.
|
/// 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
|
/// be automated. Rocket provides [`impl_from_uri_param_identity`] to generate
|
||||||
/// the _identity_ implementations automatically. For a type `T`, these are:
|
/// the _identity_ implementations automatically. For a type `T`, these are:
|
||||||
///
|
///
|
||||||
/// * `impl<P: UriPart> FromUriParam<P, T> for T`
|
/// * `impl<P: Part> FromUriParam<P, T> for T`
|
||||||
/// * `impl<'x, P: UriPart> FromUriParam<P, &'x T> for T`
|
/// * `impl<'x, P: Part> FromUriParam<P, &'x T> for T`
|
||||||
/// * `impl<'x, P: UriPart> FromUriParam<P, &'x mut T> for T`
|
/// * `impl<'x, P: Part> FromUriParam<P, &'x mut T> for T`
|
||||||
///
|
///
|
||||||
/// See [`impl_from_uri_param_identity`] for usage details.
|
/// See [`impl_from_uri_param_identity`] for usage details.
|
||||||
///
|
///
|
||||||
|
@ -40,10 +41,10 @@ use crate::uri::{self, UriPart, UriDisplay};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// # use rocket::http::uri::{FromUriParam, UriPart};
|
/// # use rocket::http::uri::fmt::{FromUriParam, Part};
|
||||||
/// # struct S;
|
/// # struct S;
|
||||||
/// # type String = 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;
|
/// type Target = &'a str;
|
||||||
/// # fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
|
/// # 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
|
/// 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,
|
/// 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
|
/// 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.
|
/// when a conversion is valid in either case.
|
||||||
///
|
///
|
||||||
/// This is typically only warranted for owned-value types with corresponding
|
/// 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;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
///
|
///
|
||||||
/// use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
|
/// use rocket::http::uri::fmt::{Formatter, UriDisplay, FromUriParam, Query};
|
||||||
///
|
///
|
||||||
/// #[derive(FromForm)]
|
/// #[derive(FromForm)]
|
||||||
/// struct User<'a> {
|
/// struct User<'a> {
|
||||||
|
@ -150,7 +151,7 @@ use crate::uri::{self, UriPart, UriDisplay};
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # use std::fmt;
|
/// # use std::fmt;
|
||||||
/// # use rocket::http::uri::{Formatter, UriDisplay, FromUriParam, Query};
|
/// # use rocket::http::uri::fmt::{Formatter, UriDisplay, FromUriParam, Query};
|
||||||
/// #
|
/// #
|
||||||
/// # #[derive(FromForm)]
|
/// # #[derive(FromForm)]
|
||||||
/// # struct User<'a> { name: &'a str, nickname: String, }
|
/// # struct User<'a> { name: &'a str, nickname: String, }
|
||||||
|
@ -172,22 +173,22 @@ use crate::uri::{self, UriPart, UriDisplay};
|
||||||
/// #[post("/<name>?<user..>")]
|
/// #[post("/<name>?<user..>")]
|
||||||
/// fn some_route(name: &str, user: 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.path(), "/hey");
|
||||||
/// assert_eq!(uri.query().unwrap(), "name=Robert%20Mike&nickname=Bob");
|
/// assert_eq!(uri.query().unwrap(), "name=Robert%20Mike&nickname=Bob");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`uri!`]: crate::uri
|
/// [`uri!`]: rocket::uri
|
||||||
/// [`UriDisplay`]: crate::uri::UriDisplay
|
/// [`FromUriParam::Target`]: crate::uri::fmt::FromUriParam::Target
|
||||||
/// [`FromUriParam::Target`]: crate::uri::FromUriParam::Target
|
/// [`Path`]: crate::uri::fmt::Path
|
||||||
/// [`Path`]: crate::uri::Path
|
/// [`Query`]: crate::uri::fmt::Query
|
||||||
pub trait FromUriParam<P: UriPart, T> {
|
pub trait FromUriParam<P: Part, T> {
|
||||||
/// The resulting type of this conversion.
|
/// The resulting type of this conversion.
|
||||||
type Target: UriDisplay<P>;
|
type Target: UriDisplay<P>;
|
||||||
|
|
||||||
/// Converts a value of type `T` into a value of type `Self::Target`. The
|
/// 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
|
/// 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;
|
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),*); );
|
($($A:ty => $B:ty),*) => ( impl_conversion_ref!(@_ $(() $A => $B),*); );
|
||||||
|
|
||||||
(@_ $(($($l:tt)*) $A:ty => $B:ty),*) => ($(
|
(@_ $(($($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),*) => ($(
|
($([$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] $A:ty => $B:ty),*) => ( impl_conversion_ref!($([$P] () $A => $B),*););
|
||||||
|
|
||||||
(@_ [$P:ty] ($($l:tt)*) $A:ty => $B:ty) => (
|
(@_ [$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;
|
type Target = $A;
|
||||||
#[inline(always)] fn from_uri_param(param: $A) -> $A { param }
|
#[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:
|
/// 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 T> for T`
|
||||||
/// * `impl<'x> FromUriParam<P, &'x mut T> for T`
|
/// * `impl<'x> FromUriParam<P, &'x mut T> for T`
|
||||||
///
|
///
|
||||||
/// where `P` is one of:
|
/// where `P` is one of:
|
||||||
///
|
///
|
||||||
/// * `P: UriPart` (the generic `P`)
|
/// * `P: Part` (the generic `P`)
|
||||||
/// * [`Path`]
|
/// * [`Path`]
|
||||||
/// * [`Query`]
|
/// * [`Query`]
|
||||||
///
|
///
|
||||||
|
@ -241,7 +242,7 @@ macro_rules! impl_conversion_ref {
|
||||||
/// Generates the three _identity_ implementations for the generic `P`.
|
/// Generates the three _identity_ implementations for the generic `P`.
|
||||||
///
|
///
|
||||||
/// * Example: `impl_from_uri_param_identity!(MyType);`
|
/// * 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);`
|
/// 2. `impl_from_uri_param_identity!((generics*) Type);`
|
||||||
///
|
///
|
||||||
|
@ -250,11 +251,11 @@ macro_rules! impl_conversion_ref {
|
||||||
/// implementation.
|
/// implementation.
|
||||||
///
|
///
|
||||||
/// * Example: `impl_from_uri_param_identity!(('a) MyType<'a>);`
|
/// * 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);`
|
/// 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`].
|
/// `Part`, where `Part` is a path to [`Path`] or [`Query`].
|
||||||
///
|
///
|
||||||
/// * Example: `impl_from_uri_param_identity!([Path] MyType);`
|
/// * 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>);`
|
/// * Example: `impl_from_uri_param_identity!([Path] ('a) MyType<'a>);`
|
||||||
/// * Generates: `impl<'a> FromUriParam<Path, _> for MyType<'a> { ... }`
|
/// * Generates: `impl<'a> FromUriParam<Path, _> for MyType<'a> { ... }`
|
||||||
///
|
///
|
||||||
/// [`FromUriParam`]: crate::uri::FromUriParam
|
/// [`FromUriParam`]: crate::uri::fmt::FromUriParam
|
||||||
/// [`Path`]: crate::uri::Path
|
/// [`Path`]: crate::uri::fmt::Path
|
||||||
/// [`Query`]: crate::uri::Query
|
/// [`Query`]: crate::uri::fmt::Query
|
||||||
#[macro_export(local_inner_macros)]
|
#[macro_export(local_inner_macros)]
|
||||||
macro_rules! impl_from_uri_param_identity {
|
macro_rules! impl_from_uri_param_identity {
|
||||||
($(($($l:tt)*) $T:ty),*) => ($( impl_conversion_ref!(($($l)*) $T => $T); )*);
|
($(($($l:tt)*) $T:ty),*) => ($( impl_conversion_ref!(($($l)*) $T => $T); )*);
|
||||||
|
@ -297,16 +298,16 @@ impl_conversion_ref! {
|
||||||
('a) String => &'a str
|
('a) String => &'a str
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_from_uri_param_identity!([uri::Path] ('a) &'a Path);
|
impl_from_uri_param_identity!([fmt::Path] ('a) &'a Path);
|
||||||
impl_from_uri_param_identity!([uri::Path] PathBuf);
|
impl_from_uri_param_identity!([fmt::Path] PathBuf);
|
||||||
|
|
||||||
impl_conversion_ref! {
|
impl_conversion_ref! {
|
||||||
[uri::Path] ('a) &'a Path => PathBuf,
|
[fmt::Path] ('a) &'a Path => PathBuf,
|
||||||
[uri::Path] ('a) PathBuf => &'a Path
|
[fmt::Path] ('a) PathBuf => &'a Path
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A no cost conversion allowing an `&str` to be used in place of a `PathBuf`.
|
/// 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;
|
type Target = &'a Path;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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`.
|
/// 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;
|
type Target = &'b Path;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>`.
|
/// 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;
|
type Target = T::Target;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>`.
|
/// 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;
|
type Target = T::Target;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>;
|
type Target = Option<T::Target>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>;
|
type Target = Option<T::Target>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>;
|
type Target = Result<T::Target, E>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[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>;
|
type Target = Result<T::Target, E>;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
|
@ -0,0 +1,14 @@
|
||||||
|
//! Type safe and URI safe formatting types and traits.
|
||||||
|
|
||||||
|
mod uri_display;
|
||||||
|
mod formatter;
|
||||||
|
mod from_uri_param;
|
||||||
|
mod encoding;
|
||||||
|
mod part;
|
||||||
|
|
||||||
|
pub use self::formatter::*;
|
||||||
|
pub use self::uri_display::*;
|
||||||
|
pub use self::from_uri_param::*;
|
||||||
|
pub use self::part::*;
|
||||||
|
|
||||||
|
pub(crate) use self::encoding::*;
|
|
@ -0,0 +1,84 @@
|
||||||
|
use crate::parse::IndexedStr;
|
||||||
|
|
||||||
|
/// Marker trait for types that mark a part of a URI.
|
||||||
|
///
|
||||||
|
/// This trait exists solely to categorize types that mark a part of the URI,
|
||||||
|
/// currently [`Path`] and [`Query`]. Said another way, types that implement
|
||||||
|
/// this trait are marker types that represent a part of a URI at the
|
||||||
|
/// type-level.
|
||||||
|
///
|
||||||
|
/// This trait is _sealed_: it cannot be implemented outside of Rocket.
|
||||||
|
///
|
||||||
|
/// # Usage
|
||||||
|
///
|
||||||
|
/// You will find this trait in traits like [`UriDisplay`] or structs like
|
||||||
|
/// [`Formatter`] as the bound on a generic parameter: `P: Part`. Because the
|
||||||
|
/// trait is sealed, the generic type is guaranteed to be instantiated as one of
|
||||||
|
/// [`Query`] or [`Path`], effectively creating two instances of the generic
|
||||||
|
/// items: `UriDisplay<Query>` and `UriDisplay<Path>`, and `Formatter<Query>`
|
||||||
|
/// and `Formatter<Path>`. Unlike having two distinct, non-generic traits, this
|
||||||
|
/// approach enables succinct, type-checked generic implementations of these
|
||||||
|
/// items.
|
||||||
|
///
|
||||||
|
/// [`UriDisplay`]: crate::uri::fmt::UriDisplay
|
||||||
|
/// [`Formatter`]: crate::uri::fmt::Formatter
|
||||||
|
pub trait Part: private::Sealed {
|
||||||
|
/// The dynamic version of `Self`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
const KIND: Kind;
|
||||||
|
|
||||||
|
/// The delimiter used to separate components of this URI part.
|
||||||
|
/// Specifically, `/` for `Path` and `&` for `Query`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
const DELIMITER: char;
|
||||||
|
|
||||||
|
/// The raw form of a segment in this part.
|
||||||
|
#[doc(hidden)]
|
||||||
|
type Raw: Send + Sync + 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod private {
|
||||||
|
pub trait Sealed {}
|
||||||
|
impl Sealed for super::Path {}
|
||||||
|
impl Sealed for super::Query {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Dynamic version of the `Path` and `Query` parts.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Kind { Path, Query }
|
||||||
|
|
||||||
|
/// Marker type indicating use of a type for the path [`Part`] of a URI.
|
||||||
|
///
|
||||||
|
/// In route URIs, this corresponds to all of the text before a `?`, if any, or
|
||||||
|
/// all of the text in the URI otherwise:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// #[get("/home/<name>/<page>?<item>")]
|
||||||
|
/// ^------------------ Path
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Path { }
|
||||||
|
|
||||||
|
/// Marker type indicating use of a type for the query [`Part`] of a URI.
|
||||||
|
///
|
||||||
|
/// In route URIs, this corresponds to all of the text after a `?`, if any.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// #[get("/home/<name>/<page>?<item>&<form..>")]
|
||||||
|
/// ^-------------- Query
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum Query { }
|
||||||
|
|
||||||
|
impl Part for Path {
|
||||||
|
const KIND: Kind = Kind::Path;
|
||||||
|
const DELIMITER: char = '/';
|
||||||
|
type Raw = IndexedStr<'static>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Part for Query {
|
||||||
|
const KIND: Kind = Kind::Query;
|
||||||
|
const DELIMITER: char = '&';
|
||||||
|
type Raw = (IndexedStr<'static>, IndexedStr<'static>);
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
use std::{fmt, path};
|
use std::{fmt, path};
|
||||||
use std::borrow::Cow;
|
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
|
/// Trait implemented by types that can be displayed as part of a URI in
|
||||||
/// [`uri!`].
|
/// [`uri!`].
|
||||||
|
@ -14,8 +15,8 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
///
|
///
|
||||||
/// # Marker Generic: `Path`, `Query`
|
/// # Marker Generic: `Path`, `Query`
|
||||||
///
|
///
|
||||||
/// The [`UriPart`] parameter `P` in `UriDisplay<P>` must be either [`Path`] or
|
/// The [`Part`] parameter `P` in `UriDisplay<P>` must be either [`Path`] or
|
||||||
/// [`Query`] (see the [`UriPart`] documentation for how this is enforced),
|
/// [`Query`] (see the [`Part`] documentation for how this is enforced),
|
||||||
/// resulting in either `UriDisplay<Path>` or `UriDisplay<Query>`.
|
/// resulting in either `UriDisplay<Path>` or `UriDisplay<Query>`.
|
||||||
///
|
///
|
||||||
/// As the names might imply, the `Path` version of the trait is used when
|
/// 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
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// # use std::fmt;
|
/// # use std::fmt;
|
||||||
/// # use rocket::http::uri::{UriPart, UriDisplay, Formatter};
|
/// # use rocket::http::uri::fmt::{Part, UriDisplay, Formatter};
|
||||||
/// # struct SomeType;
|
/// # 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(()) } }
|
/// # { fn fmt(&self, f: &mut Formatter<P>) -> fmt::Result { Ok(()) } }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// [`UriPart`]: crate::uri::UriPart
|
|
||||||
/// [`Path`]: crate::uri::Path
|
|
||||||
/// [`Query`]: crate::uri::Query
|
|
||||||
///
|
|
||||||
/// # Code Generation
|
/// # Code Generation
|
||||||
///
|
///
|
||||||
/// When the [`uri!`] macro is used to generate a URI for a route, the types for
|
/// 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>) { /* .. */ }
|
/// # fn get_item(id: i32, track: Option<String>) { /* .. */ }
|
||||||
/// #
|
/// #
|
||||||
/// // With unnamed parameters.
|
/// // With unnamed parameters.
|
||||||
/// uri!(get_item: 100, Some("inbound"));
|
/// uri!(get_item(100, Some("inbound")));
|
||||||
///
|
///
|
||||||
/// // With named parameters.
|
/// // With named parameters.
|
||||||
/// uri!(get_item: id = 100, track = Some("inbound"));
|
/// uri!(get_item(id = 100, track = Some("inbound")));
|
||||||
/// uri!(get_item: track = Some("inbound"), id = 100);
|
/// uri!(get_item(track = Some("inbound"), id = 100));
|
||||||
///
|
///
|
||||||
/// // Ignoring `track`.
|
/// // Ignoring `track`.
|
||||||
/// uri!(get_item: 100, _);
|
/// uri!(get_item(100, _));
|
||||||
/// uri!(get_item: 100, None as Option<String>);
|
/// uri!(get_item(100, None as Option<String>));
|
||||||
/// uri!(get_item: id = 100, track = _);
|
/// uri!(get_item(id = 100, track = _));
|
||||||
/// uri!(get_item: track = _, id = 100);
|
/// uri!(get_item(track = _, id = 100));
|
||||||
/// uri!(get_item: id = 100, track = None as Option<&str>);
|
/// uri!(get_item(id = 100, track = None as Option<&str>));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// After verifying parameters and their types, Rocket will generate code
|
/// After verifying parameters and their types, Rocket will generate code
|
||||||
|
@ -93,10 +90,11 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # 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={}",
|
/// 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>`
|
/// 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
|
/// seen, the implementations will be used to display the value in a URI-safe
|
||||||
/// manner.
|
/// manner.
|
||||||
///
|
///
|
||||||
/// [`uri!`]: ../../../rocket/macro.uri.html
|
/// [`uri!`]: rocket::uri
|
||||||
///
|
///
|
||||||
/// # Provided Implementations
|
/// # 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.
|
/// types.
|
||||||
///
|
///
|
||||||
/// * **i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32,
|
/// * **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
|
/// If the `Result` is `Ok`, uses the implementation of `UriDisplay` for
|
||||||
/// `T`. Otherwise, nothing is rendered.
|
/// `T`. Otherwise, nothing is rendered.
|
||||||
///
|
///
|
||||||
/// [`FromUriParam`]: crate::uri::FromUriParam
|
/// [`FromUriParam`]: crate::uri::fmt::FromUriParam
|
||||||
///
|
///
|
||||||
/// # Deriving
|
/// # Deriving
|
||||||
///
|
///
|
||||||
|
@ -176,7 +174,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// # use rocket::http::uri::{UriDisplay, Query, Path};
|
/// # use rocket::http::uri::fmt::{UriDisplay, Query, Path};
|
||||||
/// // Derives `UriDisplay<Query>`
|
/// // Derives `UriDisplay<Query>`
|
||||||
/// #[derive(UriDisplayQuery)]
|
/// #[derive(UriDisplayQuery)]
|
||||||
/// struct User {
|
/// struct User {
|
||||||
|
@ -185,7 +183,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let user = User { name: "Michael Smith".into(), age: 31 };
|
/// 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");
|
/// assert_eq!(uri_string, "name=Michael%20Smith&age=31");
|
||||||
///
|
///
|
||||||
/// // Derives `UriDisplay<Path>`
|
/// // Derives `UriDisplay<Path>`
|
||||||
|
@ -193,7 +191,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
/// struct Name(String);
|
/// struct Name(String);
|
||||||
///
|
///
|
||||||
/// let name = Name("Bob Smith".into());
|
/// 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");
|
/// 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
|
/// [`UriDisplay<Path>`] and [`UriDisplay<Query>`] derive documentation for full
|
||||||
/// details.
|
/// details.
|
||||||
///
|
///
|
||||||
/// [`Ignorable`]: crate::uri::Ignorable
|
/// [`Ignorable`]: crate::uri::fmt::Ignorable
|
||||||
/// [`UriDisplay<Path>`]: ../../derive.UriDisplayPath.html
|
/// [`UriDisplay<Path>`]: ../../derive.UriDisplayPath.html
|
||||||
/// [`UriDisplay<Query>`]: ../../derive.UriDisplayQuery.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
|
/// # Implementing
|
||||||
///
|
///
|
||||||
|
@ -259,7 +255,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
///
|
///
|
||||||
/// use std::fmt;
|
/// use std::fmt;
|
||||||
/// use rocket::http::impl_from_uri_param_identity;
|
/// 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;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// impl UriDisplay<Path> for Name<'_> {
|
/// impl UriDisplay<Path> for Name<'_> {
|
||||||
|
@ -276,7 +272,7 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
///
|
///
|
||||||
/// #[get("/name/<name>")]
|
/// #[get("/name/<name>")]
|
||||||
/// fn redirector(name: Name<'_>) -> Redirect {
|
/// fn redirector(name: Name<'_>) -> Redirect {
|
||||||
/// Redirect::to(uri!(real: name))
|
/// Redirect::to(uri!(real(name)))
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[get("/<name>")]
|
/// #[get("/<name>")]
|
||||||
|
@ -284,15 +280,15 @@ use crate::uri::{Uri, UriPart, Path, Query, Formatter};
|
||||||
/// format!("Hello, {}!", name.0)
|
/// 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");
|
/// 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.
|
/// Formats `self` in a URI-safe manner using the given formatter.
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result;
|
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)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
UriDisplay::fmt(*self, &mut <Formatter<'_, P>>::new(f))
|
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`.
|
// Direct implementations: these are the leaves of a call to `UriDisplay::fmt`.
|
||||||
|
|
||||||
/// Percent-encodes the raw string.
|
/// Percent-encodes the raw string.
|
||||||
impl<P: UriPart> UriDisplay<P> for str {
|
impl<P: Part> UriDisplay<P> for str {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
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 {
|
macro_rules! impl_with_display {
|
||||||
($($T:ty),+) => {$(
|
($($T:ty),+) => {$(
|
||||||
/// This implementation is identical to the `Display` implementation.
|
/// This implementation is identical to the `Display` implementation.
|
||||||
impl<P: UriPart> UriDisplay<P> for $T {
|
impl<P: Part> UriDisplay<P> for $T {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
@ -351,7 +347,7 @@ impl_with_display! {
|
||||||
// implementation.
|
// implementation.
|
||||||
|
|
||||||
/// Percent-encodes the raw string. Defers to `str`.
|
/// Percent-encodes the raw string. Defers to `str`.
|
||||||
impl<P: UriPart> UriDisplay<P> for String {
|
impl<P: Part> UriDisplay<P> for String {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||||
self.as_str().fmt(f)
|
self.as_str().fmt(f)
|
||||||
|
@ -359,7 +355,7 @@ impl<P: UriPart> UriDisplay<P> for String {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Percent-encodes the raw string. Defers to `str`.
|
/// 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)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||||
self.as_ref().fmt(f)
|
self.as_ref().fmt(f)
|
||||||
|
@ -375,7 +371,7 @@ impl UriDisplay<Path> for path::PathBuf {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defers to the `UriDisplay<P>` implementation for `T`.
|
/// 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)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||||
UriDisplay::fmt(*self, f)
|
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`.
|
/// 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)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_, P>) -> fmt::Result {
|
||||||
UriDisplay::fmt(*self, f)
|
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
|
/// When a parameter is explicitly ignored in `uri!` by supplying `_` as the
|
||||||
/// parameter's value, that parameter's type is required to implement this
|
/// parameter's value, that parameter's type is required to implement this
|
||||||
/// trait for the corresponding `UriPart`.
|
/// trait for the corresponding `Part`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[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>) { /* .. */ }
|
/// fn get_item(id: i32, track: Option<u8>) { /* .. */ }
|
||||||
///
|
///
|
||||||
/// // Ignore the `track` parameter: `Option<u8>` must be `Ignorable`.
|
/// // Ignore the `track` parameter: `Option<u8>` must be `Ignorable`.
|
||||||
/// uri!(get_item: 100, _);
|
/// uri!(get_item(100, _));
|
||||||
/// uri!(get_item: id = 100, track = _);
|
/// uri!(get_item(id = 100, track = _));
|
||||||
///
|
///
|
||||||
/// // Provide a value for `track`.
|
/// // Provide a value for `track`.
|
||||||
/// uri!(get_item: 100, Some(4));
|
/// uri!(get_item(100, Some(4)));
|
||||||
/// uri!(get_item: id = 100, track = Some(4));
|
/// uri!(get_item(id = 100, track = Some(4)));
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Implementations
|
/// # Implementations
|
||||||
|
@ -442,23 +438,24 @@ impl<T: UriDisplay<Query>, E> UriDisplay<Query> for Result<T, E> {
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::{Ignorable, Query};
|
/// use rocket::http::uri::fmt::{Ignorable, Query};
|
||||||
///
|
///
|
||||||
/// # struct MyType;
|
/// # struct MyType;
|
||||||
/// impl Ignorable<Query> for 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> Ignorable<Query> for Option<T> { }
|
||||||
impl<T, E> Ignorable<Query> for Result<T, E> { }
|
impl<T, E> Ignorable<Query> for Result<T, E> { }
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn assert_ignorable<P: UriPart, T: Ignorable<P>>() { }
|
pub fn assert_ignorable<P: Part, T: Ignorable<P>>() { }
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod uri_display_tests {
|
mod uri_display_tests {
|
||||||
use std::path;
|
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 {
|
macro_rules! uri_display {
|
||||||
(<$P:ident, $Target:ty> $source:expr) => ({
|
(<$P:ident, $Target:ty> $source:expr) => ({
|
||||||
|
@ -566,7 +563,7 @@ mod uri_display_tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_ignorables() {
|
fn check_ignorables() {
|
||||||
use crate::uri::assert_ignorable;
|
use crate::uri::fmt::assert_ignorable;
|
||||||
|
|
||||||
assert_ignorable::<Query, Option<usize>>();
|
assert_ignorable::<Query, Option<usize>>();
|
||||||
assert_ignorable::<Query, Option<Wrapper<usize>>>();
|
assert_ignorable::<Query, Option<Wrapper<usize>>>();
|
|
@ -1,106 +1,25 @@
|
||||||
//! Types for URIs and traits for rendering URI components.
|
//! Types for URIs and traits for rendering URI components.
|
||||||
|
|
||||||
mod uri;
|
mod uri;
|
||||||
mod uri_display;
|
|
||||||
mod formatter;
|
|
||||||
mod from_uri_param;
|
|
||||||
mod origin;
|
mod origin;
|
||||||
|
mod reference;
|
||||||
mod authority;
|
mod authority;
|
||||||
mod absolute;
|
mod absolute;
|
||||||
mod segments;
|
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::uri::*;
|
||||||
pub use self::authority::*;
|
pub use self::authority::*;
|
||||||
pub use self::origin::*;
|
pub use self::origin::*;
|
||||||
pub use self::absolute::*;
|
pub use self::absolute::*;
|
||||||
pub use self::uri_display::*;
|
|
||||||
pub use self::formatter::*;
|
|
||||||
pub use self::from_uri_param::*;
|
|
||||||
pub use self::segments::*;
|
pub use self::segments::*;
|
||||||
|
pub use self::reference::*;
|
||||||
mod private {
|
pub use self::path_query::*;
|
||||||
pub trait Sealed {}
|
pub use self::asterisk::*;
|
||||||
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 = '&';
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{self, Display};
|
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
use crate::ext::IntoOwned;
|
use crate::ext::IntoOwned;
|
||||||
use crate::parse::{Indexed, Extent, IndexedStr, uri::tables::is_pchar};
|
use crate::parse::{Extent, IndexedStr, uri::tables::is_pchar};
|
||||||
use crate::uri::{self, UriPart, Query, Path};
|
use crate::uri::{Error, Path, Query, Data, as_utf8_unchecked, fmt};
|
||||||
use crate::uri::{Error, Segments, QuerySegments, as_utf8_unchecked};
|
|
||||||
use crate::{RawStr, RawStrBuf};
|
use crate::{RawStr, RawStrBuf};
|
||||||
|
|
||||||
use state::Storage;
|
|
||||||
|
|
||||||
/// A URI with an absolute path and optional query: `/path?query`.
|
/// A URI with an absolute path and optional query: `/path?query`.
|
||||||
///
|
///
|
||||||
/// Origin URIs are the primary type of URI encountered in Rocket applications.
|
/// 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
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
|
@ -77,7 +73,7 @@ use state::Storage;
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// # use rocket::http::uri::Origin;
|
/// # use rocket::http::uri::Origin;
|
||||||
/// # let invalid = [
|
/// # let invalid = [
|
||||||
/// // abnormal versions
|
/// // non-normal versions
|
||||||
/// "//", "/a/b/", "/a/ab//c//d", "/a?a&&b&",
|
/// "//", "/a/b/", "/a/ab//c//d", "/a?a&&b&",
|
||||||
///
|
///
|
||||||
/// // normalized versions
|
/// // normalized versions
|
||||||
|
@ -89,14 +85,11 @@ use state::Storage;
|
||||||
/// # assert_eq!(abnormal.into_normalized(), expected);
|
/// # assert_eq!(abnormal.into_normalized(), expected);
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Origin<'a> {
|
pub struct Origin<'a> {
|
||||||
pub(crate) source: Option<Cow<'a, str>>,
|
pub(crate) source: Option<Cow<'a, str>>,
|
||||||
pub(crate) path: IndexedStr<'a>,
|
pub(crate) path: Data<'a, fmt::Path>,
|
||||||
pub(crate) query: Option<IndexedStr<'a>>,
|
pub(crate) query: Option<Data<'a, fmt::Query>>,
|
||||||
|
|
||||||
pub(crate) decoded_path_segs: Storage<Vec<IndexedStr<'static>>>,
|
|
||||||
pub(crate) decoded_query_segs: Storage<Vec<(IndexedStr<'static>, IndexedStr<'static>)>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Origin<'_> {
|
impl Hash for Origin<'_> {
|
||||||
|
@ -117,7 +110,7 @@ impl Eq for Origin<'_> { }
|
||||||
impl PartialEq<str> for Origin<'_> {
|
impl PartialEq<str> for Origin<'_> {
|
||||||
fn eq(&self, other: &str) -> bool {
|
fn eq(&self, other: &str) -> bool {
|
||||||
let (path, query) = RawStr::new(other).split_at_byte(b'?');
|
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(),
|
source: self.source.into_owned(),
|
||||||
path: self.path.into_owned(),
|
path: self.path.into_owned(),
|
||||||
query: self.query.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> {
|
impl<'a> Origin<'a> {
|
||||||
|
/// The root: `'/'`.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const ROOT: Origin<'static> = Origin::const_new("/", None);
|
||||||
|
|
||||||
/// SAFETY: `source` must be UTF-8.
|
/// SAFETY: `source` must be UTF-8.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) unsafe fn raw(
|
pub(crate) unsafe fn raw(
|
||||||
|
@ -176,11 +152,8 @@ impl<'a> Origin<'a> {
|
||||||
) -> Origin<'a> {
|
) -> Origin<'a> {
|
||||||
Origin {
|
Origin {
|
||||||
source: Some(as_utf8_unchecked(source)),
|
source: Some(as_utf8_unchecked(source)),
|
||||||
path: path.into(),
|
path: Data::raw(path),
|
||||||
query: query.map(|q| q.into()),
|
query: query.map(Data::raw)
|
||||||
|
|
||||||
decoded_path_segs: Storage::new(),
|
|
||||||
decoded_query_segs: Storage::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,19 +166,42 @@ impl<'a> Origin<'a> {
|
||||||
{
|
{
|
||||||
Origin {
|
Origin {
|
||||||
source: None,
|
source: None,
|
||||||
path: Indexed::from(path.into()),
|
path: Data::new(path.into()),
|
||||||
query: query.map(|q| Indexed::from(q.into())),
|
query: query.map(Data::new),
|
||||||
decoded_path_segs: Storage::new(),
|
|
||||||
decoded_query_segs: Storage::new(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to fabricate URIs in several places. Equivalent to `Origin::new("/",
|
// Used mostly for testing and to construct known good URIs from other parts
|
||||||
// None)` or `Origin::parse("/").unwrap()`. Should not be used outside of
|
// of Rocket. This should _really_ not be used outside of Rocket because the
|
||||||
// Rocket, though doing so would be harmless.
|
// resulting `Origin's` are not guaranteed to be valid origin URIs!
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn dummy() -> Origin<'static> {
|
pub fn path_only<P: Into<Cow<'a, str>>>(path: P) -> Origin<'a> {
|
||||||
Origin::new::<&'static str, &'static str>("/", None)
|
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
|
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||||
|
@ -214,7 +210,7 @@ impl<'a> Origin<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// use rocket::http::uri::Origin;
|
||||||
///
|
///
|
||||||
/// // Parse a valid origin URI.
|
/// // Parse a valid origin URI.
|
||||||
|
@ -224,6 +220,11 @@ impl<'a> Origin<'a> {
|
||||||
///
|
///
|
||||||
/// // Invalid URIs fail to parse.
|
/// // Invalid URIs fail to parse.
|
||||||
/// Origin::parse("foo bar").expect_err("invalid URI");
|
/// 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>> {
|
pub fn parse(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
|
||||||
crate::parse::uri::origin_from_str(string)
|
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 (path, query) = RawStr::new(string).split_at_byte(b'?');
|
||||||
let query = match query.is_empty() {
|
let query = (!query.is_empty()).then(|| query.as_str());
|
||||||
false => Some(query.as_str()),
|
|
||||||
true => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Origin::new(path.as_str(), query))
|
Ok(Origin::new(path.as_str(), query))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses the string `string` into an `Origin`. Parsing will never
|
/// Parses the string `string` into an `Origin`. Parsing will never
|
||||||
/// allocate. This method should be used instead of
|
/// allocate. This method should be used instead of [`Origin::parse()`] when
|
||||||
/// [`Origin::parse()`](crate::uri::Origin::parse()) when the source URI is
|
/// the source URI is already a `String`. Returns an `Error` if `string` is
|
||||||
/// already a `String`. Returns an `Error` if `string` is not a valid origin
|
/// not a valid origin URI.
|
||||||
/// URI.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -266,7 +262,7 @@ impl<'a> Origin<'a> {
|
||||||
/// let source = format!("/foo/{}/three", 2);
|
/// let source = format!("/foo/{}/three", 2);
|
||||||
/// let uri = Origin::parse_owned(source).expect("valid URI");
|
/// let uri = Origin::parse_owned(source).expect("valid URI");
|
||||||
/// assert_eq!(uri.path(), "/foo/2/three");
|
/// 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>> {
|
pub fn parse_owned(string: String) -> Result<Origin<'static>, Error<'static>> {
|
||||||
// We create a copy of a pointer to `string` to escape the borrow
|
// 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
|
// These two facts can be easily verified. An `&mut` can't be created
|
||||||
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
||||||
// since it's passed in to `source`.
|
// 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 copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||||
let origin = Origin::parse(copy_of_str)?;
|
let origin = Origin::parse(copy_of_str)?;
|
||||||
debug_assert!(origin.source.is_some(), "Origin source parsed w/o source");
|
debug_assert!(origin.source.is_some(), "Origin source parsed w/o source");
|
||||||
|
@ -288,8 +283,6 @@ impl<'a> Origin<'a> {
|
||||||
let origin = Origin {
|
let origin = Origin {
|
||||||
path: origin.path.into_owned(),
|
path: origin.path.into_owned(),
|
||||||
query: origin.query.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
|
// At this point, it's impossible for anything to be borrowing
|
||||||
// `string` except for `source`, even though Rust doesn't know it.
|
// `string` except for `source`, even though Rust doesn't know it.
|
||||||
// Because we're replacing `source` here, there can't possibly be a
|
// Because we're replacing `source` here, there can't possibly be a
|
||||||
|
@ -300,118 +293,39 @@ impl<'a> Origin<'a> {
|
||||||
Ok(origin)
|
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.
|
/// Returns the path part of this URI.
|
||||||
///
|
///
|
||||||
/// ### Examples
|
/// # Example
|
||||||
///
|
|
||||||
/// A URI with only a path:
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// let uri = uri!("/a/b/c");
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/a/b/c").unwrap();
|
|
||||||
/// assert_eq!(uri.path(), "/a/b/c");
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
/// A URI with a query:
|
/// let uri = uri!("/a/b/c?name=bob");
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Origin;
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/a/b/c?name=bob").unwrap();
|
|
||||||
/// assert_eq!(uri.path(), "/a/b/c");
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn path(&self) -> &RawStr {
|
pub fn path(&self) -> Path<'_> {
|
||||||
self.path.from_cow_source(&self.source).into()
|
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
|
/// 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.
|
/// Affix a trailing slash if one isn't present.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// 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 uri = uri!("/a/b/c");
|
||||||
/// let expected_uri = Origin::parse("/a/b/c/").unwrap();
|
/// let abnormal_map = uri.map_path(|p| format!("{}///d", p));
|
||||||
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
|
/// assert_eq!(abnormal_map.unwrap(), "/a/b/c///d");
|
||||||
///
|
///
|
||||||
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
|
/// let uri = uri!("/a/b/c");
|
||||||
/// let expected_uri = Origin::parse("/a/b/c//").unwrap();
|
/// let expected = uri!("/b/c");
|
||||||
/// assert_eq!(old_uri.map_path(|p| format!("{}/", p)), Some(expected_uri));
|
/// 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 uri = uri!("/a");
|
||||||
/// let expected = Origin::parse("/b/c/").unwrap();
|
/// assert_eq!(uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None);
|
||||||
/// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), Some(expected));
|
|
||||||
///
|
///
|
||||||
/// let old_uri = Origin::parse("/a").unwrap();
|
/// let uri = uri!("/a/b/c");
|
||||||
/// assert_eq!(old_uri.map_path(|p| p.strip_prefix("/a").unwrap_or(p)), None);
|
/// assert_eq!(uri.map_path(|p| format!("hi/{}", p)), None);
|
||||||
///
|
|
||||||
/// let old_uri = Origin::parse("/a/b/c/").unwrap();
|
|
||||||
/// assert_eq!(old_uri.map_path(|p| format!("hi/{}", p)), None);
|
|
||||||
/// ```
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn map_path<'s, F, P>(&'s self, f: F) -> Option<Self>
|
pub fn map_path<'s, F, P>(&'s self, f: F) -> Option<Self>
|
||||||
where F: FnOnce(&'s RawStr) -> P, P: Into<RawStrBuf> + 's
|
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)) {
|
if !path.starts_with('/') || !path.as_bytes().iter().all(|b| is_pchar(&b)) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(Origin {
|
Some(Origin {
|
||||||
source: self.source.clone(),
|
source: self.source.clone(),
|
||||||
path: Cow::from(path.into_string()).into(),
|
path: Data::new(Cow::from(path.into_string())),
|
||||||
query: self.query.clone(),
|
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.
|
/// Removes the query part of this URI, if there is any.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// let mut uri = uri!("/a/b/c?query=some");
|
||||||
///
|
|
||||||
/// let mut uri = Origin::parse("/a/b/c?query=some").unwrap();
|
|
||||||
/// assert_eq!(uri.query().unwrap(), "query=some");
|
/// assert_eq!(uri.query().unwrap(), "query=some");
|
||||||
///
|
///
|
||||||
/// uri.clear_query();
|
/// uri.clear_query();
|
||||||
/// assert_eq!(uri.query(), None);
|
/// assert!(uri.query().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn clear_query(&mut self) {
|
pub fn clear_query(&mut self) {
|
||||||
self.query = None;
|
self.set_query(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a (smart) iterator over the non-empty, percent-decoded segments
|
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||||
/// of the path of this URI.
|
///
|
||||||
|
/// 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
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -519,25 +427,28 @@ impl<'a> Origin<'a> {
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// use rocket::http::uri::Origin;
|
||||||
///
|
///
|
||||||
/// let uri = Origin::parse("/a%20b/b%2Fc/d//e?query=some").unwrap();
|
/// let mut abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||||
/// let path_segs: Vec<&str> = uri.path_segments().collect();
|
/// assert!(!abnormal.is_normalized());
|
||||||
/// assert_eq!(path_segs, &["a b", "b/c", "d", "e"]);
|
/// abnormal.normalize();
|
||||||
|
/// assert!(abnormal.is_normalized());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn path_segments(&self) -> Segments<'_> {
|
pub fn normalize(&mut self) {
|
||||||
let cached = self.decoded_path_segs.get_or_set(|| {
|
if !self.path().is_normalized(true) {
|
||||||
let (indexed, path) = (&self.path, self.path());
|
self.path = self.path().to_normalized(true);
|
||||||
self.raw_path_segments()
|
}
|
||||||
.filter(|r| !r.is_empty())
|
|
||||||
.map(|s| decode_to_indexed_str::<Path>(s, (indexed, path)))
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
Segments { source: self.path(), segments: cached, pos: 0 }
|
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,
|
/// Consumes `self` and returns a normalized version.
|
||||||
/// value)` pairs of the query of this URI. If there is no query, the
|
///
|
||||||
/// iterator is empty.
|
/// 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
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -545,103 +456,13 @@ impl<'a> Origin<'a> {
|
||||||
/// # extern crate rocket;
|
/// # extern crate rocket;
|
||||||
/// use rocket::http::uri::Origin;
|
/// use rocket::http::uri::Origin;
|
||||||
///
|
///
|
||||||
/// let uri = Origin::parse("/").unwrap();
|
/// let abnormal = Origin::parse("/a/b/c//d").unwrap();
|
||||||
/// let query_segs: Vec<_> = uri.query_segments().collect();
|
/// assert!(!abnormal.is_normalized());
|
||||||
/// assert!(query_segs.is_empty());
|
/// assert!(abnormal.into_normalized().is_normalized());
|
||||||
///
|
|
||||||
/// 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", "")]);
|
|
||||||
/// ```
|
/// ```
|
||||||
pub fn query_segments(&self) -> QuerySegments<'_> {
|
pub fn into_normalized(mut self) -> Self {
|
||||||
let cached = self.decoded_query_segs.get_or_set(|| {
|
self.normalize();
|
||||||
let (indexed, query) = match (self.query.as_ref(), self.query()) {
|
self
|
||||||
(Some(i), Some(q)) => (i, q),
|
|
||||||
_ => return vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
self.raw_query_segments()
|
|
||||||
.filter(|s| !s.is_empty())
|
|
||||||
.map(|s| s.split_at_byte(b'='))
|
|
||||||
.map(|(name, val)| {
|
|
||||||
let name = decode_to_indexed_str::<Query>(name, (indexed, query));
|
|
||||||
let val = decode_to_indexed_str::<Query>(val, (indexed, query));
|
|
||||||
(name, val)
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
QuerySegments { source: self.query(), segments: cached, pos: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over the raw, undecoded segments of the path in this
|
|
||||||
/// URI. Segments may be empty.
|
|
||||||
///
|
|
||||||
/// ### Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Origin;
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/").unwrap();
|
|
||||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
|
||||||
/// assert!(segments.is_empty());
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("//").unwrap();
|
|
||||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
|
||||||
/// assert_eq!(segments, &["", ""]);
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/a").unwrap();
|
|
||||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
|
||||||
/// assert_eq!(segments, &["a"]);
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/a//b///c/d?query¶m").unwrap();
|
|
||||||
/// let segments: Vec<_> = uri.raw_path_segments().collect();
|
|
||||||
/// assert_eq!(segments, &["a", "", "b", "", "", "c", "d"]);
|
|
||||||
/// ```
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn raw_path_segments(&self) -> impl Iterator<Item = &RawStr> {
|
|
||||||
let path = match self.path() {
|
|
||||||
p if p == "/" => None,
|
|
||||||
p if p.starts_with('/') => Some(&p[1..]),
|
|
||||||
p => Some(p)
|
|
||||||
};
|
|
||||||
|
|
||||||
path.map(|p| p.split(Path::DELIMITER))
|
|
||||||
.into_iter()
|
|
||||||
.flatten()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns an iterator over the non-empty, non-url-decoded `(name, value)`
|
|
||||||
/// pairs of the query of this URI. If there is no query, the iterator is
|
|
||||||
/// empty. Segments may be empty.
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Origin;
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/").unwrap();
|
|
||||||
/// assert!(uri.raw_query_segments().next().is_none());
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/?a=b&dog").unwrap();
|
|
||||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
|
||||||
/// assert_eq!(query_segs, &["a=b", "dog"]);
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/?&").unwrap();
|
|
||||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
|
||||||
/// assert_eq!(query_segs, &["", ""]);
|
|
||||||
///
|
|
||||||
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
|
||||||
/// let query_segs: Vec<_> = uri.raw_query_segments().collect();
|
|
||||||
/// assert_eq!(query_segs, &["a+b%2F=some+one%40gmail.com", "", "%26%3D2"]);
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
|
||||||
pub fn raw_query_segments(&self) -> impl Iterator<Item = &RawStr> {
|
|
||||||
self.query()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|q| q.split(Query::DELIMITER))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,11 +491,11 @@ impl<'a> TryFrom<&'a str> for Origin<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Origin<'_> {
|
impl std::fmt::Display for Origin<'_> {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{}", self.path())?;
|
write!(f, "{}", self.path())?;
|
||||||
if let Some(q) = self.query() {
|
if let Some(query) = self.query() {
|
||||||
write!(f, "?{}", q)?;
|
write!(f, "?{}", query)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -687,7 +508,7 @@ mod tests {
|
||||||
|
|
||||||
fn seg_count(path: &str, expected: usize) -> bool {
|
fn seg_count(path: &str, expected: usize) -> bool {
|
||||||
let origin = Origin::parse(path).unwrap();
|
let origin = Origin::parse(path).unwrap();
|
||||||
let segments = origin.path_segments();
|
let segments = origin.path().segments();
|
||||||
let actual = segments.len();
|
let actual = segments.len();
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
|
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
|
||||||
|
@ -707,7 +528,7 @@ mod tests {
|
||||||
Err(e) => panic!("failed to parse {}: {}", path, e)
|
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
|
actual == expected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -792,7 +613,7 @@ mod tests {
|
||||||
|
|
||||||
fn test_query(uri: &str, query: Option<&str>) {
|
fn test_query(uri: &str, query: Option<&str>) {
|
||||||
let uri = Origin::parse(uri).unwrap();
|
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]
|
#[test]
|
||||||
|
|
|
@ -0,0 +1,387 @@
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use state::Storage;
|
||||||
|
|
||||||
|
use crate::{RawStr, ext::IntoOwned};
|
||||||
|
use crate::uri::Segments;
|
||||||
|
use crate::uri::fmt::{self, Part};
|
||||||
|
use crate::parse::{IndexedStr, Extent};
|
||||||
|
|
||||||
|
// INTERNAL DATA STRUCTURE.
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Data<'a, P: Part> {
|
||||||
|
pub(crate) value: IndexedStr<'a>,
|
||||||
|
pub(crate) decoded_segments: Storage<Vec<P::Raw>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, P: Part> Data<'a, P> {
|
||||||
|
pub(crate) fn raw(value: Extent<&'a [u8]>) -> Self {
|
||||||
|
Data { value: value.into(), decoded_segments: Storage::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// INTERNAL METHOD.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn new<S: Into<Cow<'a, str>>>(value: S) -> Self {
|
||||||
|
Data {
|
||||||
|
value: IndexedStr::from(value.into()),
|
||||||
|
decoded_segments: Storage::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A URI path: `/foo/bar`, `foo/bar`, etc.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Path<'a> {
|
||||||
|
pub(crate) source: &'a Option<Cow<'a, str>>,
|
||||||
|
pub(crate) data: &'a Data<'a, fmt::Path>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A URI query: `?foo&bar`.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Query<'a> {
|
||||||
|
pub(crate) source: &'a Option<Cow<'a, str>>,
|
||||||
|
pub(crate) data: &'a Data<'a, fmt::Query>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_to_indexed_str<P: fmt::Part>(
|
||||||
|
value: &RawStr,
|
||||||
|
(indexed, source): (&IndexedStr<'_>, &RawStr)
|
||||||
|
) -> IndexedStr<'static> {
|
||||||
|
let decoded = match P::KIND {
|
||||||
|
fmt::Kind::Path => value.percent_decode_lossy(),
|
||||||
|
fmt::Kind::Query => value.url_decode_lossy(),
|
||||||
|
};
|
||||||
|
|
||||||
|
match decoded {
|
||||||
|
Cow::Borrowed(b) if indexed.is_indexed() => {
|
||||||
|
let indexed = IndexedStr::checked_from(b, source.as_str());
|
||||||
|
debug_assert!(indexed.is_some());
|
||||||
|
indexed.unwrap_or(IndexedStr::from(Cow::Borrowed("")))
|
||||||
|
}
|
||||||
|
cow => IndexedStr::from(Cow::Owned(cow.into_owned())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Path<'a> {
|
||||||
|
/// Returns the raw path value.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("/foo%20bar%2dbaz");
|
||||||
|
/// assert_eq!(uri.path(), "/foo%20bar%2dbaz");
|
||||||
|
/// assert_eq!(uri.path().raw(), "/foo%20bar%2dbaz");
|
||||||
|
/// ```
|
||||||
|
pub fn raw(&self) -> &'a RawStr {
|
||||||
|
self.data.value.from_cow_source(&self.source).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw, undecoded path value as an `&str`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("/foo%20bar%2dbaz");
|
||||||
|
/// assert_eq!(uri.path(), "/foo%20bar%2dbaz");
|
||||||
|
/// assert_eq!(uri.path().as_str(), "/foo%20bar%2dbaz");
|
||||||
|
/// ```
|
||||||
|
pub fn as_str(&self) -> &'a str {
|
||||||
|
self.raw().as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `self` is normalized, i.e, it has no empty segments.
|
||||||
|
///
|
||||||
|
/// If `absolute`, then a starting `/` is required.
|
||||||
|
pub(crate) fn is_normalized(&self, absolute: bool) -> bool {
|
||||||
|
(!absolute || self.raw().starts_with('/'))
|
||||||
|
&& self.raw_segments().all(|s| !s.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes `self`. If `absolute`, a starting `/` is required.
|
||||||
|
pub(crate) fn to_normalized(&self, absolute: bool) -> Data<'static, fmt::Path> {
|
||||||
|
let mut path = String::with_capacity(self.raw().len());
|
||||||
|
let absolute = absolute || self.raw().starts_with('/');
|
||||||
|
for (i, seg) in self.raw_segments().filter(|s| !s.is_empty()).enumerate() {
|
||||||
|
if absolute || i != 0 { path.push('/'); }
|
||||||
|
let _ = write!(path, "{}", seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if path.is_empty() && absolute {
|
||||||
|
path.push('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
Data {
|
||||||
|
value: IndexedStr::from(Cow::Owned(path)),
|
||||||
|
decoded_segments: Storage::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the raw, undecoded segments. Segments may be
|
||||||
|
/// empty.
|
||||||
|
///
|
||||||
|
/// ### Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/").unwrap();
|
||||||
|
/// assert_eq!(uri.path().raw_segments().count(), 0);
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("//").unwrap();
|
||||||
|
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||||
|
/// assert_eq!(segments, &["", ""]);
|
||||||
|
///
|
||||||
|
/// // Recall that `uri!()` normalizes static inputs.
|
||||||
|
/// let uri = uri!("//");
|
||||||
|
/// assert_eq!(uri.path().raw_segments().count(), 0);
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a").unwrap();
|
||||||
|
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||||
|
/// assert_eq!(segments, &["a"]);
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a//b///c/d?query¶m").unwrap();
|
||||||
|
/// let segments: Vec<_> = uri.path().raw_segments().collect();
|
||||||
|
/// assert_eq!(segments, &["a", "", "b", "", "", "c", "d"]);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn raw_segments(&self) -> impl Iterator<Item = &'a RawStr> {
|
||||||
|
let path = match self.raw() {
|
||||||
|
p if p.is_empty() || p == "/" => None,
|
||||||
|
p if p.starts_with(fmt::Path::DELIMITER) => Some(&p[1..]),
|
||||||
|
p => Some(p)
|
||||||
|
};
|
||||||
|
|
||||||
|
path.map(|p| p.split(fmt::Path::DELIMITER))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a (smart) iterator over the non-empty, percent-decoded segments.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/a%20b/b%2Fc/d//e?query=some").unwrap();
|
||||||
|
/// let path_segs: Vec<&str> = uri.path().segments().collect();
|
||||||
|
/// assert_eq!(path_segs, &["a b", "b/c", "d", "e"]);
|
||||||
|
/// ```
|
||||||
|
pub fn segments(&self) -> Segments<'a, fmt::Path> {
|
||||||
|
let cached = self.data.decoded_segments.get_or_set(|| {
|
||||||
|
let (indexed, path) = (&self.data.value, self.raw());
|
||||||
|
self.raw_segments()
|
||||||
|
.filter(|r| !r.is_empty())
|
||||||
|
.map(|s| decode_to_indexed_str::<fmt::Path>(s, (indexed, path)))
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
Segments::new(self.raw(), cached)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Query<'a> {
|
||||||
|
/// Returns the raw, undecoded query value.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("/foo?baz+bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "baz+bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap().raw(), "baz+bar");
|
||||||
|
/// ```
|
||||||
|
pub fn raw(&self) -> &'a RawStr {
|
||||||
|
self.data.value.from_cow_source(&self.source).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw, undecoded query value as an `&str`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("/foo/bar?baz+bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "baz+bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap().as_str(), "baz+bar");
|
||||||
|
/// ```
|
||||||
|
pub fn as_str(&self) -> &'a str {
|
||||||
|
self.raw().as_str()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether `self` is normalized, i.e, it has no empty segments.
|
||||||
|
pub(crate) fn is_normalized(&self) -> bool {
|
||||||
|
!self.is_empty() && self.raw_segments().all(|s| !s.is_empty())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes `self`.
|
||||||
|
pub(crate) fn to_normalized(&self) -> Option<Data<'static, fmt::Query>> {
|
||||||
|
let mut query = String::with_capacity(self.raw().len());
|
||||||
|
for (i, seg) in self.raw_segments().filter(|s| !s.is_empty()).enumerate() {
|
||||||
|
if i != 0 { query.push('&'); }
|
||||||
|
let _ = write!(query, "{}", seg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if query.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(Data {
|
||||||
|
value: IndexedStr::from(Cow::Owned(query)),
|
||||||
|
decoded_segments: Storage::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator over the non-empty, undecoded `(name, value)` pairs
|
||||||
|
/// of this query. If there is no query, the iterator is empty. Segments may
|
||||||
|
/// be empty.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/").unwrap();
|
||||||
|
/// assert!(uri.query().is_none());
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/?a=b&dog").unwrap();
|
||||||
|
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||||
|
/// assert_eq!(query_segs, &["a=b", "dog"]);
|
||||||
|
///
|
||||||
|
/// // This is not normalized, so the query is `""`, the empty string.
|
||||||
|
/// let uri = Origin::parse("/?&").unwrap();
|
||||||
|
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||||
|
/// assert_eq!(query_segs, &["", ""]);
|
||||||
|
///
|
||||||
|
/// // Recall that `uri!()` normalizes.
|
||||||
|
/// let uri = uri!("/?&");
|
||||||
|
/// assert!(uri.query().is_none());
|
||||||
|
///
|
||||||
|
/// // These are raw and undecoded. Use `segments()` for decoded variant.
|
||||||
|
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||||
|
/// let query_segs: Vec<_> = uri.query().unwrap().raw_segments().collect();
|
||||||
|
/// assert_eq!(query_segs, &["a+b%2F=some+one%40gmail.com", "", "%26%3D2"]);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn raw_segments(&self) -> impl Iterator<Item = &'a RawStr> {
|
||||||
|
let query = match self.raw() {
|
||||||
|
q if q.is_empty() => None,
|
||||||
|
q => Some(q)
|
||||||
|
};
|
||||||
|
|
||||||
|
query.map(|p| p.split(fmt::Query::DELIMITER))
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a (smart) iterator over the non-empty, url-decoded `(name,
|
||||||
|
/// value)` pairs of this query. If there is no query, the iterator is
|
||||||
|
/// empty.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Origin;
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/").unwrap();
|
||||||
|
/// assert!(uri.query().is_none());
|
||||||
|
///
|
||||||
|
/// let uri = Origin::parse("/foo/bar?a+b%2F=some+one%40gmail.com&&%26%3D2").unwrap();
|
||||||
|
/// let query_segs: Vec<_> = uri.query().unwrap().segments().collect();
|
||||||
|
/// assert_eq!(query_segs, &[("a b/", "some one@gmail.com"), ("&=2", "")]);
|
||||||
|
/// ```
|
||||||
|
pub fn segments(&self) -> Segments<'a, fmt::Query> {
|
||||||
|
let cached = self.data.decoded_segments.get_or_set(|| {
|
||||||
|
let (indexed, query) = (&self.data.value, self.raw());
|
||||||
|
self.raw_segments()
|
||||||
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.split_at_byte(b'='))
|
||||||
|
.map(|(k, v)| {
|
||||||
|
let key = decode_to_indexed_str::<fmt::Query>(k, (indexed, query));
|
||||||
|
let val = decode_to_indexed_str::<fmt::Query>(v, (indexed, query));
|
||||||
|
(key, val)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
Segments::new(self.raw(), cached)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_partial_eq {
|
||||||
|
($A:ty = $B:ty) => (
|
||||||
|
impl PartialEq<$A> for $B {
|
||||||
|
#[inline(always)]
|
||||||
|
fn eq(&self, other: &$A) -> bool {
|
||||||
|
let left: &RawStr = self.as_ref();
|
||||||
|
let right: &RawStr = other.as_ref();
|
||||||
|
left == right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_traits {
|
||||||
|
($T:ident) => (
|
||||||
|
impl Hash for $T<'_> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.raw().hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for $T<'_> { }
|
||||||
|
|
||||||
|
impl IntoOwned for Data<'_, fmt::$T> {
|
||||||
|
type Owned = Data<'static, fmt::$T>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
Data {
|
||||||
|
value: self.value.into_owned(),
|
||||||
|
decoded_segments: self.decoded_segments.map(|v| v.into_owned()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for $T<'_> {
|
||||||
|
type Target = RawStr;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.raw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<RawStr> for $T<'_> {
|
||||||
|
fn as_ref(&self) -> &RawStr {
|
||||||
|
self.raw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for $T<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.raw())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_partial_eq!($T<'_> = $T<'_>);
|
||||||
|
impl_partial_eq!(str = $T<'_>);
|
||||||
|
impl_partial_eq!(&str = $T<'_>);
|
||||||
|
impl_partial_eq!($T<'_> = str);
|
||||||
|
impl_partial_eq!($T<'_> = &str);
|
||||||
|
impl_partial_eq!(RawStr = $T<'_>);
|
||||||
|
impl_partial_eq!(&RawStr = $T<'_>);
|
||||||
|
impl_partial_eq!($T<'_> = RawStr);
|
||||||
|
impl_partial_eq!($T<'_> = &RawStr);
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_traits!(Path);
|
||||||
|
impl_traits!(Query);
|
|
@ -0,0 +1,549 @@
|
||||||
|
use std::{borrow::Cow, convert::TryFrom};
|
||||||
|
|
||||||
|
use crate::RawStr;
|
||||||
|
use crate::ext::IntoOwned;
|
||||||
|
use crate::uri::{Authority, Data, Origin, Absolute, Asterisk};
|
||||||
|
use crate::uri::{Path, Query, Error, as_utf8_unchecked, fmt};
|
||||||
|
use crate::parse::{Extent, IndexedStr};
|
||||||
|
|
||||||
|
/// A URI-reference with optional scheme, authority, relative path, query, and
|
||||||
|
/// fragment parts.
|
||||||
|
///
|
||||||
|
/// # Structure
|
||||||
|
///
|
||||||
|
/// The following diagram illustrates the syntactic structure of a URI reference
|
||||||
|
/// with all optional parts:
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// http://user:pass@domain.com:4444/foo/bar?some=query#and-fragment
|
||||||
|
/// |--| |------------------------||------| |--------| |----------|
|
||||||
|
/// scheme authority path query fragment
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// All parts are optional. When a scheme and authority are not present, the
|
||||||
|
/// path may be relative: `foo/bar?baz#cat`.
|
||||||
|
///
|
||||||
|
/// # Conversion
|
||||||
|
///
|
||||||
|
/// All other URI types ([`Origin`], [`Absolute`], and so on) are valid URI
|
||||||
|
/// references. As such, conversion between the types is lossless:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// let absolute = uri!("http://rocket.rs");
|
||||||
|
/// let reference: Reference = absolute.into();
|
||||||
|
/// assert_eq!(reference.scheme(), Some("http"));
|
||||||
|
/// assert_eq!(reference.authority().unwrap().host(), "rocket.rs");
|
||||||
|
///
|
||||||
|
/// let origin = uri!("/foo/bar");
|
||||||
|
/// let reference: Reference = origin.into();
|
||||||
|
/// assert_eq!(reference.path(), "/foo/bar");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Note that `uri!()` macro _always_ prefers the more specific URI variant to
|
||||||
|
/// `Reference` when possible, as is demonstrated above for `absolute` and
|
||||||
|
/// `origin`.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Reference<'a> {
|
||||||
|
source: Option<Cow<'a, str>>,
|
||||||
|
scheme: Option<IndexedStr<'a>>,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
path: Data<'a, fmt::Path>,
|
||||||
|
query: Option<Data<'a, fmt::Query>>,
|
||||||
|
fragment: Option<IndexedStr<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Reference<'a> {
|
||||||
|
#[inline]
|
||||||
|
pub(crate) unsafe fn raw(
|
||||||
|
source: Cow<'a, [u8]>,
|
||||||
|
scheme: Option<Extent<&'a [u8]>>,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
path: Extent<&'a [u8]>,
|
||||||
|
query: Option<Extent<&'a [u8]>>,
|
||||||
|
fragment: Option<Extent<&'a [u8]>>,
|
||||||
|
) -> Reference<'a> {
|
||||||
|
Reference {
|
||||||
|
source: Some(as_utf8_unchecked(source)),
|
||||||
|
scheme: scheme.map(|s| s.into()),
|
||||||
|
authority: authority.map(|s| s.into()),
|
||||||
|
path: Data::raw(path),
|
||||||
|
query: query.map(Data::raw),
|
||||||
|
fragment: fragment.map(|f| f.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PRIVATE. Used during test.
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn new(
|
||||||
|
scheme: impl Into<Option<&'a str>>,
|
||||||
|
auth: impl Into<Option<Authority<'a>>>,
|
||||||
|
path: &'a str,
|
||||||
|
query: impl Into<Option<&'a str>>,
|
||||||
|
frag: impl Into<Option<&'a str>>,
|
||||||
|
) -> Reference<'a> {
|
||||||
|
Reference::const_new(scheme.into(), auth.into(), path, query.into(), frag.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PRIVATE. Used by codegen.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub const fn const_new(
|
||||||
|
scheme: Option<&'a str>,
|
||||||
|
authority: Option<Authority<'a>>,
|
||||||
|
path: &'a str,
|
||||||
|
query: Option<&'a str>,
|
||||||
|
fragment: Option<&'a str>,
|
||||||
|
) -> Reference<'a> {
|
||||||
|
Reference {
|
||||||
|
source: None,
|
||||||
|
scheme: match scheme {
|
||||||
|
Some(scheme) => Some(IndexedStr::Concrete(Cow::Borrowed(scheme))),
|
||||||
|
None => None
|
||||||
|
},
|
||||||
|
authority,
|
||||||
|
path: Data {
|
||||||
|
value: IndexedStr::Concrete(Cow::Borrowed(path)),
|
||||||
|
decoded_segments: state::Storage::new(),
|
||||||
|
},
|
||||||
|
query: match query {
|
||||||
|
Some(query) => Some(Data {
|
||||||
|
value: IndexedStr::Concrete(Cow::Borrowed(query)),
|
||||||
|
decoded_segments: state::Storage::new(),
|
||||||
|
}),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
fragment: match fragment {
|
||||||
|
Some(frag) => Some(IndexedStr::Concrete(Cow::Borrowed(frag))),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into an `Reference`. Parsing will never
|
||||||
|
/// allocate. Returns an `Error` if `string` is not a valid origin URI.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// // Parse a valid URI reference.
|
||||||
|
/// let uri = Reference::parse("/a/b/c?query").expect("valid URI");
|
||||||
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "query");
|
||||||
|
///
|
||||||
|
/// // Invalid URIs fail to parse.
|
||||||
|
/// Reference::parse("foo bar").expect_err("invalid URI");
|
||||||
|
///
|
||||||
|
/// // Prefer to use `uri!()` when the input is statically known:
|
||||||
|
/// let uri = uri!("/a/b/c?query#fragment");
|
||||||
|
/// assert_eq!(uri.path(), "/a/b/c");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "query");
|
||||||
|
/// assert_eq!(uri.fragment().unwrap(), "fragment");
|
||||||
|
/// ```
|
||||||
|
pub fn parse(string: &'a str) -> Result<Self, Error<'a>> {
|
||||||
|
crate::parse::uri::reference_from_str(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses the string `string` into a `Reference`. Never allocates.
|
||||||
|
///
|
||||||
|
/// This method should be used instead of [`Reference::parse()`] when the
|
||||||
|
/// source URI is already a `String`. Returns an `Error` if `string` is not
|
||||||
|
/// a valid URI reference.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// let source = format!("/foo?{}#3", 2);
|
||||||
|
/// let uri = Reference::parse_owned(source).unwrap();
|
||||||
|
/// assert_eq!(uri.path(), "/foo");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "2");
|
||||||
|
/// assert_eq!(uri.fragment().unwrap(), "3");
|
||||||
|
/// ```
|
||||||
|
pub fn parse_owned(string: String) -> Result<Self, Error<'a>> {
|
||||||
|
// We create a copy of a pointer to `string` to escape the borrow
|
||||||
|
// checker. This is so that we can "move out of the borrow" later.
|
||||||
|
//
|
||||||
|
// For this to be correct and safe, we need to ensure that:
|
||||||
|
//
|
||||||
|
// 1. No `&mut` references to `string` are created after this line.
|
||||||
|
// 2. `string` isn't dropped while `copy_of_str` is live.
|
||||||
|
//
|
||||||
|
// These two facts can be easily verified. An `&mut` can't be created
|
||||||
|
// because `string` isn't `mut`. Then, `string` is clearly not dropped
|
||||||
|
// since it's passed in to `source`.
|
||||||
|
let copy_of_str = unsafe { &*(string.as_str() as *const str) };
|
||||||
|
let uri_ref = Reference::parse(copy_of_str)?;
|
||||||
|
debug_assert!(uri_ref.source.is_some(), "UriRef parsed w/o source");
|
||||||
|
|
||||||
|
let uri_ref = Reference {
|
||||||
|
scheme: uri_ref.scheme.into_owned(),
|
||||||
|
authority: uri_ref.authority.into_owned(),
|
||||||
|
path: uri_ref.path.into_owned(),
|
||||||
|
query: uri_ref.query.into_owned(),
|
||||||
|
fragment: uri_ref.fragment.into_owned(),
|
||||||
|
// At this point, it's impossible for anything to be borrowing
|
||||||
|
// `string` except for `source`, even though Rust doesn't know it.
|
||||||
|
// Because we're replacing `source` here, there can't possibly be a
|
||||||
|
// borrow remaining, it's safe to "move out of the borrow".
|
||||||
|
source: Some(Cow::Owned(string)),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(uri_ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the scheme. If `Some`, is non-empty.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("http://rocket.rs?foo#bar");
|
||||||
|
/// assert_eq!(uri.scheme(), Some("http"));
|
||||||
|
///
|
||||||
|
/// let uri = uri!("ftp:/?foo#bar");
|
||||||
|
/// assert_eq!(uri.scheme(), Some("ftp"));
|
||||||
|
///
|
||||||
|
/// let uri = uri!("?foo#bar");
|
||||||
|
/// assert_eq!(uri.scheme(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn scheme(&self) -> Option<&str> {
|
||||||
|
self.scheme.as_ref().map(|s| s.from_cow_source(&self.source))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the authority part.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("http://rocket.rs:4444?foo#bar");
|
||||||
|
/// let auth = uri!("rocket.rs:4444");
|
||||||
|
/// assert_eq!(uri.authority().unwrap(), &auth);
|
||||||
|
///
|
||||||
|
/// let uri = uri!("?foo#bar");
|
||||||
|
/// assert_eq!(uri.authority(), None);
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||||
|
self.authority.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the path part. May be empty.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||||
|
/// assert_eq!(uri.path(), "/guide");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn path(&self) -> Path<'_> {
|
||||||
|
Path { source: &self.source, data: &self.path }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the query part. May be empty.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "foo");
|
||||||
|
///
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?q=bar");
|
||||||
|
/// assert_eq!(uri.query().unwrap(), "q=bar");
|
||||||
|
///
|
||||||
|
/// // Empty query parts are normalized away by `uri!()`.
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?#bar");
|
||||||
|
/// assert!(uri.query().is_none());
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn query(&self) -> Option<Query<'_>> {
|
||||||
|
self.query.as_ref().map(|data| Query { source: &self.source, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the fragment part, if any.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?foo#bar");
|
||||||
|
/// assert_eq!(uri.fragment().unwrap(), "bar");
|
||||||
|
///
|
||||||
|
/// // Fragment parts aren't normalized away, unlike query parts.
|
||||||
|
/// let uri = uri!("http://rocket.rs/guide?foo#");
|
||||||
|
/// assert_eq!(uri.fragment().unwrap(), "");
|
||||||
|
/// ```
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn fragment(&self) -> Option<&RawStr> {
|
||||||
|
self.fragment.as_ref()
|
||||||
|
.map(|frag| frag.from_cow_source(&self.source).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if `self` is normalized. Otherwise, returns `false`.
|
||||||
|
///
|
||||||
|
/// Normalization for a URI reference is equivalent to normalization for an
|
||||||
|
/// absolute URI. See [`Absolute#normalization`] for more information on
|
||||||
|
/// what it means for an absolute URI to be normalized.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// assert!(Reference::parse("foo/bar").unwrap().is_normalized());
|
||||||
|
/// assert!(Reference::parse("foo/bar#").unwrap().is_normalized());
|
||||||
|
/// assert!(Reference::parse("http://").unwrap().is_normalized());
|
||||||
|
/// assert!(Reference::parse("http://foo.rs/foo/bar").unwrap().is_normalized());
|
||||||
|
/// assert!(Reference::parse("foo:bar#baz").unwrap().is_normalized());
|
||||||
|
/// assert!(Reference::parse("http://rocket.rs#foo").unwrap().is_normalized());
|
||||||
|
///
|
||||||
|
/// assert!(!Reference::parse("http://?").unwrap().is_normalized());
|
||||||
|
/// assert!(!Reference::parse("git://rocket.rs/").unwrap().is_normalized());
|
||||||
|
/// assert!(!Reference::parse("http:/foo//bar").unwrap().is_normalized());
|
||||||
|
/// assert!(!Reference::parse("foo:bar?baz&&bop#c").unwrap().is_normalized());
|
||||||
|
/// assert!(!Reference::parse("http://rocket.rs?#foo").unwrap().is_normalized());
|
||||||
|
///
|
||||||
|
/// // Recall that `uri!()` normalizes static input.
|
||||||
|
/// assert!(uri!("http://rocket.rs#foo").is_normalized());
|
||||||
|
/// assert!(uri!("http://rocket.rs///foo////bar#cat").is_normalized());
|
||||||
|
/// ```
|
||||||
|
pub fn is_normalized(&self) -> bool {
|
||||||
|
let normalized_query = self.query().map_or(true, |q| q.is_normalized());
|
||||||
|
if self.authority().is_some() && !self.path().is_empty() {
|
||||||
|
self.path().is_normalized(true)
|
||||||
|
&& self.path() != "/"
|
||||||
|
&& normalized_query
|
||||||
|
} else {
|
||||||
|
self.path().is_normalized(false) && normalized_query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes `self` in-place. Does nothing if `self` is already
|
||||||
|
/// normalized.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("git://rocket.rs/").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// uri.normalize();
|
||||||
|
/// assert!(uri.is_normalized());
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("http:/foo//bar?baz&&#cat").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// uri.normalize();
|
||||||
|
/// assert!(uri.is_normalized());
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("foo:bar?baz&&bop").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// uri.normalize();
|
||||||
|
/// assert!(uri.is_normalized());
|
||||||
|
/// ```
|
||||||
|
pub fn normalize(&mut self) {
|
||||||
|
if self.authority().is_some() && !self.path().is_empty() {
|
||||||
|
if self.path() == "/" {
|
||||||
|
self.set_path("");
|
||||||
|
} else if !self.path().is_normalized(true) {
|
||||||
|
self.path = self.path().to_normalized(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.path = self.path().to_normalized(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(query) = self.query() {
|
||||||
|
if !query.is_normalized() {
|
||||||
|
self.query = query.to_normalized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Normalizes `self`. This is a no-op if `self` is already normalized.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use rocket::http::uri::Reference;
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("git://rocket.rs/").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// assert!(uri.into_normalized().is_normalized());
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("http:/foo//bar?baz&&#cat").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// assert!(uri.into_normalized().is_normalized());
|
||||||
|
///
|
||||||
|
/// let mut uri = Reference::parse("foo:bar?baz&&bop").unwrap();
|
||||||
|
/// assert!(!uri.is_normalized());
|
||||||
|
/// assert!(uri.into_normalized().is_normalized());
|
||||||
|
/// ```
|
||||||
|
pub fn into_normalized(mut self) -> Self {
|
||||||
|
self.normalize();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn set_path<P>(&mut self, path: P)
|
||||||
|
where P: Into<Cow<'a, str>>
|
||||||
|
{
|
||||||
|
self.path = Data::new(path.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the conrete path and query.
|
||||||
|
pub(crate) fn with_query_fragment_of(mut self, other: Reference<'a>) -> Self {
|
||||||
|
if let Some(query) = other.query {
|
||||||
|
if self.query().is_none() {
|
||||||
|
self.query = Some(Data::new(query.value.into_concrete(&self.source)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(frag) = other.fragment {
|
||||||
|
if self.fragment().is_none() {
|
||||||
|
self.fragment = Some(IndexedStr::from(frag.into_concrete(&self.source)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq<Reference<'_>> for Reference<'_> {
|
||||||
|
fn eq(&self, other: &Reference<'_>) -> bool {
|
||||||
|
self.scheme() == other.scheme()
|
||||||
|
&& self.authority() == other.authority()
|
||||||
|
&& self.path() == other.path()
|
||||||
|
&& self.query() == other.query()
|
||||||
|
&& self.fragment() == other.fragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoOwned for Reference<'_> {
|
||||||
|
type Owned = Reference<'static>;
|
||||||
|
|
||||||
|
fn into_owned(self) -> Self::Owned {
|
||||||
|
Reference {
|
||||||
|
source: self.source.into_owned(),
|
||||||
|
scheme: self.scheme.into_owned(),
|
||||||
|
authority: self.authority.into_owned(),
|
||||||
|
path: self.path.into_owned(),
|
||||||
|
query: self.query.into_owned(),
|
||||||
|
fragment: self.fragment.into_owned(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Absolute<'a>> for Reference<'a> {
|
||||||
|
fn from(absolute: Absolute<'a>) -> Self {
|
||||||
|
Reference {
|
||||||
|
source: absolute.source,
|
||||||
|
scheme: Some(absolute.scheme),
|
||||||
|
authority: absolute.authority,
|
||||||
|
path: absolute.path,
|
||||||
|
query: absolute.query,
|
||||||
|
fragment: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Origin<'a>> for Reference<'a> {
|
||||||
|
fn from(origin: Origin<'a>) -> Self {
|
||||||
|
Reference {
|
||||||
|
source: origin.source,
|
||||||
|
scheme: None,
|
||||||
|
authority: None,
|
||||||
|
path: origin.path,
|
||||||
|
query: origin.query,
|
||||||
|
fragment: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<Authority<'a>> for Reference<'a> {
|
||||||
|
fn from(authority: Authority<'a>) -> Self {
|
||||||
|
Reference {
|
||||||
|
source: match authority.source {
|
||||||
|
Some(Cow::Borrowed(b)) => Some(Cow::Borrowed(b)),
|
||||||
|
_ => None
|
||||||
|
},
|
||||||
|
authority: Some(authority),
|
||||||
|
scheme: None,
|
||||||
|
path: Data::new(""),
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Asterisk> for Reference<'_> {
|
||||||
|
fn from(_: Asterisk) -> Self {
|
||||||
|
Reference {
|
||||||
|
source: None,
|
||||||
|
authority: None,
|
||||||
|
scheme: None,
|
||||||
|
path: Data::new("*"),
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> TryFrom<&'a str> for Reference<'a> {
|
||||||
|
type Error = Error<'a>;
|
||||||
|
|
||||||
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
||||||
|
Reference::parse(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Reference<'static> {
|
||||||
|
type Error = Error<'static>;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
Reference::parse_owned(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because inference doesn't take `&String` to `&str`.
|
||||||
|
impl<'a> TryFrom<&'a String> for Reference<'a> {
|
||||||
|
type Error = Error<'a>;
|
||||||
|
|
||||||
|
fn try_from(value: &'a String) -> Result<Self, Self::Error> {
|
||||||
|
Reference::parse(value.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Reference<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(scheme) = self.scheme() {
|
||||||
|
write!(f, "{}:", scheme)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(authority) = self.authority() {
|
||||||
|
write!(f, "//{}", authority)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
write!(f, "{}", self.path())?;
|
||||||
|
|
||||||
|
if let Some(query) = self.query() {
|
||||||
|
write!(f, "?{}", query)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(frag) = self.fragment() {
|
||||||
|
write!(f, "#{}", frag)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use crate::RawStr;
|
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
|
/// ### Examples
|
||||||
///
|
///
|
||||||
|
@ -14,7 +18,7 @@ use crate::parse::IndexedStr;
|
||||||
/// use rocket::http::uri::Origin;
|
/// use rocket::http::uri::Origin;
|
||||||
///
|
///
|
||||||
/// let uri = Origin::parse("/a%20z/////b/c////////d").unwrap();
|
/// 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() {
|
/// for (i, segment) in segments.enumerate() {
|
||||||
/// match i {
|
/// match i {
|
||||||
/// 0 => assert_eq!(segment, "a z"),
|
/// 0 => assert_eq!(segment, "a z"),
|
||||||
|
@ -24,31 +28,44 @@ use crate::parse::IndexedStr;
|
||||||
/// _ => panic!("only four segments")
|
/// _ => panic!("only four segments")
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// # assert_eq!(uri.path_segments().len(), 4);
|
/// # assert_eq!(uri.path().segments().len(), 4);
|
||||||
/// # assert_eq!(uri.path_segments().count(), 4);
|
/// # assert_eq!(uri.path().segments().count(), 4);
|
||||||
/// # assert_eq!(uri.path_segments().next(), Some("a z"));
|
/// # assert_eq!(uri.path().segments().next(), Some("a z"));
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Segments<'o> {
|
pub struct Segments<'a, P: Part> {
|
||||||
pub(super) source: &'o RawStr,
|
pub(super) source: &'a RawStr,
|
||||||
pub(super) segments: &'o [IndexedStr<'static>],
|
pub(super) segments: &'a [P::Raw],
|
||||||
pub(super) pos: usize,
|
pub(super) pos: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An error interpreting a segment as a [`PathBuf`] component in
|
impl<P: Part> Segments<'_, P> {
|
||||||
/// [`Segments::to_path_buf()`].
|
#[doc(hidden)]
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[inline(always)]
|
||||||
pub enum PathError {
|
pub fn new<'a>(source: &'a RawStr, segments: &'a [P::Raw]) -> Segments<'a, P> {
|
||||||
/// The segment started with the wrapped invalid character.
|
Segments { source, segments, pos: 0, }
|
||||||
BadStart(char),
|
}
|
||||||
/// The segment contained the wrapped invalid character.
|
|
||||||
BadChar(char),
|
|
||||||
/// The segment ended with the wrapped invalid character.
|
|
||||||
BadEnd(char),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'o> Segments<'o> {
|
|
||||||
/// Returns the number of path segments left.
|
/// 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]
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
let max_pos = std::cmp::min(self.pos, self.segments.len());
|
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.
|
/// 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]
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
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]
|
#[inline]
|
||||||
pub fn skip(mut self, n: usize) -> Self {
|
pub fn skip(mut self, n: usize) -> Self {
|
||||||
self.pos = std::cmp::min(self.pos + n, self.segments.len());
|
self.pos = std::cmp::min(self.pos + n, self.segments.len());
|
||||||
self
|
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]
|
#[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)
|
self.segments.get(self.pos + n)
|
||||||
.map(|i| i.from_source(Some(self.source.as_str())))
|
.map(|i| i.from_source(Some(self.source.as_str())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if `self` is a prefix of `other`.
|
/// 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]
|
#[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() {
|
if self.len() > other.len() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -86,17 +163,17 @@ impl<'o> Segments<'o> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a `PathBuf` from `self`. The returned `PathBuf` is
|
/// 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.
|
/// any) is skipped.
|
||||||
///
|
///
|
||||||
/// For security purposes, if a segment meets any of the following
|
/// For security purposes, if a segment meets any of the following
|
||||||
/// conditions, an `Err` is returned indicating the condition met:
|
/// 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 ends with any of: `:`, `>`, `<`
|
||||||
/// * Decoded segment contains any of: `/`
|
/// * Decoded segment contains any of: `/`
|
||||||
/// * On Windows, 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
|
/// Additionally, if `allow_dotfiles` is `false`, an `Err` is returned if
|
||||||
/// the following condition is met:
|
/// the following condition is met:
|
||||||
|
@ -106,6 +183,21 @@ impl<'o> Segments<'o> {
|
||||||
/// As a result of these conditions, a `PathBuf` derived via `FromSegments`
|
/// 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
|
/// is safe to interpolate within, or use as a suffix of, a path without
|
||||||
/// additional checks.
|
/// 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> {
|
pub fn to_path_buf(&self, allow_dotfiles: bool) -> Result<PathBuf, PathError> {
|
||||||
let mut buf = PathBuf::new();
|
let mut buf = PathBuf::new();
|
||||||
for segment in self.clone() {
|
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)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'o> Iterator for Segments<'o> {
|
impl<'a> Segments<'a, Query> {
|
||||||
type Item = &'o str;
|
/// Returns the `n`th segment, 0-indexed, from the current position.
|
||||||
|
///
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
/// # Example
|
||||||
let item = self.get(0)?;
|
///
|
||||||
self.pos += 1;
|
/// ```rust
|
||||||
Some(item)
|
/// # #[macro_use] extern crate rocket;
|
||||||
}
|
/// let uri = uri!("/?foo=1&bar=hi+there&baaaz&cat=dog&=oh");
|
||||||
|
///
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
/// let segments = uri.query().unwrap().segments();
|
||||||
(self.len(), Some(self.len()))
|
/// assert_eq!(segments.get(0), Some(("foo", "1")));
|
||||||
}
|
/// assert_eq!(segments.get(1), Some(("bar", "hi there")));
|
||||||
|
/// assert_eq!(segments.get(2), Some(("baaaz", "")));
|
||||||
fn count(self) -> usize {
|
/// assert_eq!(segments.get(3), Some(("cat", "dog")));
|
||||||
self.len()
|
/// assert_eq!(segments.get(4), Some(("", "oh")));
|
||||||
}
|
/// assert_eq!(segments.get(5), None);
|
||||||
}
|
/// ```
|
||||||
|
|
||||||
/// 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.
|
|
||||||
#[inline]
|
#[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 (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 name = name.from_source(source);
|
||||||
let val = val.from_source(source);
|
let val = val.from_source(source);
|
||||||
Some((name, val))
|
Some((name, val))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'o> Iterator for QuerySegments<'o> {
|
macro_rules! impl_iterator {
|
||||||
type Item = (&'o str, &'o str);
|
($T:ty => $I:ty) => (
|
||||||
|
impl<'a> Iterator for Segments<'a, $T> {
|
||||||
|
type Item = $I;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let item = self.get(0)?;
|
let item = self.get(0)?;
|
||||||
self.pos += 1;
|
self.pos += 1;
|
||||||
Some(item)
|
Some(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||||
(self.len(), Some(self.len()))
|
(self.len(), Some(self.len()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn count(self) -> usize {
|
fn count(self) -> usize {
|
||||||
self.len()
|
self.len()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_iterator!(Path => &'a str);
|
||||||
|
impl_iterator!(Query => (&'a str, &'a str));
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::convert::From;
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::str::Utf8Error;
|
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use crate::RawStr;
|
|
||||||
use crate::ext::IntoOwned;
|
use crate::ext::IntoOwned;
|
||||||
use crate::parse::Extent;
|
use crate::uri::{Origin, Authority, Absolute, Reference, Asterisk};
|
||||||
use crate::uri::{Origin, Authority, Absolute, Error};
|
use crate::uri::error::{Error, TryFromUriError};
|
||||||
use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
|
||||||
|
|
||||||
/// An `enum` encapsulating any of the possible URI variants.
|
/// 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
|
/// In Rocket, this type will rarely be used directly. Instead, you will
|
||||||
/// typically encounter URIs via the [`Origin`] type. This is because all
|
/// typically encounter URIs via the [`Origin`] type. This is because all
|
||||||
/// incoming requests contain origin-type URIs.
|
/// incoming requests accepred by Rocket contain URIs in origin-form.
|
||||||
///
|
|
||||||
/// 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`].
|
|
||||||
///
|
///
|
||||||
/// ## Parsing
|
/// ## Parsing
|
||||||
///
|
///
|
||||||
|
@ -32,8 +20,7 @@ use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||||
/// compliant "request target" parser with limited liberties for real-world
|
/// compliant "request target" parser with limited liberties for real-world
|
||||||
/// deviations. In particular, the parser deviates as follows:
|
/// deviations. In particular, the parser deviates as follows:
|
||||||
///
|
///
|
||||||
/// * It accepts `%` characters without two trailing hex-digits unless it is
|
/// * It accepts `%` characters without two trailing hex-digits.
|
||||||
/// the only character in the URI.
|
|
||||||
///
|
///
|
||||||
/// * It accepts the following additional unencoded characters in query parts,
|
/// * It accepts the following additional unencoded characters in query parts,
|
||||||
/// to match real-world browser behavior:
|
/// to match real-world browser behavior:
|
||||||
|
@ -46,63 +33,102 @@ use crate::uri::encoding::{percent_encode, DEFAULT_ENCODE_SET};
|
||||||
/// methods of the internal structure.
|
/// methods of the internal structure.
|
||||||
///
|
///
|
||||||
/// [RFC 7230]: https://tools.ietf.org/html/rfc7230
|
/// [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)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum Uri<'a> {
|
pub enum Uri<'a> {
|
||||||
|
/// An asterisk: exactly `*`.
|
||||||
|
Asterisk(Asterisk),
|
||||||
/// An origin URI.
|
/// An origin URI.
|
||||||
Origin(Origin<'a>),
|
Origin(Origin<'a>),
|
||||||
/// An authority URI.
|
/// An authority URI.
|
||||||
Authority(Authority<'a>),
|
Authority(Authority<'a>),
|
||||||
/// An absolute URI.
|
/// An absolute URI.
|
||||||
Absolute(Absolute<'a>),
|
Absolute(Absolute<'a>),
|
||||||
/// An asterisk: exactly `*`.
|
/// A URI reference.
|
||||||
Asterisk,
|
Reference(Reference<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Uri<'a> {
|
impl<'a> Uri<'a> {
|
||||||
#[inline]
|
/// Parses the string `string` into a `Uri` of kind `T`.
|
||||||
pub(crate) unsafe fn raw_absolute(
|
///
|
||||||
source: Cow<'a, [u8]>,
|
/// This is identical to `T::parse(string).map(Uri::from)`.
|
||||||
scheme: Extent<&'a [u8]>,
|
///
|
||||||
path: Extent<&'a [u8]>,
|
/// `T` is typically one of [`Asterisk`], [`Origin`], [`Authority`],
|
||||||
query: Option<Extent<&'a [u8]>>,
|
/// [`Absolute`], or [`Reference`]. Parsing never allocates. Returns an
|
||||||
) -> Uri<'a> {
|
/// `Error` if `string` is not a valid URI of kind `T`.
|
||||||
let origin = Origin::raw(source.clone(), path, query);
|
///
|
||||||
Uri::Absolute(Absolute::raw(source.clone(), scheme, None, Some(origin)))
|
/// To perform an ambgiuous parse into _any_ valid URI type, use
|
||||||
}
|
/// [`Uri::parse_any()`].
|
||||||
|
|
||||||
/// Parses the string `string` into a `Uri`. Parsing will never allocate.
|
|
||||||
/// Returns an `Error` if `string` is not a valid URI.
|
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::{Uri, Origin};
|
||||||
///
|
///
|
||||||
/// // Parse a valid origin URI (note: in practice, use `Origin::parse()`).
|
/// // 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");
|
/// let origin = uri.origin().expect("origin URI");
|
||||||
/// assert_eq!(origin.path(), "/a/b/c");
|
/// assert_eq!(origin.path(), "/a/b/c");
|
||||||
/// assert_eq!(origin.query().unwrap(), "query");
|
/// 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.
|
/// // 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)
|
crate::parse::uri::from_str(string)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,13 +138,19 @@ impl<'a> Uri<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// 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());
|
/// 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());
|
/// assert!(uri.origin().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn origin(&self) -> Option<&Origin<'a>> {
|
pub fn origin(&self) -> Option<&Origin<'a>> {
|
||||||
|
@ -134,13 +166,19 @@ impl<'a> Uri<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// 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());
|
/// 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());
|
/// assert!(uri.authority().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn authority(&self) -> Option<&Authority<'a>> {
|
pub fn authority(&self) -> Option<&Authority<'a>> {
|
||||||
|
@ -156,13 +194,19 @@ impl<'a> Uri<'a> {
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// 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());
|
/// 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());
|
/// assert!(uri.absolute().is_none());
|
||||||
/// ```
|
/// ```
|
||||||
pub fn absolute(&self) -> Option<&Absolute<'a>> {
|
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
|
/// Returns the internal instance of `Reference` if `self` is a
|
||||||
/// percent-encoded.
|
/// `Uri::Reference`. Otherwise, returns `None`.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::http::uri::Uri;
|
/// use rocket::http::uri::{Uri, Absolute, Reference};
|
||||||
///
|
///
|
||||||
/// let encoded = Uri::percent_encode("hello?a=<b>hi</b>");
|
/// let uri = Uri::parse::<Reference>("foo/bar").expect("valid URI");
|
||||||
/// assert_eq!(encoded, "hello%3Fa%3D%3Cb%3Ehi%3C%2Fb%3E");
|
/// 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>
|
pub fn reference(&self) -> Option<&Reference<'a>> {
|
||||||
where S: AsRef<str> + ?Sized
|
match self {
|
||||||
{
|
Uri::Reference(ref inner) => Some(inner),
|
||||||
percent_encode::<DEFAULT_ENCODE_SET>(RawStr::new(string))
|
_ => None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a URL-decoded version of the string. If the percent encoded
|
|
||||||
/// values are not valid UTF-8, an `Err` is returned.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let decoded = Uri::percent_decode("/Hello%2C%20world%21".as_bytes());
|
|
||||||
/// assert_eq!(decoded.unwrap(), "/Hello, world!");
|
|
||||||
/// ```
|
|
||||||
pub fn percent_decode<S>(bytes: &S) -> Result<Cow<'_, str>, Utf8Error>
|
|
||||||
where S: AsRef<[u8]> + ?Sized
|
|
||||||
{
|
|
||||||
let decoder = percent_encoding::percent_decode(bytes.as_ref());
|
|
||||||
decoder.decode_utf8()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a URL-decoded version of the path. Any invalid UTF-8
|
|
||||||
/// percent-encoded byte sequences will be replaced <20> U+FFFD, the
|
|
||||||
/// replacement character.
|
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```rust
|
|
||||||
/// # extern crate rocket;
|
|
||||||
/// use rocket::http::uri::Uri;
|
|
||||||
///
|
|
||||||
/// let decoded = Uri::percent_decode_lossy("/Hello%2C%20world%21".as_bytes());
|
|
||||||
/// assert_eq!(decoded, "/Hello, world!");
|
|
||||||
/// ```
|
|
||||||
pub fn percent_decode_lossy<S>(bytes: &S) -> Cow<'_, str>
|
|
||||||
where S: AsRef<[u8]> + ?Sized
|
|
||||||
{
|
|
||||||
let decoder = percent_encoding::percent_decode(bytes.as_ref());
|
|
||||||
decoder.decode_utf8_lossy()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,26 +252,26 @@ pub(crate) unsafe fn as_utf8_unchecked(input: Cow<'_, [u8]>) -> Cow<'_, str> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<&'a str> for Uri<'a> {
|
// impl<'a> TryFrom<&'a str> for Uri<'a> {
|
||||||
type Error = Error<'a>;
|
// type Error = Error<'a>;
|
||||||
|
//
|
||||||
#[inline]
|
// #[inline]
|
||||||
fn try_from(string: &'a str) -> Result<Uri<'a>, Self::Error> {
|
// fn try_from(string: &'a str) -> Result<Uri<'a>, Self::Error> {
|
||||||
Uri::parse(string)
|
// Uri::parse(string)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
impl TryFrom<String> for Uri<'static> {
|
// impl TryFrom<String> for Uri<'static> {
|
||||||
type Error = Error<'static>;
|
// type Error = Error<'static>;
|
||||||
|
//
|
||||||
#[inline]
|
// #[inline]
|
||||||
fn try_from(string: String) -> Result<Uri<'static>, Self::Error> {
|
// fn try_from(string: String) -> Result<Uri<'static>, Self::Error> {
|
||||||
// TODO: Potentially optimize this like `Origin::parse_owned`.
|
// // TODO: Potentially optimize this like `Origin::parse_owned`.
|
||||||
Uri::parse(&string)
|
// Uri::parse(&string)
|
||||||
.map(|u| u.into_owned())
|
// .map(|u| u.into_owned())
|
||||||
.map_err(|e| e.into_owned())
|
// .map_err(|e| e.into_owned())
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl IntoOwned for Uri<'_> {
|
impl IntoOwned for Uri<'_> {
|
||||||
type Owned = Uri<'static>;
|
type Owned = Uri<'static>;
|
||||||
|
@ -266,7 +281,8 @@ impl IntoOwned for Uri<'_> {
|
||||||
Uri::Origin(origin) => Uri::Origin(origin.into_owned()),
|
Uri::Origin(origin) => Uri::Origin(origin.into_owned()),
|
||||||
Uri::Authority(authority) => Uri::Authority(authority.into_owned()),
|
Uri::Authority(authority) => Uri::Authority(authority.into_owned()),
|
||||||
Uri::Absolute(absolute) => Uri::Absolute(absolute.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::Origin(ref origin) => write!(f, "{}", origin),
|
||||||
Uri::Authority(ref authority) => write!(f, "{}", authority),
|
Uri::Authority(ref authority) => write!(f, "{}", authority),
|
||||||
Uri::Absolute(ref absolute) => write!(f, "{}", absolute),
|
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 {
|
macro_rules! impl_uri_from {
|
||||||
($type:ident) => (
|
($T:ident $(<$lt:lifetime>)?) => (
|
||||||
impl<'a> From<$type<'a>> for Uri<'a> {
|
impl<'a> From<$T $(<$lt>)?> for Uri<'a> {
|
||||||
fn from(other: $type<'a>) -> Uri<'a> {
|
fn from(other: $T $(<$lt>)?) -> Uri<'a> {
|
||||||
Uri::$type(other)
|
Uri::$T(other)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<Uri<'a>> for $type<'a> {
|
impl<'a> TryFrom<Uri<'a>> for $T $(<$lt>)? {
|
||||||
type Error = TryFromUriError;
|
type Error = TryFromUriError;
|
||||||
|
|
||||||
fn try_from(uri: Uri<'a>) -> Result<Self, Self::Error> {
|
fn try_from(uri: Uri<'a>) -> Result<Self, Self::Error> {
|
||||||
match uri {
|
match uri {
|
||||||
Uri::$type(inner) => Ok(inner),
|
Uri::$T(inner) => Ok(inner),
|
||||||
_ => Err(TryFromUriError(()))
|
_ => 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!(Origin<'a>);
|
||||||
impl_uri_from!(Authority);
|
impl_uri_from!(Authority<'a>);
|
||||||
impl_uri_from!(Absolute);
|
impl_uri_from!(Absolute<'a>);
|
||||||
|
impl_uri_from!(Reference<'a>);
|
||||||
|
impl_uri_from!(Asterisk);
|
||||||
|
|
|
@ -164,7 +164,7 @@ impl Catcher {
|
||||||
|
|
||||||
Catcher {
|
Catcher {
|
||||||
name: None,
|
name: None,
|
||||||
base: uri::Origin::new("/", None::<&str>),
|
base: uri::Origin::ROOT,
|
||||||
handler: Box::new(handler),
|
handler: Box::new(handler),
|
||||||
code,
|
code,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::form::prelude::*;
|
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.
|
/// A form guard for parsing form types leniently.
|
||||||
///
|
///
|
||||||
|
|
|
@ -58,7 +58,7 @@ impl FileName {
|
||||||
/// '(', ')', '&', ';', '#', '?', '*'`.
|
/// '(', ')', '&', ';', '#', '?', '*'`.
|
||||||
///
|
///
|
||||||
/// On Windows (and non-Unix OSs), these are the characters `'.', '<', '>',
|
/// On Windows (and non-Unix OSs), these are the characters `'.', '<', '>',
|
||||||
/// ':', '"', '/', '\\', '|', '?', '*', ',', ';', '=', '(', ')', '&', '#'`,
|
/// ':', '"', '/', '\', '|', '?', '*', ',', ';', '=', '(', ')', '&', '#'`,
|
||||||
/// and the reserved names `"CON", "PRN", "AUX", "NUL", "COM1", "COM2",
|
/// and the reserved names `"CON", "PRN", "AUX", "NUL", "COM1", "COM2",
|
||||||
/// "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",
|
/// "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2",
|
||||||
/// "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"`.
|
/// "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"`.
|
||||||
|
@ -164,6 +164,33 @@ impl FileName {
|
||||||
Some(file_name)
|
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
|
/// The raw, unsanitized, potentially unsafe file name. Prefer to use
|
||||||
/// [`FileName::as_str()`], always.
|
/// [`FileName::as_str()`], always.
|
||||||
///
|
///
|
||||||
|
@ -171,8 +198,8 @@ impl FileName {
|
||||||
///
|
///
|
||||||
/// This method returns the file name exactly as it was specified by the
|
/// This method returns the file name exactly as it was specified by the
|
||||||
/// client. You should **_not_** use this name _unless_ you require the
|
/// client. You should **_not_** use this name _unless_ you require the
|
||||||
/// originally specified `filename` _and_ it is known to contain special,
|
/// originally specified `filename` _and_ it is known not to contain
|
||||||
/// potentially dangerous characters, _and_:
|
/// special, potentially dangerous characters, _and_:
|
||||||
///
|
///
|
||||||
/// 1. All clients are known to be trusted, perhaps because the server
|
/// 1. All clients are known to be trusted, perhaps because the server
|
||||||
/// only runs locally, serving known, local requests, or...
|
/// only runs locally, serving known, local requests, or...
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use crate::form::prelude::*;
|
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.
|
/// A form guard for parsing form types strictly.
|
||||||
///
|
///
|
||||||
|
|
|
@ -45,8 +45,7 @@ impl<'c> LocalRequest<'c> {
|
||||||
{
|
{
|
||||||
// Try to parse `uri` into an `Origin`, storing whether it's good.
|
// Try to parse `uri` into an `Origin`, storing whether it's good.
|
||||||
let uri_str = uri.to_string();
|
let uri_str = uri.to_string();
|
||||||
let try_origin = uri.try_into()
|
let try_origin = uri.try_into().map_err(|_| Origin::path_only(uri_str));
|
||||||
.map_err(|_| Origin::new::<_, &'static str>(uri_str, None));
|
|
||||||
|
|
||||||
// Create a request. We'll handle bad URIs later, in `_dispatch`.
|
// Create a request. We'll handle bad URIs later, in `_dispatch`.
|
||||||
let origin = try_origin.clone().unwrap_or_else(|bad| bad);
|
let origin = try_origin.clone().unwrap_or_else(|bad| bad);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::path::PathBuf;
|
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.
|
/// 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
|
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> {
|
impl<'a, T: FromParam<'a>> FromParam<'a> for Result<T, T::Error> {
|
||||||
type Error = std::convert::Infallible;
|
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
|
/// Parses an instance of `Self` from many dynamic path parameter strings or
|
||||||
/// returns an `Error` if one cannot be parsed.
|
/// 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;
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn from_segments(segments: Segments<'r>) -> Result<Segments<'r>, Self::Error> {
|
fn from_segments(segments: Self) -> Result<Self, Self::Error> {
|
||||||
Ok(segments)
|
Ok(segments)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -307,7 +319,7 @@ impl<'r> FromSegments<'r> for Segments<'r> {
|
||||||
impl FromSegments<'_> for PathBuf {
|
impl FromSegments<'_> for PathBuf {
|
||||||
type Error = PathError;
|
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)
|
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;
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
#[inline]
|
#[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) {
|
match T::from_segments(segments) {
|
||||||
Ok(val) => Ok(Ok(val)),
|
Ok(val) => Ok(Ok(val)),
|
||||||
Err(e) => Ok(Err(e)),
|
Err(e) => Ok(Err(e)),
|
||||||
|
@ -328,7 +340,7 @@ impl<'r, T: FromSegments<'r>> FromSegments<'r> for Option<T> {
|
||||||
type Error = std::convert::Infallible;
|
type Error = std::convert::Infallible;
|
||||||
|
|
||||||
#[inline]
|
#[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) {
|
match T::from_segments(segments) {
|
||||||
Ok(val) => Ok(Some(val)),
|
Ok(val) => Ok(Some(val)),
|
||||||
Err(_) => Ok(None)
|
Err(_) => Ok(None)
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use std::{ops::RangeFrom, sync::Arc};
|
|
||||||
use std::net::{IpAddr, SocketAddr};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::fmt;
|
use std::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 yansi::Paint;
|
||||||
use state::{Container, Storage};
|
use state::{Container, Storage};
|
||||||
|
@ -14,7 +13,7 @@ use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
||||||
use crate::form::{self, ValueField, FromForm};
|
use crate::form::{self, ValueField, FromForm};
|
||||||
|
|
||||||
use crate::{Rocket, Route, Orbit};
|
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::{Method, Header, HeaderMap};
|
||||||
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||||
use crate::data::Limits;
|
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
|
/// 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.
|
/// point for the currently matched route, if they exist. Used by codegen.
|
||||||
#[inline]
|
#[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()
|
let mount_segments = self.route()
|
||||||
.map(|r| r.uri.metadata.base_segs.len())
|
.map(|r| r.uri.metadata.base_segs.len())
|
||||||
.unwrap_or(0);
|
.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.
|
// Retrieves the pre-parsed query items. Used by matching and codegen.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn query_fields(&self) -> impl Iterator<Item = ValueField<'_>> {
|
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
|
/// 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>> {
|
) -> Result<Request<'r>, Error<'r>> {
|
||||||
// Get a copy of the URI (only supports path-and-query) for later use.
|
// 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()) {
|
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)),
|
_ => return Err(Error::InvalidUri(h_uri)),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -846,8 +848,10 @@ impl<'r> Request<'r> {
|
||||||
None => return Err(Error::BadMethod(h_method))
|
None => return Err(Error::BadMethod(h_method))
|
||||||
};
|
};
|
||||||
|
|
||||||
// We need to re-parse the URI since we don't trust Hyper... :(
|
// In debug, make sure we agree with Hyper. Otherwise, cross our fingers
|
||||||
let uri = Origin::parse(uri)?;
|
// 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.
|
// Construct the request object.
|
||||||
let mut request = Request::new(rocket, method, uri);
|
let mut request = Request::new(rocket, method, uri);
|
||||||
|
|
|
@ -2,7 +2,7 @@ use std::convert::TryInto;
|
||||||
|
|
||||||
use crate::request::Request;
|
use crate::request::Request;
|
||||||
use crate::response::{self, Response, Responder};
|
use crate::response::{self, Response, Responder};
|
||||||
use crate::http::uri::Uri;
|
use crate::http::uri::Reference;
|
||||||
use crate::http::Status;
|
use crate::http::Status;
|
||||||
|
|
||||||
/// An empty redirect response to a given URL.
|
/// An empty redirect response to a given URL.
|
||||||
|
@ -11,19 +11,19 @@ use crate::http::Status;
|
||||||
///
|
///
|
||||||
/// # Usage
|
/// # Usage
|
||||||
///
|
///
|
||||||
/// All constructors accept a generic type of `T: TryInto<Uri<'static>>`. Among
|
/// All constructors accept a generic type of `T: TryInto<Reference<'static>>`.
|
||||||
/// the candidate types are:
|
/// Among the candidate types are:
|
||||||
///
|
///
|
||||||
/// * `String`
|
/// * `String`, `&'static str`
|
||||||
/// * `&'static str`
|
|
||||||
/// * [`Origin`](crate::http::uri::Origin)
|
/// * [`Origin`](crate::http::uri::Origin)
|
||||||
/// * [`Authority`](crate::http::uri::Authority)
|
/// * [`Authority`](crate::http::uri::Authority)
|
||||||
/// * [`Absolute`](crate::http::uri::Absolute)
|
/// * [`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
|
/// Any non-`'static` strings must first be allocated using `.to_string()` or
|
||||||
/// similar before being passed to a `Redirect` constructor. When redirecting to
|
/// 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
|
/// ```rust
|
||||||
/// # #[macro_use] extern crate rocket;
|
/// # #[macro_use] extern crate rocket;
|
||||||
|
@ -36,14 +36,19 @@ use crate::http::Status;
|
||||||
///
|
///
|
||||||
/// #[get("/hi/<name>/<age>")]
|
/// #[get("/hi/<name>/<age>")]
|
||||||
/// fn hi(name: String, age: u8) -> Redirect {
|
/// 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
|
/// [`Origin`]: crate::http::uri::Origin
|
||||||
/// [`uri!`]: ../macro.uri.html
|
/// [`uri!`]: ../macro.uri.html
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Redirect(Status, Option<Uri<'static>>);
|
pub struct Redirect(Status, Option<Reference<'static>>);
|
||||||
|
|
||||||
impl Redirect {
|
impl Redirect {
|
||||||
/// Construct a temporary "see other" (303) redirect response. This is the
|
/// Construct a temporary "see other" (303) redirect response. This is the
|
||||||
|
@ -54,34 +59,35 @@ impl Redirect {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # let query = "foo";
|
/// let redirect = Redirect::to(uri!("/foo/bar"));
|
||||||
/// let redirect = Redirect::to("/other_url");
|
/// let redirect = Redirect::to(uri!("https://domain.com#foo"));
|
||||||
/// let redirect = Redirect::to(format!("https://google.com/{}", query));
|
|
||||||
/// ```
|
/// ```
|
||||||
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())
|
Redirect(Status::SeeOther, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct a "temporary" (307) redirect response. This response instructs
|
/// Construct a "temporary" (307) redirect response. This response instructs
|
||||||
/// the client to reissue the current request to a different URL,
|
/// the client to reissue the current request to a different URL,
|
||||||
/// maintaining the contents of the request identically. This means that,
|
/// maintaining the contents of the request identically. This means that,
|
||||||
/// for example, a `POST` request will be resent, contents included, to the
|
/// for example, a `POST` request will be resent, contents included, to the
|
||||||
/// requested URL.
|
/// requested URL.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use rocket::response::Redirect;
|
/// # #[macro_use] extern crate rocket;
|
||||||
///
|
/// use rocket::response::Redirect;
|
||||||
/// # let query = "foo";
|
///
|
||||||
/// let redirect = Redirect::temporary("/other_url");
|
/// let redirect = Redirect::temporary(uri!("some/other/path"));
|
||||||
/// let redirect = Redirect::temporary(format!("https://google.com/{}", query));
|
/// let redirect = Redirect::temporary(uri!("https://rocket.rs?foo"));
|
||||||
/// ```
|
/// let redirect = Redirect::temporary(format!("some-{}-thing", "crazy"));
|
||||||
pub fn temporary<U: TryInto<Uri<'static>>>(uri: U) -> Redirect {
|
/// ```
|
||||||
Redirect(Status::TemporaryRedirect, uri.try_into().ok())
|
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
|
/// Construct a "permanent" (308) redirect response. This redirect must only
|
||||||
/// be used for permanent redirects as it is cached by clients. This
|
/// be used for permanent redirects as it is cached by clients. This
|
||||||
|
@ -93,13 +99,13 @@ impl Redirect {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # let query = "foo";
|
/// let redirect = Redirect::permanent(uri!("/other_url"));
|
||||||
/// let redirect = Redirect::permanent("/other_url");
|
/// let redirect = Redirect::permanent(format!("some-{}-thing", "crazy"));
|
||||||
/// let redirect = Redirect::permanent(format!("https://google.com/{}", query));
|
|
||||||
/// ```
|
/// ```
|
||||||
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())
|
Redirect(Status::PermanentRedirect, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,13 +119,13 @@ impl Redirect {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # let query = "foo";
|
/// let redirect = Redirect::found(uri!("/other_url"));
|
||||||
/// let redirect = Redirect::found("/other_url");
|
/// let redirect = Redirect::found(format!("some-{}-thing", "crazy"));
|
||||||
/// let redirect = Redirect::found(format!("https://google.com/{}", query));
|
|
||||||
/// ```
|
/// ```
|
||||||
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())
|
Redirect(Status::Found, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,13 +137,13 @@ impl Redirect {
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
|
/// # #[macro_use] extern crate rocket;
|
||||||
/// use rocket::response::Redirect;
|
/// use rocket::response::Redirect;
|
||||||
///
|
///
|
||||||
/// # let query = "foo";
|
/// let redirect = Redirect::moved(uri!("here"));
|
||||||
/// let redirect = Redirect::moved("/other_url");
|
/// let redirect = Redirect::moved(format!("some-{}-thing", "crazy"));
|
||||||
/// let redirect = Redirect::moved(format!("https://google.com/{}", query));
|
|
||||||
/// ```
|
/// ```
|
||||||
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())
|
Redirect(Status::MovedPermanently, uri.try_into().ok())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -529,9 +529,9 @@ fn log_items<T, I, B, O>(e: &str, t: &str, items: I, base: B, origin: O)
|
||||||
}
|
}
|
||||||
|
|
||||||
items.sort_by_key(|i| origin(i).path().as_str().chars().count());
|
items.sort_by_key(|i| origin(i).path().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().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));
|
items.iter().for_each(|i| launch_info_!("{}", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -171,7 +171,7 @@ impl<'a> RouteUri<'a> {
|
||||||
self.origin.path().as_str()
|
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
|
/// # Example
|
||||||
///
|
///
|
||||||
|
@ -180,10 +180,18 @@ impl<'a> RouteUri<'a> {
|
||||||
/// use rocket::http::Method;
|
/// use rocket::http::Method;
|
||||||
/// # use rocket::route::dummy_handler as handler;
|
/// # 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);
|
/// 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();
|
/// 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)]
|
#[inline(always)]
|
||||||
pub fn query(&self) -> Option<&str> {
|
pub fn query(&self) -> Option<&str> {
|
||||||
|
@ -241,17 +249,17 @@ impl<'a> RouteUri<'a> {
|
||||||
|
|
||||||
impl Metadata {
|
impl Metadata {
|
||||||
fn from(base: &Origin<'_>, origin: &Origin<'_>) -> Self {
|
fn from(base: &Origin<'_>, origin: &Origin<'_>) -> Self {
|
||||||
let base_segs = base.raw_path_segments()
|
let base_segs = base.path().raw_segments()
|
||||||
.map(Segment::from)
|
.map(Segment::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let path_segs = origin.raw_path_segments()
|
let path_segs = origin.path().raw_segments()
|
||||||
.map(Segment::from)
|
.map(Segment::from)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let query_segs = origin.raw_query_segments()
|
let query_segs = origin.query()
|
||||||
.map(Segment::from)
|
.map(|q| q.raw_segments().map(Segment::from).collect::<Vec<_>>())
|
||||||
.collect::<Vec<_>>();
|
.unwrap_or_default();
|
||||||
|
|
||||||
let static_query_fields = query_segs.iter().filter(|s| !s.dynamic)
|
let static_query_fields = query_segs.iter().filter(|s| !s.dynamic)
|
||||||
.map(|s| ValueField::parse(&s.value))
|
.map(|s| ValueField::parse(&s.value))
|
||||||
|
|
|
@ -106,7 +106,7 @@ impl Route {
|
||||||
|
|
||||||
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
|
fn paths_match(route: &Route, req: &Request<'_>) -> bool {
|
||||||
let route_segments = &route.uri.metadata.path_segs;
|
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 {
|
if route.uri.metadata.trailing_path {
|
||||||
// The last route segment can be trailing, which is allowed to be empty.
|
// 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()));
|
.map(|(k, v)| (k.as_str(), v.as_str()));
|
||||||
|
|
||||||
for route_seg in route_query_fields {
|
for route_seg in route_query_fields {
|
||||||
if !req.uri().query_segments().any(|req_seg| req_seg == route_seg) {
|
if let Some(query) = req.uri().query() {
|
||||||
trace_!("request {} missing static query {:?}", req, route_seg);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,7 +186,7 @@ impl Collide for Catcher {
|
||||||
/// * Have the same status code or are both defaults.
|
/// * Have the same status code or are both defaults.
|
||||||
fn collides_with(&self, other: &Self) -> bool {
|
fn collides_with(&self, other: &Self) -> bool {
|
||||||
self.code == other.code
|
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()`.
|
/// * Its base is a prefix of the normalized/decoded `req.path()`.
|
||||||
pub(crate) fn matches(&self, status: Status, req: &Request<'_>) -> bool {
|
pub(crate) fn matches(&self, status: Status, req: &Request<'_>) -> bool {
|
||||||
self.code.map_or(true, |code| code == status.code)
|
self.code.map_or(true, |code| code == status.code)
|
||||||
&& self.base.path_segments().prefix_of(req.uri().path_segments())
|
&& self.base.path().segments().prefix_of(req.uri().path().segments())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ impl Router {
|
||||||
pub fn add_catcher(&mut self, catcher: Catcher) {
|
pub fn add_catcher(&mut self, catcher: Catcher) {
|
||||||
let catchers = self.catchers.entry(catcher.code).or_default();
|
let catchers = self.catchers.entry(catcher.code).or_default();
|
||||||
catchers.push(catcher);
|
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]
|
#[inline]
|
||||||
|
@ -68,7 +68,7 @@ impl Router {
|
||||||
(None, None) => None,
|
(None, None) => None,
|
||||||
(None, c@Some(_)) | (c@Some(_), None) => c,
|
(None, c@Some(_)) | (c@Some(_), None) => c,
|
||||||
(Some(a), Some(b)) => {
|
(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)
|
Some(b)
|
||||||
} else {
|
} else {
|
||||||
Some(a)
|
Some(a)
|
||||||
|
|
|
@ -87,7 +87,7 @@ async fn hyper_service_fn(
|
||||||
// fabricate one. This is weird. We should let the user know
|
// fabricate one. This is weird. We should let the user know
|
||||||
// that we failed to parse a request (by invoking some special
|
// that we failed to parse a request (by invoking some special
|
||||||
// handler) instead of doing this.
|
// 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;
|
let r = rocket.handle_error(Status::BadRequest, &dummy).await;
|
||||||
return rocket.send_response(r, tx).await;
|
return rocket.send_response(r, tx).await;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
|
|
||||||
#[get("/google")]
|
#[get("/http")]
|
||||||
fn google() -> Redirect {
|
fn http() -> Redirect {
|
||||||
Redirect::to("https://www.google.com")
|
Redirect::to(uri!("http://rocket.rs"))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/rocket")]
|
#[get("/rocket")]
|
||||||
|
@ -18,13 +18,13 @@ mod test_absolute_uris_okay {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn redirect_works() {
|
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");
|
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");
|
let location = response.headers().get_one("Location");
|
||||||
assert_eq!(location, Some("https://rocket.rs:80"));
|
assert_eq!(location, Some("https://rocket.rs:80"));
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,13 +6,13 @@ mod inner {
|
||||||
|
|
||||||
#[rocket::get("/")]
|
#[rocket::get("/")]
|
||||||
pub fn hello() -> String {
|
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>")]
|
#[rocket::get("/<name>")]
|
||||||
fn hello_name(name: String) -> String {
|
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> {
|
fn rocket() -> Rocket<Build> {
|
||||||
|
|
|
@ -1,29 +1,29 @@
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use] extern crate rocket;
|
||||||
|
|
||||||
use rocket::http::uri::Segments;
|
use rocket::http::uri::{Segments, fmt::Path};
|
||||||
|
|
||||||
#[get("/test/<path..>")]
|
#[get("/test/<path..>")]
|
||||||
fn test(path: Segments<'_>) -> String {
|
fn test(path: Segments<'_, Path>) -> String {
|
||||||
path.collect::<Vec<_>>().join("/")
|
path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/two/<path..>")]
|
#[get("/two/<path..>")]
|
||||||
fn two(path: Segments<'_>) -> String {
|
fn two(path: Segments<'_, Path>) -> String {
|
||||||
path.collect::<Vec<_>>().join("/")
|
path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/one/two/<path..>")]
|
#[get("/one/two/<path..>")]
|
||||||
fn one_two(path: Segments<'_>) -> String {
|
fn one_two(path: Segments<'_, Path>) -> String {
|
||||||
path.collect::<Vec<_>>().join("/")
|
path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<path..>", rank = 2)]
|
#[get("/<path..>", rank = 2)]
|
||||||
fn none(path: Segments<'_>) -> String {
|
fn none(path: Segments<'_, Path>) -> String {
|
||||||
path.collect::<Vec<_>>().join("/")
|
path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/static/<user>/is/<path..>")]
|
#[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("/")
|
user + "/is/" + &path.collect::<Vec<_>>().join("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
use rocket::{Rocket, Build};
|
use rocket::{Rocket, Build};
|
||||||
use rocket::response::Redirect;
|
use rocket::response::Redirect;
|
||||||
use rocket::http::uri::Uri;
|
|
||||||
|
|
||||||
const NAME: &str = "John[]|\\%@^";
|
const NAME: &str = "John[]|\\%@^";
|
||||||
|
|
||||||
|
@ -13,12 +12,12 @@ fn hello(name: String) -> String {
|
||||||
|
|
||||||
#[get("/raw")]
|
#[get("/raw")]
|
||||||
fn raw_redirect() -> Redirect {
|
fn raw_redirect() -> Redirect {
|
||||||
Redirect::to(format!("/hello/{}", Uri::percent_encode(NAME)))
|
Redirect::to(uri!(hello(NAME)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/uri")]
|
#[get("/uri")]
|
||||||
fn uri_redirect() -> Redirect {
|
fn uri_redirect() -> Redirect {
|
||||||
Redirect::to(uri!(hello: NAME))
|
Redirect::to(uri!(hello(NAME)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rocket() -> Rocket<Build> {
|
fn rocket() -> Rocket<Build> {
|
||||||
|
@ -28,7 +27,7 @@ fn rocket() -> Rocket<Build> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rocket::local::blocking::Client;
|
use rocket::local::blocking::Client;
|
||||||
use rocket::http::{Status, uri::Uri};
|
use rocket::http::Status;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn uri_percent_encoding_redirect() {
|
fn uri_percent_encoding_redirect() {
|
||||||
|
@ -49,8 +48,7 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn uri_percent_encoding_get() {
|
fn uri_percent_encoding_get() {
|
||||||
let client = Client::debug(rocket()).unwrap();
|
let client = Client::debug(rocket()).unwrap();
|
||||||
let name = Uri::percent_encode(NAME);
|
let response = client.get(uri!(hello(NAME))).dispatch();
|
||||||
let response = client.get(format!("/hello/{}", name)).dispatch();
|
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
assert_eq!(response.into_string().unwrap(), format!("Hello, {}!", NAME));
|
assert_eq!(response.into_string().unwrap(), format!("Hello, {}!", NAME));
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ mod paste_id;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use rocket::State;
|
|
||||||
use rocket::data::{Data, ToByteUnit};
|
use rocket::data::{Data, ToByteUnit};
|
||||||
use rocket::http::uri::Absolute;
|
use rocket::http::uri::Absolute;
|
||||||
use rocket::response::content::Plain;
|
use rocket::response::content::Plain;
|
||||||
|
@ -13,17 +12,15 @@ use rocket::tokio::fs::{self, File};
|
||||||
|
|
||||||
use crate::paste_id::PasteId;
|
use crate::paste_id::PasteId;
|
||||||
|
|
||||||
const HOST: &str = "http://localhost:8000";
|
const HOST: Absolute<'static> = uri!("http://localhost:8000");
|
||||||
|
|
||||||
const ID_LENGTH: usize = 3;
|
const ID_LENGTH: usize = 3;
|
||||||
|
|
||||||
#[post("/", data = "<paste>")]
|
#[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);
|
let id = PasteId::new(ID_LENGTH);
|
||||||
paste.open(128.kibibytes()).into_file(id.file_path()).await?;
|
paste.open(128.kibibytes()).into_file(id.file_path()).await?;
|
||||||
|
Ok(uri!(HOST, retrieve(id)).to_string())
|
||||||
// TODO: Ok(uri!(HOST, retrieve: id))
|
|
||||||
let host = host.inner().clone();
|
|
||||||
Ok(host.with_origin(uri!(retrieve: id)).to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/<id>")]
|
#[get("/<id>")]
|
||||||
|
@ -57,6 +54,5 @@ fn index() -> &'static str {
|
||||||
#[launch]
|
#[launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.manage(Absolute::parse(HOST).expect("valid host"))
|
|
||||||
.mount("/", routes![index, upload, delete, retrieve])
|
.mount("/", routes![index, upload, delete, retrieve])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use rocket::http::uri::{self, FromUriParam};
|
use rocket::http::uri::fmt;
|
||||||
use rocket::request::FromParam;
|
use rocket::request::FromParam;
|
||||||
use rand::{self, Rng};
|
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>;
|
type Target = PasteId<'a>;
|
||||||
|
|
||||||
fn from_uri_param(param: &'a str) -> Self::Target {
|
fn from_uri_param(param: &'a str) -> Self::Target {
|
||||||
|
|
|
@ -25,7 +25,7 @@ fn upload_paste(client: &Client, body: &str) -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn download_paste(client: &Client, id: &str) -> Option<String> {
|
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() {
|
if response.status().class().is_success() {
|
||||||
Some(response.into_string().unwrap())
|
Some(response.into_string().unwrap())
|
||||||
} else {
|
} else {
|
||||||
|
@ -34,7 +34,7 @@ fn download_paste(client: &Client, id: &str) -> Option<String> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_paste(client: &Client, id: &str) {
|
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);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,7 @@ async fn test_one_hi_per_second() {
|
||||||
// Listen for 1 second at 1 `hi` per 250ms, see if we get ~4 `hi`'s, then
|
// 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`.
|
// send a shutdown() signal, meaning we should get a `goodbye`.
|
||||||
let client = Client::tracked(super::rocket()).await.unwrap();
|
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 response = response.into_string();
|
||||||
let timer = time::sleep(Duration::from_secs(1));
|
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.");
|
assert_eq!(r.into_string().unwrap(), "Hi! Please log in before continuing.");
|
||||||
|
|
||||||
for name in &["Bob", "Charley", "Joe Roger"] {
|
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);
|
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.status(), Status::Ok);
|
||||||
assert_eq!(r.into_string().unwrap(), "Hello, Sergio!");
|
assert_eq!(r.into_string().unwrap(), "Hello, Sergio!");
|
||||||
}
|
}
|
||||||
|
@ -139,11 +139,11 @@ fn test_xml() {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_either() {
|
fn test_either() {
|
||||||
let client = Client::tracked(super::rocket()).unwrap();
|
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.content_type().unwrap(), ContentType::JSON);
|
||||||
assert_eq!(r.into_string().unwrap(), "\"hi\"");
|
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.content_type().unwrap(), ContentType::MsgPack);
|
||||||
assert_eq!(r.into_bytes().unwrap(), &[162, 104, 105]);
|
assert_eq!(r.into_bytes().unwrap(), &[162, 104, 105]);
|
||||||
}
|
}
|
||||||
|
@ -155,13 +155,13 @@ use super::Kind;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_custom() {
|
fn test_custom() {
|
||||||
let client = Client::tracked(super::rocket()).unwrap();
|
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.");
|
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");
|
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.status(), Status::Unauthorized);
|
||||||
assert_eq!(r.content_type().unwrap(), ContentType::HTML);
|
assert_eq!(r.content_type().unwrap(), ContentType::HTML);
|
||||||
assert_eq!(r.into_string().unwrap(), "No no no!");
|
assert_eq!(r.into_string().unwrap(), "No no no!");
|
||||||
|
@ -175,7 +175,7 @@ fn test_custom() {
|
||||||
assert_eq!(response.status(), Status::Ok);
|
assert_eq!(response.status(), Status::Ok);
|
||||||
|
|
||||||
// Fetch it using `custom`.
|
// 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()));
|
assert_eq!(r.into_string(), Some(CONTENTS.into()));
|
||||||
|
|
||||||
// Delete it.
|
// Delete it.
|
||||||
|
|
|
@ -14,7 +14,7 @@ struct TemplateContext<'r> {
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn index() -> Redirect {
|
pub fn index() -> Redirect {
|
||||||
Redirect::to(uri!("/hbs", hello: name = "Your Name"))
|
Redirect::to(uri!("/hbs", hello(name = "Your Name")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/hello/<name>")]
|
#[get("/hello/<name>")]
|
||||||
|
@ -38,7 +38,7 @@ pub fn about() -> Template {
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
pub fn not_found(req: &Request<'_>) -> Template {
|
pub fn not_found(req: &Request<'_>) -> Template {
|
||||||
let mut map = std::collections::HashMap::new();
|
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)
|
Template::render("hbs/error/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ struct TemplateContext<'r> {
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
pub fn index() -> Redirect {
|
pub fn index() -> Redirect {
|
||||||
Redirect::to(uri!("/tera", hello: name = "Your Name"))
|
Redirect::to(uri!("/tera", hello(name = "Your Name")))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/hello/<name>")]
|
#[get("/hello/<name>")]
|
||||||
|
@ -35,7 +35,7 @@ pub fn about() -> Template {
|
||||||
#[catch(404)]
|
#[catch(404)]
|
||||||
pub fn not_found(req: &Request<'_>) -> Template {
|
pub fn not_found(req: &Request<'_>) -> Template {
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert("path", req.uri().path());
|
map.insert("path", req.uri().path().raw());
|
||||||
Template::render("tera/error/404", &map)
|
Template::render("tera/error/404", &map)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -492,23 +492,23 @@ URIs to `person` can be created as follows:
|
||||||
# fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
# fn person(id: Option<usize>, name: &str, age: Option<u8>) { /* .. */ }
|
||||||
|
|
||||||
// with unnamed parameters, in route path declaration order
|
// 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");
|
assert_eq!(mike.to_string(), "/101/Mike%20Smith?age=28");
|
||||||
|
|
||||||
// with named parameters, order irrelevant
|
// 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");
|
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");
|
assert_eq!(mike.to_string(), "/101/Mike?age=28");
|
||||||
|
|
||||||
// with a specific mount-point
|
// 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");
|
assert_eq!(mike.to_string(), "/api/101/Mike?age=28");
|
||||||
|
|
||||||
// with optional (defaultable) query parameters ignored
|
// 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");
|
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");
|
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
|
error: `person` route uri expects 3 parameters but 1 was supplied
|
||||||
--> examples/uri/main.rs:7:26
|
--> 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>
|
= 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
|
```rust,ignore
|
||||||
--> examples/uri/src/main.rs:7:31
|
--> examples/uri/src/main.rs:7:31
|
||||||
|
|
|
|
||||||
7 | let x = uri!(person: id = "10", name = "Mike Smith", age = Some(10));
|
7 | let x = uri!(person(id = "10", name = "Mike Smith", age = Some(10)));
|
||||||
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
|
| ^^^^ `FromUriParam<Path, &str>` is not implemented for `usize`
|
||||||
```
|
```
|
||||||
|
|
||||||
We recommend that you use `uri!` exclusively when constructing URIs to your
|
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..>")]
|
# #[post("/user/<id>?<details..>")]
|
||||||
# fn add_user(id: usize, details: UserDetails) { /* .. */ }
|
# 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");
|
assert_eq!(link.to_string(), "/user/120?age=20&nickname=Bob");
|
||||||
```
|
```
|
||||||
|
|
||||||
### Typed URI Parts
|
### Typed URI Parts
|
||||||
|
|
||||||
The [`UriPart`] trait categorizes types that mark a part of the URI as either a
|
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 `UriPart` are
|
[`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
|
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:
|
[`UriDisplay`] and [`FromUriParam`] bound a generic parameter by `Part`: `P:
|
||||||
UriPart`. This creates two instances of each trait: `UriDisplay<Query>` and
|
Part`. This creates two instances of each trait: `UriDisplay<Query>` and
|
||||||
`UriDisplay<Path>`, and `FromUriParam<Query>` and `FromUriParam<Path>`.
|
`UriDisplay<Path>`, and `FromUriParam<Query>` and `FromUriParam<Path>`.
|
||||||
|
|
||||||
As the names might imply, the `Path` version of the traits is used when
|
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_
|
/// 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`
|
/// 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.
|
/// 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");
|
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`:
|
parameters declared as `String`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# use rocket::http::uri::{FromUriParam, UriPart};
|
# use rocket::http::uri::fmt::{FromUriParam, Part};
|
||||||
# struct S;
|
# struct S;
|
||||||
# type String = 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;
|
type Target = &'a str;
|
||||||
# fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
|
# fn from_uri_param(s: &'a str) -> Self::Target { "hi" }
|
||||||
}
|
}
|
||||||
|
@ -679,13 +679,13 @@ use std::path::PathBuf;
|
||||||
#[get("/person/<id>/<details..>")]
|
#[get("/person/<id>/<details..>")]
|
||||||
fn person(id: usize, details: Option<PathBuf>) { /* .. */ }
|
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.
|
See the [`FromUriParam`] documentation for further details.
|
||||||
|
|
||||||
[`Origin`]: @api/rocket/http/uri/struct.Origin.html
|
[`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
|
[`Uri`]: @api/rocket/http/uri/enum.Uri.html
|
||||||
[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to
|
[`Redirect::to()`]: @api/rocket/response/struct.Redirect.html#method.to
|
||||||
[`uri!`]: @api/rocket/macro.uri.html
|
[`uri!`]: @api/rocket/macro.uri.html
|
||||||
|
|
Loading…
Reference in New Issue