mirror of https://github.com/rwf2/Rocket.git
Allow ignored route path segments.
Co-authored-by: timokoesters <timo@koesters.xyz>
This commit is contained in:
parent
23738446f0
commit
8e8fb4cae8
|
@ -66,23 +66,23 @@ fn parse_route(attr: RouteAttribute, function: syn::ItemFn) -> Result<Route> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect all of the dynamic segments in an `IndexSet`, checking for dups.
|
// Collect non-wild dynamic segments in an `IndexSet`, checking for dups.
|
||||||
let mut segments: IndexSet<Segment> = IndexSet::new();
|
let mut segments: IndexSet<Segment> = IndexSet::new();
|
||||||
fn dup_check<I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
|
fn dup_check<'a, I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
|
||||||
where I: Iterator<Item = Segment>
|
where I: Iterator<Item = &'a Segment>
|
||||||
{
|
{
|
||||||
for segment in iter.filter(|s| s.kind != Kind::Static) {
|
for segment in iter.filter(|s| s.is_dynamic()) {
|
||||||
let span = segment.span;
|
let span = segment.span;
|
||||||
if let Some(previous) = set.replace(segment) {
|
if let Some(previous) = set.replace(segment.clone()) {
|
||||||
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
|
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
|
||||||
.span_note(previous.span, "previous parameter with the same name here"))
|
.span_note(previous.span, "previous parameter with the same name here"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dup_check(&mut segments, attr.path.path.iter().cloned(), &mut diags);
|
dup_check(&mut segments, attr.path.path.iter().filter(|s| !s.is_wild()), &mut diags);
|
||||||
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter().cloned(), &mut diags));
|
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter(), &mut diags));
|
||||||
dup_check(&mut segments, attr.data.clone().map(|s| s.value.0).into_iter(), &mut diags);
|
dup_check(&mut segments, attr.data.as_ref().map(|s| &s.value.0).into_iter(), &mut diags);
|
||||||
|
|
||||||
// Check the validity of function arguments.
|
// Check the validity of function arguments.
|
||||||
let mut inputs = vec![];
|
let mut inputs = vec![];
|
||||||
|
|
|
@ -3,11 +3,11 @@ use std::hash::{Hash, Hasher};
|
||||||
use devise::{syn, Diagnostic, ext::SpanDiagnosticExt};
|
use devise::{syn, Diagnostic, ext::SpanDiagnosticExt};
|
||||||
use crate::proc_macro2::Span;
|
use crate::proc_macro2::Span;
|
||||||
|
|
||||||
use crate::http::uri::{UriPart, Path};
|
use crate::http::uri::{self, UriPart};
|
||||||
use crate::http::route::RouteSegment;
|
use crate::http::route::RouteSegment;
|
||||||
use crate::proc_macro_ext::{Diagnostics, StringLit, PResult, DResult};
|
use crate::proc_macro_ext::{Diagnostics, StringLit, PResult, DResult};
|
||||||
|
|
||||||
pub use crate::http::route::{Error, Kind, Source};
|
pub use crate::http::route::{Error, Kind};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Segment {
|
pub struct Segment {
|
||||||
|
@ -18,6 +18,14 @@ pub struct Segment {
|
||||||
pub index: Option<usize>,
|
pub index: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||||
|
pub enum Source {
|
||||||
|
Path,
|
||||||
|
Query,
|
||||||
|
Data,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
impl Segment {
|
impl Segment {
|
||||||
fn from<P: UriPart>(segment: RouteSegment<'_, P>, span: Span) -> Segment {
|
fn from<P: UriPart>(segment: RouteSegment<'_, P>, span: Span) -> Segment {
|
||||||
let source = match P::DELIMITER {
|
let source = match P::DELIMITER {
|
||||||
|
@ -29,6 +37,17 @@ impl Segment {
|
||||||
let (kind, index) = (segment.kind, segment.index);
|
let (kind, index) = (segment.kind, segment.index);
|
||||||
Segment { span, kind, source, index, name: segment.name.into_owned() }
|
Segment { span, kind, source, index, name: segment.name.into_owned() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_wild(&self) -> bool {
|
||||||
|
self.name == "_"
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_dynamic(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Static => false,
|
||||||
|
Kind::Single | Kind::Multi => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&syn::Ident> for Segment {
|
impl From<&syn::Ident> for Segment {
|
||||||
|
@ -80,35 +99,33 @@ fn into_diagnostic(
|
||||||
) -> Diagnostic {
|
) -> Diagnostic {
|
||||||
let seg_span = subspan(segment, source, span);
|
let seg_span = subspan(segment, source, span);
|
||||||
match error {
|
match error {
|
||||||
Error::Empty => {
|
Error::Empty => seg_span.error(error.to_string()),
|
||||||
seg_span.error("parameter names cannot be empty")
|
Error::Ident(_) => {
|
||||||
}
|
seg_span.error(error.to_string())
|
||||||
Error::Ident(name) => {
|
|
||||||
seg_span.error(format!("`{}` is not a valid identifier", name))
|
|
||||||
.help("parameter names must be valid identifiers")
|
.help("parameter names must be valid identifiers")
|
||||||
}
|
}
|
||||||
Error::Ignored => {
|
Error::Ignored => {
|
||||||
seg_span.error("parameters must be named")
|
seg_span.error(error.to_string())
|
||||||
.help("use a name such as `_guard` or `_param`")
|
.help("use a name such as `_guard` or `_param`")
|
||||||
}
|
}
|
||||||
Error::MissingClose => {
|
Error::MissingClose => {
|
||||||
seg_span.error("parameter is missing a closing bracket")
|
seg_span.error(error.to_string())
|
||||||
.help(format!("did you mean '{}>'?", segment))
|
.help(format!("did you mean '{}>'?", segment))
|
||||||
}
|
}
|
||||||
Error::Malformed => {
|
Error::Malformed => {
|
||||||
seg_span.error("malformed parameter or identifier")
|
seg_span.error(error.to_string())
|
||||||
.help("parameters must be of the form '<param>'")
|
.help("parameters must be of the form '<param>'")
|
||||||
.help("identifiers cannot contain '<' or '>'")
|
.help("identifiers cannot contain '<' or '>'")
|
||||||
}
|
}
|
||||||
Error::Uri => {
|
Error::Uri => {
|
||||||
seg_span.error("component contains invalid URI characters")
|
seg_span.error(error.to_string())
|
||||||
.note("components cannot contain reserved characters")
|
.note("components cannot contain reserved characters")
|
||||||
.help("reserved characters include: '%', '+', '&', etc.")
|
.help("reserved characters include: '%', '+', '&', etc.")
|
||||||
}
|
}
|
||||||
Error::Trailing(multi) => {
|
Error::Trailing(multi) => {
|
||||||
let multi_span = subspan(multi, source, span);
|
let multi_span = subspan(multi, source, span);
|
||||||
trailspan(segment, source, span)
|
trailspan(segment, source, span)
|
||||||
.error("unexpected trailing text after a '..' param")
|
.error(error.to_string())
|
||||||
.help("a multi-segment param must be the final component")
|
.help("a multi-segment param must be the final component")
|
||||||
.span_note(multi_span, "multi-segment param is here")
|
.span_note(multi_span, "multi-segment param is here")
|
||||||
}
|
}
|
||||||
|
@ -116,7 +133,7 @@ fn into_diagnostic(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_data_segment(segment: &str, span: Span) -> PResult<Segment> {
|
pub fn parse_data_segment(segment: &str, span: Span) -> PResult<Segment> {
|
||||||
<RouteSegment<'_, Path>>::parse_one(segment)
|
<RouteSegment<'_, uri::Query>>::parse_one(segment)
|
||||||
.map(|segment| {
|
.map(|segment| {
|
||||||
let mut seg = Segment::from(segment, span);
|
let mut seg = Segment::from(segment, span);
|
||||||
seg.source = Source::Data;
|
seg.source = Source::Data;
|
||||||
|
@ -134,14 +151,17 @@ pub fn parse_segments<P: UriPart>(
|
||||||
let mut diags = Diagnostics::new();
|
let mut diags = Diagnostics::new();
|
||||||
|
|
||||||
for result in <RouteSegment<'_, P>>::parse_many(string) {
|
for result in <RouteSegment<'_, P>>::parse_many(string) {
|
||||||
if let Err((segment_string, error)) = result {
|
match result {
|
||||||
|
Ok(segment) => {
|
||||||
|
let seg_span = subspan(&segment.string, string, span);
|
||||||
|
segments.push(Segment::from(segment, seg_span));
|
||||||
|
},
|
||||||
|
Err((segment_string, error)) => {
|
||||||
diags.push(into_diagnostic(segment_string, string, span, &error));
|
diags.push(into_diagnostic(segment_string, string, span, &error));
|
||||||
if let Error::Trailing(..) = error {
|
if let Error::Trailing(..) = error {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if let Ok(segment) = result {
|
}
|
||||||
let seg_span = subspan(&segment.string, string, span);
|
|
||||||
segments.push(Segment::from(segment, seg_span));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@ use std::fmt::Display;
|
||||||
use devise::{syn, Result, ext::SpanDiagnosticExt};
|
use devise::{syn, Result, ext::SpanDiagnosticExt};
|
||||||
|
|
||||||
use crate::http::{uri::{Origin, Path, Query}, ext::IntoOwned};
|
use crate::http::{uri::{Origin, Path, Query}, ext::IntoOwned};
|
||||||
use crate::http::route::{RouteSegment, Kind, Source};
|
use crate::http::route::{RouteSegment, Kind};
|
||||||
|
use crate::attribute::segments::Source;
|
||||||
|
|
||||||
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
use crate::syn::{Expr, Ident, Type, spanned::Spanned};
|
||||||
use crate::http_codegen::Optional;
|
use crate::http_codegen::Optional;
|
||||||
|
|
|
@ -58,7 +58,7 @@ error: invalid path URI: expected EOF but found # at index 3
|
||||||
|
|
|
|
||||||
= help: expected path in origin form: "/path/<param>"
|
= help: expected path in origin form: "/path/<param>"
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--> $DIR/route-path-bad-syntax.rs:31:9
|
--> $DIR/route-path-bad-syntax.rs:31:9
|
||||||
|
|
|
|
||||||
31 | #[get("/a%20b")]
|
31 | #[get("/a%20b")]
|
||||||
|
@ -67,7 +67,7 @@ error: component contains invalid URI characters
|
||||||
= note: components cannot contain reserved characters
|
= note: components cannot contain reserved characters
|
||||||
= help: reserved characters include: '%', '+', '&', etc.
|
= help: reserved characters include: '%', '+', '&', etc.
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--> $DIR/route-path-bad-syntax.rs:34:11
|
--> $DIR/route-path-bad-syntax.rs:34:11
|
||||||
|
|
|
|
||||||
34 | #[get("/a?a%20b")]
|
34 | #[get("/a?a%20b")]
|
||||||
|
@ -76,7 +76,7 @@ error: component contains invalid URI characters
|
||||||
= note: components cannot contain reserved characters
|
= note: components cannot contain reserved characters
|
||||||
= help: reserved characters include: '%', '+', '&', etc.
|
= help: reserved characters include: '%', '+', '&', etc.
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--> $DIR/route-path-bad-syntax.rs:37:11
|
--> $DIR/route-path-bad-syntax.rs:37:11
|
||||||
|
|
|
|
||||||
37 | #[get("/a?a+b")]
|
37 | #[get("/a?a+b")]
|
||||||
|
@ -221,13 +221,13 @@ error: `test ` is not a valid identifier
|
||||||
|
|
|
|
||||||
= help: parameter names must be valid identifiers
|
= help: parameter names must be valid identifiers
|
||||||
|
|
||||||
error: parameters must be named
|
error: handler arguments cannot be ignored
|
||||||
--> $DIR/route-path-bad-syntax.rs:88:9
|
--> $DIR/route-path-bad-syntax.rs:89:7
|
||||||
|
|
|
|
||||||
88 | #[get("/<_>")]
|
89 | fn k0(_: usize) {}
|
||||||
| ^^^
|
| ^^^^^^^^
|
||||||
|
|
|
|
||||||
= help: use a name such as `_guard` or `_param`
|
= help: all handler arguments must be of the form: `ident: Type`
|
||||||
|
|
||||||
error: parameter names cannot be empty
|
error: parameter names cannot be empty
|
||||||
--> $DIR/route-path-bad-syntax.rs:93:9
|
--> $DIR/route-path-bad-syntax.rs:93:9
|
||||||
|
|
|
@ -52,7 +52,7 @@ error: invalid path URI: expected EOF but found # at index 3
|
||||||
28 | #[get("/!@#$%^&*()")]
|
28 | #[get("/!@#$%^&*()")]
|
||||||
| ^^^^^^^^^^^^^
|
| ^^^^^^^^^^^^^
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--- note: components cannot contain reserved characters
|
--- note: components cannot contain reserved characters
|
||||||
--- help: reserved characters include: '%', '+', '&', etc.
|
--- help: reserved characters include: '%', '+', '&', etc.
|
||||||
--> $DIR/route-path-bad-syntax.rs:31:7
|
--> $DIR/route-path-bad-syntax.rs:31:7
|
||||||
|
@ -60,7 +60,7 @@ error: component contains invalid URI characters
|
||||||
31 | #[get("/a%20b")]
|
31 | #[get("/a%20b")]
|
||||||
| ^^^^^^^^
|
| ^^^^^^^^
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--- note: components cannot contain reserved characters
|
--- note: components cannot contain reserved characters
|
||||||
--- help: reserved characters include: '%', '+', '&', etc.
|
--- help: reserved characters include: '%', '+', '&', etc.
|
||||||
--> $DIR/route-path-bad-syntax.rs:34:7
|
--> $DIR/route-path-bad-syntax.rs:34:7
|
||||||
|
@ -68,7 +68,7 @@ error: component contains invalid URI characters
|
||||||
34 | #[get("/a?a%20b")]
|
34 | #[get("/a?a%20b")]
|
||||||
| ^^^^^^^^^^
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
error: component contains invalid URI characters
|
error: segment contains invalid URI characters
|
||||||
--- note: components cannot contain reserved characters
|
--- note: components cannot contain reserved characters
|
||||||
--- help: reserved characters include: '%', '+', '&', etc.
|
--- help: reserved characters include: '%', '+', '&', etc.
|
||||||
--> $DIR/route-path-bad-syntax.rs:37:7
|
--> $DIR/route-path-bad-syntax.rs:37:7
|
||||||
|
@ -192,12 +192,12 @@ error: `test ` is not a valid identifier
|
||||||
83 | #[get("/", data = "<test >")]
|
83 | #[get("/", data = "<test >")]
|
||||||
| ^^^^^^^^^
|
| ^^^^^^^^^
|
||||||
|
|
||||||
error: parameters must be named
|
error: handler arguments cannot be ignored
|
||||||
--- help: use a name such as `_guard` or `_param`
|
--- help: all handler arguments must be of the form: `ident: Type`
|
||||||
--> $DIR/route-path-bad-syntax.rs:88:7
|
--> $DIR/route-path-bad-syntax.rs:89:7
|
||||||
|
|
|
|
||||||
88 | #[get("/<_>")]
|
89 | fn k0(_: usize) {}
|
||||||
| ^^^^^^
|
| ^
|
||||||
|
|
||||||
error: parameter names cannot be empty
|
error: parameter names cannot be empty
|
||||||
--> $DIR/route-path-bad-syntax.rs:93:7
|
--> $DIR/route-path-bad-syntax.rs:93:7
|
||||||
|
|
|
@ -16,14 +16,6 @@ pub enum Kind {
|
||||||
Multi,
|
Multi,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
|
||||||
pub enum Source {
|
|
||||||
Path,
|
|
||||||
Query,
|
|
||||||
Data,
|
|
||||||
Unknown,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RouteSegment<'a, P: UriPart> {
|
pub struct RouteSegment<'a, P: UriPart> {
|
||||||
pub string: Cow<'a, str>,
|
pub string: Cow<'a, str>,
|
||||||
|
@ -59,6 +51,20 @@ pub enum Error<'a> {
|
||||||
Trailing(&'a str)
|
Trailing(&'a str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Error<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Empty => "parameter names cannot be empty".fmt(f),
|
||||||
|
Ident(name) => write!(f, "`{}` is not a valid identifier", name),
|
||||||
|
Ignored => "parameter must be named".fmt(f),
|
||||||
|
MissingClose => "parameter is missing a closing bracket".fmt(f),
|
||||||
|
Malformed => "malformed parameter or identifier".fmt(f),
|
||||||
|
Uri => "segment contains invalid URI characters".fmt(f),
|
||||||
|
Trailing(i) => write!(f, "unexpected trailing text after `{}`", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type SResult<'a, P> = Result<RouteSegment<'a, P>, (&'a str, Error<'a>)>;
|
pub type SResult<'a, P> = Result<RouteSegment<'a, P>, (&'a str, Error<'a>)>;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -100,7 +106,8 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
|
||||||
return Err(Empty);
|
return Err(Empty);
|
||||||
} else if !is_valid_ident(name) {
|
} else if !is_valid_ident(name) {
|
||||||
return Err(Ident(name));
|
return Err(Ident(name));
|
||||||
} else if name == "_" {
|
} else if name == "_" && P::DELIMITER != '/' {
|
||||||
|
// Only path segments may be ignored.
|
||||||
return Err(Ignored);
|
return Err(Ignored);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,6 +136,7 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
|
||||||
string: &'a str,
|
string: &'a str,
|
||||||
) -> impl Iterator<Item = SResult<'_, P>> {
|
) -> impl Iterator<Item = SResult<'_, P>> {
|
||||||
let mut last_multi_seg: Option<&str> = None;
|
let mut last_multi_seg: Option<&str> = None;
|
||||||
|
// We check for empty segments when we parse an `Origin` in `FromMeta`.
|
||||||
string.split(P::DELIMITER)
|
string.split(P::DELIMITER)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
|
|
|
@ -211,7 +211,7 @@ use crate::http::route::Error as SegmentError;
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum RouteUriError {
|
pub enum RouteUriError {
|
||||||
/// The base (mount point) or route path contains invalid segments.
|
/// The base (mount point) or route path contains invalid segments.
|
||||||
Segment,
|
Segment(String, String),
|
||||||
/// The route URI is not a valid URI.
|
/// The route URI is not a valid URI.
|
||||||
Uri(uri::Error<'static>),
|
Uri(uri::Error<'static>),
|
||||||
/// The base (mount point) contains dynamic segments.
|
/// The base (mount point) contains dynamic segments.
|
||||||
|
@ -219,8 +219,8 @@ pub enum RouteUriError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
|
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
|
||||||
fn from(_: (&'a str, SegmentError<'a>)) -> Self {
|
fn from((seg, err): (&'a str, SegmentError<'a>)) -> Self {
|
||||||
RouteUriError::Segment
|
RouteUriError::Segment(seg.into(), err.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -233,8 +233,8 @@ impl<'a> From<uri::Error<'a>> for RouteUriError {
|
||||||
impl fmt::Display for RouteUriError {
|
impl fmt::Display for RouteUriError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
RouteUriError::Segment => {
|
RouteUriError::Segment(seg, err) => {
|
||||||
write!(f, "The URI contains malformed dynamic route path segments.")
|
write!(f, "Malformed segment '{}': {}", Paint::white(seg), err)
|
||||||
}
|
}
|
||||||
RouteUriError::DynamicBase => {
|
RouteUriError::DynamicBase => {
|
||||||
write!(f, "The mount point contains dynamic parameters.")
|
write!(f, "The mount point contains dynamic parameters.")
|
||||||
|
|
Loading…
Reference in New Issue