Merge pull request #1680 from ealmloff/derive-query-parsing
Automatically derive standard query parsing in the router macro
This commit is contained in:
6 files changed
+174
-17
No files matched your search
@@ -36,7 +36,7 @@ mod segment;
|
||||
/// 1. Static Segments: "/static"
|
||||
/// 2. Dynamic Segments: "/:dynamic" (where dynamic has a type that is FromStr in all child Variants)
|
||||
/// 3. Catch all Segments: "/:..segments" (where segments has a type that is FromSegments in all child Variants)
|
||||
/// 4. Query Segments: "/?:query" (where query has a type that is FromQuery in all child Variants)
|
||||
/// 4. Query Segments: "/?:..query" (where query has a type that is FromQuery in all child Variants) or "/?:query&:other_query" (where query and other_query has a type that is FromQueryArgument in all child Variants)
|
||||
///
|
||||
/// Routes are matched:
|
||||
/// 1. By there specificity this order: Query Routes ("/?:query"), Static Routes ("/route"), Dynamic Routes ("/:route"), Catch All Routes ("/:..route")
|
||||
|
||||
@@ -4,12 +4,62 @@ use syn::{Ident, Type};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuerySegment {
|
||||
pub enum QuerySegment {
|
||||
Single(FullQuerySegment),
|
||||
Segments(Vec<QueryArgument>),
|
||||
}
|
||||
|
||||
impl QuerySegment {
|
||||
pub fn contains_ident(&self, ident: &Ident) -> bool {
|
||||
match self {
|
||||
QuerySegment::Single(segment) => segment.ident == *ident,
|
||||
QuerySegment::Segments(segments) => {
|
||||
segments.iter().any(|segment| segment.ident == *ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(&self) -> TokenStream2 {
|
||||
match self {
|
||||
QuerySegment::Single(segment) => segment.parse(),
|
||||
QuerySegment::Segments(segments) => {
|
||||
let mut tokens = TokenStream2::new();
|
||||
tokens.extend(quote! { let split_query: std::collections::HashMap<&str, &str> = query.split('&').filter_map(|s| s.split_once('=')).collect(); });
|
||||
for segment in segments {
|
||||
tokens.extend(segment.parse());
|
||||
}
|
||||
tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) -> TokenStream2 {
|
||||
match self {
|
||||
QuerySegment::Single(segment) => segment.write(),
|
||||
QuerySegment::Segments(segments) => {
|
||||
let mut tokens = TokenStream2::new();
|
||||
tokens.extend(quote! { write!(f, "?")?; });
|
||||
let mut segments_iter = segments.iter();
|
||||
if let Some(first_segment) = segments_iter.next() {
|
||||
tokens.extend(first_segment.write());
|
||||
}
|
||||
for segment in segments_iter {
|
||||
tokens.extend(quote! { write!(f, "&")?; });
|
||||
tokens.extend(segment.write());
|
||||
}
|
||||
tokens
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FullQuerySegment {
|
||||
pub ident: Ident,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl QuerySegment {
|
||||
impl FullQuerySegment {
|
||||
pub fn parse(&self) -> TokenStream2 {
|
||||
let ident = &self.ident;
|
||||
let ty = &self.ty;
|
||||
@@ -25,3 +75,29 @@ impl QuerySegment {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QueryArgument {
|
||||
pub ident: Ident,
|
||||
pub ty: Type,
|
||||
}
|
||||
|
||||
impl QueryArgument {
|
||||
pub fn parse(&self) -> TokenStream2 {
|
||||
let ident = &self.ident;
|
||||
let ty = &self.ty;
|
||||
quote! {
|
||||
let #ident = match split_query.get(stringify!(#ident)) {
|
||||
Some(query_argument) => <#ty as dioxus_router::routable::FromQueryArgument>::from_query_argument(query_argument).unwrap_or_default(),
|
||||
None => <#ty as Default>::default(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self) -> TokenStream2 {
|
||||
let ident = &self.ident;
|
||||
quote! {
|
||||
write!(f, "{}={}", stringify!(#ident), #ident)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,7 +282,7 @@ impl Route {
|
||||
}
|
||||
}
|
||||
if let Some(query) = &self.query {
|
||||
if &query.ident == name {
|
||||
if query.contains_ident(name) {
|
||||
from_route = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use syn::{Ident, Type};
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
|
||||
use crate::query::QuerySegment;
|
||||
use crate::query::{FullQuerySegment, QueryArgument, QuerySegment};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RouteSegment {
|
||||
@@ -201,7 +201,7 @@ pub fn parse_route_segments<'a>(
|
||||
// check if the route has a query string
|
||||
let parsed_query = match query {
|
||||
Some(query) => {
|
||||
if let Some(query) = query.strip_prefix(':') {
|
||||
if let Some(query) = query.strip_prefix(":..") {
|
||||
let query_ident = Ident::new(query, Span::call_site());
|
||||
let field = fields.find(|(name, _)| *name == &query_ident);
|
||||
|
||||
@@ -214,12 +214,44 @@ pub fn parse_route_segments<'a>(
|
||||
));
|
||||
};
|
||||
|
||||
Some(QuerySegment {
|
||||
Some(QuerySegment::Single(FullQuerySegment {
|
||||
ident: query_ident,
|
||||
ty,
|
||||
})
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
let mut query_arguments = Vec::new();
|
||||
for segment in query.split('&') {
|
||||
if segment.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
"Query segments should be non-empty",
|
||||
));
|
||||
}
|
||||
if let Some(query_argument) = segment.strip_prefix(':') {
|
||||
let query_ident = Ident::new(query_argument, Span::call_site());
|
||||
let field = fields.find(|(name, _)| *name == &query_ident);
|
||||
|
||||
let ty = if let Some((_, ty)) = field {
|
||||
ty.clone()
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
format!("Could not find a field with the name '{}'", query_ident),
|
||||
));
|
||||
};
|
||||
|
||||
query_arguments.push(QueryArgument {
|
||||
ident: query_ident,
|
||||
ty,
|
||||
});
|
||||
} else {
|
||||
return Err(syn::Error::new(
|
||||
route_span,
|
||||
"Query segments should be a : followed by the name of the query argument",
|
||||
));
|
||||
}
|
||||
}
|
||||
Some(QuerySegment::Segments(query_arguments))
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
|
||||
Reference in new issue
Block a user