Allow ignored route path segments.

Co-authored-by: timokoesters <timo@koesters.xyz>
This commit is contained in:
Sergio Benitez 2020-07-28 21:35:07 -07:00
parent 23738446f0
commit 8e8fb4cae8
7 changed files with 88 additions and 59 deletions

View File

@ -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![];

View File

@ -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 {
diags.push(into_diagnostic(segment_string, string, span, &error));
if let Error::Trailing(..) = error {
break;
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));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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