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();
|
||||
fn dup_check<I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
|
||||
where I: Iterator<Item = Segment>
|
||||
fn dup_check<'a, I>(set: &mut IndexSet<Segment>, iter: I, diags: &mut Diagnostics)
|
||||
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;
|
||||
if let Some(previous) = set.replace(segment) {
|
||||
if let Some(previous) = set.replace(segment.clone()) {
|
||||
diags.push(span.error(format!("duplicate parameter: `{}`", previous.name))
|
||||
.span_note(previous.span, "previous parameter with the same name here"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dup_check(&mut segments, attr.path.path.iter().cloned(), &mut diags);
|
||||
attr.path.query.as_ref().map(|q| dup_check(&mut segments, q.iter().cloned(), &mut diags));
|
||||
dup_check(&mut segments, attr.data.clone().map(|s| s.value.0).into_iter(), &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(), &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.
|
||||
let mut inputs = vec![];
|
||||
|
|
|
@ -3,11 +3,11 @@ use std::hash::{Hash, Hasher};
|
|||
use devise::{syn, Diagnostic, ext::SpanDiagnosticExt};
|
||||
use crate::proc_macro2::Span;
|
||||
|
||||
use crate::http::uri::{UriPart, Path};
|
||||
use crate::http::uri::{self, UriPart};
|
||||
use crate::http::route::RouteSegment;
|
||||
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)]
|
||||
pub struct Segment {
|
||||
|
@ -18,6 +18,14 @@ pub struct Segment {
|
|||
pub index: Option<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Source {
|
||||
Path,
|
||||
Query,
|
||||
Data,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl Segment {
|
||||
fn from<P: UriPart>(segment: RouteSegment<'_, P>, span: Span) -> Segment {
|
||||
let source = match P::DELIMITER {
|
||||
|
@ -29,6 +37,17 @@ impl Segment {
|
|||
let (kind, index) = (segment.kind, segment.index);
|
||||
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 {
|
||||
|
@ -80,35 +99,33 @@ fn into_diagnostic(
|
|||
) -> Diagnostic {
|
||||
let seg_span = subspan(segment, source, span);
|
||||
match error {
|
||||
Error::Empty => {
|
||||
seg_span.error("parameter names cannot be empty")
|
||||
}
|
||||
Error::Ident(name) => {
|
||||
seg_span.error(format!("`{}` is not a valid identifier", name))
|
||||
Error::Empty => seg_span.error(error.to_string()),
|
||||
Error::Ident(_) => {
|
||||
seg_span.error(error.to_string())
|
||||
.help("parameter names must be valid identifiers")
|
||||
}
|
||||
Error::Ignored => {
|
||||
seg_span.error("parameters must be named")
|
||||
seg_span.error(error.to_string())
|
||||
.help("use a name such as `_guard` or `_param`")
|
||||
}
|
||||
Error::MissingClose => {
|
||||
seg_span.error("parameter is missing a closing bracket")
|
||||
seg_span.error(error.to_string())
|
||||
.help(format!("did you mean '{}>'?", segment))
|
||||
}
|
||||
Error::Malformed => {
|
||||
seg_span.error("malformed parameter or identifier")
|
||||
seg_span.error(error.to_string())
|
||||
.help("parameters must be of the form '<param>'")
|
||||
.help("identifiers cannot contain '<' or '>'")
|
||||
}
|
||||
Error::Uri => {
|
||||
seg_span.error("component contains invalid URI characters")
|
||||
seg_span.error(error.to_string())
|
||||
.note("components cannot contain reserved characters")
|
||||
.help("reserved characters include: '%', '+', '&', etc.")
|
||||
}
|
||||
Error::Trailing(multi) => {
|
||||
let multi_span = subspan(multi, 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")
|
||||
.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> {
|
||||
<RouteSegment<'_, Path>>::parse_one(segment)
|
||||
<RouteSegment<'_, uri::Query>>::parse_one(segment)
|
||||
.map(|segment| {
|
||||
let mut seg = Segment::from(segment, span);
|
||||
seg.source = Source::Data;
|
||||
|
@ -134,14 +151,17 @@ pub fn parse_segments<P: UriPart>(
|
|||
let mut diags = Diagnostics::new();
|
||||
|
||||
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));
|
||||
if let Error::Trailing(..) = error {
|
||||
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 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::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>"
|
||||
|
||||
error: component contains invalid URI characters
|
||||
error: segment contains invalid URI characters
|
||||
--> $DIR/route-path-bad-syntax.rs:31:9
|
||||
|
|
||||
31 | #[get("/a%20b")]
|
||||
|
@ -67,7 +67,7 @@ error: component contains invalid URI characters
|
|||
= note: components cannot contain reserved characters
|
||||
= 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
|
||||
|
|
||||
34 | #[get("/a?a%20b")]
|
||||
|
@ -76,7 +76,7 @@ error: component contains invalid URI characters
|
|||
= note: components cannot contain reserved characters
|
||||
= 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
|
||||
|
|
||||
37 | #[get("/a?a+b")]
|
||||
|
@ -221,13 +221,13 @@ error: `test ` is not a valid identifier
|
|||
|
|
||||
= help: parameter names must be valid identifiers
|
||||
|
||||
error: parameters must be named
|
||||
--> $DIR/route-path-bad-syntax.rs:88:9
|
||||
error: handler arguments cannot be ignored
|
||||
--> $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
|
||||
--> $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("/!@#$%^&*()")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
error: component contains invalid URI characters
|
||||
error: segment contains invalid URI characters
|
||||
--- note: components cannot contain reserved characters
|
||||
--- help: reserved characters include: '%', '+', '&', etc.
|
||||
--> $DIR/route-path-bad-syntax.rs:31:7
|
||||
|
@ -60,7 +60,7 @@ error: component contains invalid URI characters
|
|||
31 | #[get("/a%20b")]
|
||||
| ^^^^^^^^
|
||||
|
||||
error: component contains invalid URI characters
|
||||
error: segment contains invalid URI characters
|
||||
--- note: components cannot contain reserved characters
|
||||
--- help: reserved characters include: '%', '+', '&', etc.
|
||||
--> $DIR/route-path-bad-syntax.rs:34:7
|
||||
|
@ -68,7 +68,7 @@ error: component contains invalid URI characters
|
|||
34 | #[get("/a?a%20b")]
|
||||
| ^^^^^^^^^^
|
||||
|
||||
error: component contains invalid URI characters
|
||||
error: segment contains invalid URI characters
|
||||
--- note: components cannot contain reserved characters
|
||||
--- help: reserved characters include: '%', '+', '&', etc.
|
||||
--> $DIR/route-path-bad-syntax.rs:37:7
|
||||
|
@ -192,12 +192,12 @@ error: `test ` is not a valid identifier
|
|||
83 | #[get("/", data = "<test >")]
|
||||
| ^^^^^^^^^
|
||||
|
||||
error: parameters must be named
|
||||
--- help: use a name such as `_guard` or `_param`
|
||||
--> $DIR/route-path-bad-syntax.rs:88:7
|
||||
error: handler arguments cannot be ignored
|
||||
--- help: all handler arguments must be of the form: `ident: Type`
|
||||
--> $DIR/route-path-bad-syntax.rs:89:7
|
||||
|
|
||||
88 | #[get("/<_>")]
|
||||
| ^^^^^^
|
||||
89 | fn k0(_: usize) {}
|
||||
| ^
|
||||
|
||||
error: parameter names cannot be empty
|
||||
--> $DIR/route-path-bad-syntax.rs:93:7
|
||||
|
|
|
@ -16,14 +16,6 @@ pub enum Kind {
|
|||
Multi,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
pub enum Source {
|
||||
Path,
|
||||
Query,
|
||||
Data,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RouteSegment<'a, P: UriPart> {
|
||||
pub string: Cow<'a, str>,
|
||||
|
@ -59,6 +51,20 @@ pub enum Error<'a> {
|
|||
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>)>;
|
||||
|
||||
#[inline]
|
||||
|
@ -100,7 +106,8 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
|
|||
return Err(Empty);
|
||||
} else if !is_valid_ident(name) {
|
||||
return Err(Ident(name));
|
||||
} else if name == "_" {
|
||||
} else if name == "_" && P::DELIMITER != '/' {
|
||||
// Only path segments may be ignored.
|
||||
return Err(Ignored);
|
||||
}
|
||||
|
||||
|
@ -129,6 +136,7 @@ impl<'a, P: UriPart> RouteSegment<'a, P> {
|
|||
string: &'a str,
|
||||
) -> impl Iterator<Item = SResult<'_, P>> {
|
||||
let mut last_multi_seg: Option<&str> = None;
|
||||
// We check for empty segments when we parse an `Origin` in `FromMeta`.
|
||||
string.split(P::DELIMITER)
|
||||
.filter(|s| !s.is_empty())
|
||||
.enumerate()
|
||||
|
|
|
@ -211,7 +211,7 @@ use crate::http::route::Error as SegmentError;
|
|||
#[derive(Debug)]
|
||||
pub enum RouteUriError {
|
||||
/// The base (mount point) or route path contains invalid segments.
|
||||
Segment,
|
||||
Segment(String, String),
|
||||
/// The route URI is not a valid URI.
|
||||
Uri(uri::Error<'static>),
|
||||
/// The base (mount point) contains dynamic segments.
|
||||
|
@ -219,8 +219,8 @@ pub enum RouteUriError {
|
|||
}
|
||||
|
||||
impl<'a> From<(&'a str, SegmentError<'a>)> for RouteUriError {
|
||||
fn from(_: (&'a str, SegmentError<'a>)) -> Self {
|
||||
RouteUriError::Segment
|
||||
fn from((seg, err): (&'a str, SegmentError<'a>)) -> Self {
|
||||
RouteUriError::Segment(seg.into(), err.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,8 +233,8 @@ impl<'a> From<uri::Error<'a>> for RouteUriError {
|
|||
impl fmt::Display for RouteUriError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
RouteUriError::Segment => {
|
||||
write!(f, "The URI contains malformed dynamic route path segments.")
|
||||
RouteUriError::Segment(seg, err) => {
|
||||
write!(f, "Malformed segment '{}': {}", Paint::white(seg), err)
|
||||
}
|
||||
RouteUriError::DynamicBase => {
|
||||
write!(f, "The mount point contains dynamic parameters.")
|
||||
|
|
Loading…
Reference in New Issue