1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-22 17:41:11 +00:00

update syn to 2 in web codegen (#3081)

This commit is contained in:
Rob Ede 2023-07-20 10:49:01 +01:00 committed by GitHub
parent e25f3f8f1d
commit d22c9f9fb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 164 additions and 116 deletions

View file

@ -2,6 +2,7 @@
## Unreleased ## Unreleased
- Update `syn` dependency to `2`.
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
## 0.6.0 - 2023-02-26 ## 0.6.0 - 2023-02-26

View file

@ -17,11 +17,11 @@ all-features = true
proc-macro = true proc-macro = true
[dependencies] [dependencies]
darling = "0.14" darling = "0.20"
parse-size = "1" parse-size = "1"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = "1" syn = "2"
[dev-dependencies] [dev-dependencies]
actix-multipart = "0.6" actix-multipart = "0.6"

View file

@ -252,7 +252,7 @@ impl ResourceDef {
/// Multi-pattern resources can be constructed by providing a slice (or vec) of patterns. /// Multi-pattern resources can be constructed by providing a slice (or vec) of patterns.
/// ///
/// # Panics /// # Panics
/// Panics if path pattern is malformed. /// Panics if any path patterns are malformed.
/// ///
/// # Examples /// # Examples
/// ``` /// ```
@ -838,6 +838,7 @@ impl ResourceDef {
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self { fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
let patterns = paths.patterns(); let patterns = paths.patterns();
let (pat_type, segments) = match &patterns { let (pat_type, segments) = match &patterns {
Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false), Patterns::Single(pattern) => ResourceDef::parse(pattern, is_prefix, false),

View file

@ -2,6 +2,7 @@
## Unreleased - 2023-xx-xx ## Unreleased - 2023-xx-xx
- Update `syn` dependency to `2`.
- Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency. - Minimum supported Rust version (MSRV) is now 1.65 due to transitive `time` dependency.
## 4.2.0 - 2023-02-26 ## 4.2.0 - 2023-02-26

View file

@ -18,10 +18,10 @@ proc-macro = true
actix-router = "0.5" actix-router = "0.5"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "extra-traits"] } syn = { version = "2", features = ["full", "extra-traits"] }
[dev-dependencies] [dev-dependencies]
actix-macros = "0.2.3" actix-macros = "0.2.4"
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1" actix-test = "0.1"
actix-utils = "3" actix-utils = "3"

View file

@ -4,7 +4,54 @@ use actix_router::ResourceDef;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2}; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, Meta, NestedMeta, Path}; use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
#[derive(Debug)]
pub struct RouteArgs {
path: syn::LitStr,
options: Punctuated<syn::MetaNameValue, Token![,]>,
}
impl syn::parse::Parse for RouteArgs {
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
// path to match: "/foo"
let path = input.parse::<syn::LitStr>().map_err(|mut err| {
err.combine(syn::Error::new(
err.span(),
r#"invalid service definition, expected #[<method>("<path>")]"#,
));
err
})?;
// verify that path pattern is valid
let _ = ResourceDef::new(path.value());
// if there's no comma, assume that no options are provided
if !input.peek(Token![,]) {
return Ok(Self {
path,
options: Punctuated::new(),
});
}
// advance past comma separator
input.parse::<Token![,]>()?;
// if next char is a literal, assume that it is a string and show multi-path error
if input.cursor().literal().is_some() {
return Err(syn::Error::new(
Span::call_site(),
r#"Multiple paths specified! There should be only one."#,
));
}
// zero or more options: name = "foo"
let options = input.parse_terminated(syn::MetaNameValue::parse, Token![,])?;
Ok(Self { path, options })
}
}
macro_rules! standard_method_type { macro_rules! standard_method_type {
( (
@ -182,111 +229,90 @@ struct Args {
} }
impl Args { impl Args {
fn new(args: AttributeArgs, method: Option<MethodType>) -> syn::Result<Self> { fn new(args: RouteArgs, method: Option<MethodType>) -> syn::Result<Self> {
let mut path = None;
let mut resource_name = None; let mut resource_name = None;
let mut guards = Vec::new(); let mut guards = Vec::new();
let mut wrappers = Vec::new(); let mut wrappers = Vec::new();
let mut methods = HashSet::new(); let mut methods = HashSet::new();
if args.is_empty() {
return Err(syn::Error::new(
Span::call_site(),
format!(
r#"invalid service definition, expected #[{}("<path>")]"#,
method
.map_or("route", |it| it.as_str())
.to_ascii_lowercase()
),
));
}
let is_route_macro = method.is_none(); let is_route_macro = method.is_none();
if let Some(method) = method { if let Some(method) = method {
methods.insert(MethodTypeExt::Standard(method)); methods.insert(MethodTypeExt::Standard(method));
} }
for arg in args { for nv in args.options {
match arg { if nv.path.is_ident("name") {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path { if let syn::Expr::Lit(syn::ExprLit {
None => { lit: syn::Lit::Str(lit),
let _ = ResourceDef::new(lit.value()); ..
path = Some(lit); }) = nv.value
} {
_ => { resource_name = Some(lit);
return Err(syn::Error::new_spanned( } else {
lit, return Err(syn::Error::new_spanned(
"Multiple paths specified! Should be only one!", nv.value,
)); "Attribute name expects literal string!",
} ));
}, }
} else if nv.path.is_ident("guard") {
NestedMeta::Meta(syn::Meta::NameValue(nv)) => { if let syn::Expr::Lit(syn::ExprLit {
if nv.path.is_ident("name") { lit: syn::Lit::Str(lit),
if let syn::Lit::Str(lit) = nv.lit { ..
resource_name = Some(lit); }) = nv.value
} else { {
return Err(syn::Error::new_spanned( guards.push(lit.parse::<Path>()?);
nv.lit, } else {
"Attribute name expects literal string!", return Err(syn::Error::new_spanned(
)); nv.value,
} "Attribute guard expects literal string!",
} else if nv.path.is_ident("guard") { ));
if let syn::Lit::Str(lit) = nv.lit { }
guards.push(lit.parse::<Path>()?); } else if nv.path.is_ident("wrap") {
} else { if let syn::Expr::Lit(syn::ExprLit {
return Err(syn::Error::new_spanned( lit: syn::Lit::Str(lit),
nv.lit, ..
"Attribute guard expects literal string!", }) = nv.value
)); {
} wrappers.push(lit.parse()?);
} else if nv.path.is_ident("wrap") { } else {
if let syn::Lit::Str(lit) = nv.lit { return Err(syn::Error::new_spanned(
wrappers.push(lit.parse()?); nv.value,
} else { "Attribute wrap expects type",
return Err(syn::Error::new_spanned( ));
nv.lit, }
"Attribute wrap expects type", } else if nv.path.is_ident("method") {
)); if !is_route_macro {
} return Err(syn::Error::new_spanned(
} else if nv.path.is_ident("method") {
if !is_route_macro {
return Err(syn::Error::new_spanned(
&nv, &nv,
"HTTP method forbidden here. To handle multiple methods, use `route` instead", "HTTP method forbidden here. To handle multiple methods, use `route` instead",
)); ));
} else if let syn::Lit::Str(ref lit) = nv.lit { } else if let syn::Expr::Lit(syn::ExprLit {
if !methods.insert(MethodTypeExt::try_from(lit)?) { lit: syn::Lit::Str(lit),
return Err(syn::Error::new_spanned( ..
&nv.lit, }) = nv.value.clone()
format!( {
"HTTP method defined more than once: `{}`", if !methods.insert(MethodTypeExt::try_from(&lit)?) {
lit.value()
),
));
}
} else {
return Err(syn::Error::new_spanned(
nv.lit,
"Attribute method expects literal string!",
));
}
} else {
return Err(syn::Error::new_spanned( return Err(syn::Error::new_spanned(
nv.path, nv.value,
"Unknown attribute key is specified. Allowed: guard, method and wrap", format!("HTTP method defined more than once: `{}`", lit.value()),
)); ));
} }
} else {
return Err(syn::Error::new_spanned(
nv.value,
"Attribute method expects literal string!",
));
} }
} else {
arg => { return Err(syn::Error::new_spanned(
return Err(syn::Error::new_spanned(arg, "Unknown attribute.")); nv.path,
} "Unknown attribute key is specified. Allowed: guard, method and wrap",
));
} }
} }
Ok(Args { Ok(Args {
path: path.unwrap(), path: args.path,
resource_name, resource_name,
guards, guards,
wrappers, wrappers,
@ -312,11 +338,7 @@ pub struct Route {
} }
impl Route { impl Route {
pub fn new( pub fn new(args: RouteArgs, ast: syn::ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
args: AttributeArgs,
ast: syn::ItemFn,
method: Option<MethodType>,
) -> syn::Result<Self> {
let name = ast.sig.ident.clone(); let name = ast.sig.ident.clone();
// Try and pull out the doc comments so that we can reapply them to the generated struct. // Try and pull out the doc comments so that we can reapply them to the generated struct.
@ -324,7 +346,7 @@ impl Route {
let doc_attributes = ast let doc_attributes = ast
.attrs .attrs
.iter() .iter()
.filter(|attr| attr.path.is_ident("doc")) .filter(|attr| attr.path().is_ident("doc"))
.cloned() .cloned()
.collect(); .collect();
@ -360,7 +382,7 @@ impl Route {
let doc_attributes = ast let doc_attributes = ast
.attrs .attrs
.iter() .iter()
.filter(|attr| attr.path.is_ident("doc")) .filter(|attr| attr.path().is_ident("doc"))
.cloned() .cloned()
.collect(); .collect();
@ -455,7 +477,11 @@ pub(crate) fn with_method(
args: TokenStream, args: TokenStream,
input: TokenStream, input: TokenStream,
) -> TokenStream { ) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = match syn::parse(args) {
Ok(args) => args,
// on parse error, make IDEs happy; see fn docs
Err(err) => return input_and_compile_error(input, err),
};
let ast = match syn::parse::<syn::ItemFn>(input.clone()) { let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
Ok(ast) => ast, Ok(ast) => ast,
@ -480,7 +506,7 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
let (methods, others) = ast let (methods, others) = ast
.attrs .attrs
.into_iter() .into_iter()
.map(|attr| match MethodType::from_path(&attr.path) { .map(|attr| match MethodType::from_path(attr.path()) {
Ok(method) => Ok((method, attr)), Ok(method) => Ok((method, attr)),
Err(_) => Err(attr), Err(_) => Err(attr),
}) })
@ -492,13 +518,8 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
.into_iter() .into_iter()
.map(Result::unwrap) .map(Result::unwrap)
.map(|(method, attr)| { .map(|(method, attr)| {
attr.parse_meta().and_then(|args| { attr.parse_args()
if let Meta::List(args) = args { .and_then(|args| Args::new(args, Some(method)))
Args::new(args.nested.into_iter().collect(), Some(method))
} else {
Err(syn::Error::new_spanned(attr, "Invalid input for macro"))
}
})
}) })
.collect::<Result<Vec<_>, _>>() .collect::<Result<Vec<_>, _>>()
{ {

View file

@ -1,4 +1,4 @@
error: invalid service definition, expected #[get("<path>")] error: unexpected end of input, expected string literal
--> tests/trybuild/routes-missing-args-fail.rs:4:1 --> tests/trybuild/routes-missing-args-fail.rs:4:1
| |
4 | #[get] 4 | #[get]
@ -6,11 +6,19 @@ error: invalid service definition, expected #[get("<path>")]
| |
= note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info) = note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)
error: Invalid input for macro error: invalid service definition, expected #[<method>("<path>")]
--> tests/trybuild/routes-missing-args-fail.rs:4:1 --> tests/trybuild/routes-missing-args-fail.rs:4:1
| |
4 | #[get] 4 | #[get]
| ^^^^^^ | ^^^^^^
|
= note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected attribute arguments in parentheses: #[get(...)]
--> tests/trybuild/routes-missing-args-fail.rs:4:3
|
4 | #[get]
| ^^^
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
--> tests/trybuild/routes-missing-args-fail.rs:13:55 --> tests/trybuild/routes-missing-args-fail.rs:13:55

View file

@ -1,26 +1,42 @@
error: Unknown attribute. error: expected `=`
--> $DIR/simple-fail.rs:3:15 --> $DIR/simple-fail.rs:3:1
| |
3 | #[get("/one", other)] 3 | #[get("/one", other)]
| ^^^^^ | ^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)
error: expected identifier or literal error: expected string literal
--> $DIR/simple-fail.rs:8:8 --> $DIR/simple-fail.rs:8:8
| |
8 | #[post(/two)] 8 | #[post(/two)]
| ^ | ^
error: Unknown attribute. error: invalid service definition, expected #[<method>("<path>")]
--> $DIR/simple-fail.rs:8:8
|
8 | #[post(/two)]
| ^
error: expected string literal
--> $DIR/simple-fail.rs:15:9 --> $DIR/simple-fail.rs:15:9
| |
15 | #[patch(PATCH_PATH)] 15 | #[patch(PATCH_PATH)]
| ^^^^^^^^^^ | ^^^^^^^^^^
error: Multiple paths specified! Should be only one! error: invalid service definition, expected #[<method>("<path>")]
--> $DIR/simple-fail.rs:20:19 --> $DIR/simple-fail.rs:15:9
|
15 | #[patch(PATCH_PATH)]
| ^^^^^^^^^^
error: Multiple paths specified! There should be only one.
--> $DIR/simple-fail.rs:20:1
| |
20 | #[delete("/four", "/five")] 20 | #[delete("/four", "/five")]
| ^^^^^^^ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `delete` (in Nightly builds, run with -Z macro-backtrace for more info)
error: HTTP method forbidden here. To handle multiple methods, use `route` instead error: HTTP method forbidden here. To handle multiple methods, use `route` instead
--> $DIR/simple-fail.rs:25:19 --> $DIR/simple-fail.rs:25:19