feat: sync with upstream 4.2.2
This commit is contained in:
parent
1cc9b8dbaa
commit
6e0a59ee4d
18 changed files with 892 additions and 283 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -84,12 +84,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-macros"
|
name = "actix-macros"
|
||||||
version = "0.2.3"
|
version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
|
checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -245,12 +245,12 @@ dependencies = [
|
||||||
"actix-router",
|
"actix-router",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.99",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "actix-web-codegen-const-routes"
|
name = "actix-web-codegen-const-routes"
|
||||||
version = "4.0.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"actix-macros",
|
"actix-macros",
|
||||||
"actix-router",
|
"actix-router",
|
||||||
|
@ -262,7 +262,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"syn",
|
"syn 2.0.38",
|
||||||
"trybuild",
|
"trybuild",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -474,7 +474,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"syn",
|
"syn 1.0.99",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -847,18 +847,18 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.43"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
|
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.21"
|
version = "1.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
@ -969,7 +969,7 @@ checksum = "d3d8e8de557aee63c26b85b947f5e59b690d0454c753f3adeb5cd7835ab88391"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn 1.0.99",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1051,6 +1051,17 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "termcolor"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -2,7 +2,6 @@
|
||||||
name = "actix-web-codegen-const-routes"
|
name = "actix-web-codegen-const-routes"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Routing and runtime macros for Actix Web with support for const routes"
|
description = "Routing and runtime macros for Actix Web with support for const routes"
|
||||||
homepage = "https://actix.rs"
|
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
authors = [
|
authors = [
|
||||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
@ -16,18 +15,18 @@ edition = "2021"
|
||||||
proc-macro = true
|
proc-macro = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-router = "0.5.0"
|
actix-router = "0.5"
|
||||||
proc-macro2 = "1"
|
proc-macro2 = "1"
|
||||||
quote = "1"
|
quote = "1"
|
||||||
syn = { version = "1", features = ["full", "parsing"] }
|
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.0-beta.13"
|
actix-test = "0.1"
|
||||||
actix-utils = "3.0.0"
|
actix-utils = "3"
|
||||||
actix-web = "4.0.0"
|
actix-web = "4"
|
||||||
|
|
||||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||||
trybuild = "1"
|
trybuild = "1"
|
||||||
rustversion = "1"
|
rustversion = "1"
|
||||||
|
|
58
src/lib.rs
58
src/lib.rs
|
@ -46,9 +46,20 @@
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! # Multiple Path Handlers
|
//! # Multiple Path Handlers
|
||||||
//! There are no macros to generate multi-path handlers. Let us know in [this issue].
|
//! Acts as a wrapper for multiple single method handler macros. It takes no arguments and
|
||||||
|
//! delegates those to the macros for the individual methods. See [macro@routes] macro docs.
|
||||||
//!
|
//!
|
||||||
//! [this issue]: https://github.com/actix/actix-web/issues/1709
|
//! ```
|
||||||
|
//! # use actix_web::HttpResponse;
|
||||||
|
//! # use actix_web_codegen_const_routes::routes;
|
||||||
|
//! #[routes]
|
||||||
|
//! #[get("/test")]
|
||||||
|
//! #[get("/test2")]
|
||||||
|
//! #[delete("/test")]
|
||||||
|
//! async fn example() -> HttpResponse {
|
||||||
|
//! HttpResponse::Ok().finish()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes
|
//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes
|
||||||
//! [GET]: macro@get
|
//! [GET]: macro@get
|
||||||
|
@ -64,6 +75,7 @@
|
||||||
#![recursion_limit = "512"]
|
#![recursion_limit = "512"]
|
||||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
#![warn(future_incompatible)]
|
#![warn(future_incompatible)]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
@ -79,6 +91,7 @@ mod route;
|
||||||
///
|
///
|
||||||
/// # Attributes
|
/// # Attributes
|
||||||
/// - `"path"`: Raw literal string with path for which to register handler.
|
/// - `"path"`: Raw literal string with path for which to register handler.
|
||||||
|
/// - `path="variable_name"` - Variable name that contains path for which to register handler
|
||||||
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
||||||
/// name of handler is used.
|
/// name of handler is used.
|
||||||
/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
|
/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
|
||||||
|
@ -94,7 +107,7 @@ mod route;
|
||||||
/// ```
|
/// ```
|
||||||
/// # use actix_web::HttpResponse;
|
/// # use actix_web::HttpResponse;
|
||||||
/// # use actix_web_codegen_const_routes::route;
|
/// # use actix_web_codegen_const_routes::route;
|
||||||
/// #[route("/test", method = "GET", method = "HEAD")]
|
/// #[route("/test", method = "GET", method = "HEAD", method = "CUSTOM")]
|
||||||
/// async fn example() -> HttpResponse {
|
/// async fn example() -> HttpResponse {
|
||||||
/// HttpResponse::Ok().finish()
|
/// HttpResponse::Ok().finish()
|
||||||
/// }
|
/// }
|
||||||
|
@ -104,6 +117,39 @@ pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
route::with_method(None, args, input)
|
route::with_method(None, args, input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates resource handler, allowing multiple HTTP methods and paths.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// ```plain
|
||||||
|
/// #[routes]
|
||||||
|
/// #[<method>("path", ...)]
|
||||||
|
/// #[<method>("path", ...)]
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
/// The `routes` macro itself has no parameters, but allows specifying the attribute macros for
|
||||||
|
/// the multiple paths and/or methods, e.g. [`GET`](macro@get) and [`POST`](macro@post).
|
||||||
|
///
|
||||||
|
/// These helper attributes take the same parameters as the [single method handlers](crate#single-method-handler).
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web::HttpResponse;
|
||||||
|
/// # use actix_web_codegen_const_routes::routes;
|
||||||
|
/// #[routes]
|
||||||
|
/// #[get("/test")]
|
||||||
|
/// #[get("/test2")]
|
||||||
|
/// #[delete("/test")]
|
||||||
|
/// async fn example() -> HttpResponse {
|
||||||
|
/// HttpResponse::Ok().finish()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn routes(_: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::with_methods(input)
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! method_macro {
|
macro_rules! method_macro {
|
||||||
($variant:ident, $method:ident) => {
|
($variant:ident, $method:ident) => {
|
||||||
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
||||||
|
@ -115,8 +161,8 @@ macro_rules! method_macro {
|
||||||
///
|
///
|
||||||
/// # Attributes
|
/// # Attributes
|
||||||
/// - `"path"`: Raw literal string with path for which to register handler.
|
/// - `"path"`: Raw literal string with path for which to register handler.
|
||||||
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the function
|
/// - `name = "resource_name"`: Specifies resource name for the handler. If not set, the
|
||||||
/// name of handler is used.
|
/// function name of handler is used.
|
||||||
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
///
|
///
|
||||||
|
@ -174,7 +220,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks async test functions to use the actix system entry-point.
|
/// Marks async test functions to use the Actix Web system entry-point.
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
|
|
627
src/route.rs
627
src/route.rs
|
@ -1,28 +1,56 @@
|
||||||
use std::{collections::HashSet, convert::TryFrom};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use actix_router::ResourceDef;
|
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::{format_ident, quote, ToTokens, TokenStreamExt};
|
use quote::{quote, ToTokens, TokenStreamExt};
|
||||||
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta};
|
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
||||||
|
|
||||||
enum ResourceType {
|
#[derive(Debug)]
|
||||||
Async,
|
pub struct RouteArgs {
|
||||||
Sync,
|
//path: syn::LitStr,
|
||||||
|
path: Option<Box<dyn PathMarker>>,
|
||||||
|
options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ResourceType {
|
impl syn::parse::Parse for RouteArgs {
|
||||||
fn to_tokens(&self, stream: &mut TokenStream2) {
|
fn parse(input: syn::parse::ParseStream<'_>) -> syn::Result<Self> {
|
||||||
let ident = format_ident!("to");
|
// path to match: "/foo"
|
||||||
stream.append(ident);
|
|
||||||
|
let path = if let Ok(path) = input.parse::<syn::LitStr>() {
|
||||||
|
// verify that path pattern is valid
|
||||||
|
let _ = ResourceDef::new(path.value());
|
||||||
|
let path: Box<dyn PathMarker> = Box::new(path);
|
||||||
|
Some(path)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.is_some() {
|
||||||
|
// if there's no comma, assume that no options are provided
|
||||||
|
if !input.peek(Token![,]) {
|
||||||
|
return Ok(Self {
|
||||||
|
path,
|
||||||
|
options: Punctuated::new(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// advance past comma separator
|
||||||
|
input.parse::<Token![,]>()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// zero or more options: name = "foo"
|
||||||
|
let options = input.parse_terminated(syn::MetaNameValue::parse, Token![,])?;
|
||||||
|
|
||||||
|
Ok(Self { path, options })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! method_type {
|
macro_rules! standard_method_type {
|
||||||
(
|
(
|
||||||
$($variant:ident, $upper:ident,)+
|
$($variant:ident, $upper:ident, $lower:ident,)+
|
||||||
) => {
|
) => {
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum MethodType {
|
pub enum MethodType {
|
||||||
$(
|
$(
|
||||||
$variant,
|
$variant,
|
||||||
|
@ -39,30 +67,30 @@ macro_rules! method_type {
|
||||||
fn parse(method: &str) -> Result<Self, String> {
|
fn parse(method: &str) -> Result<Self, String> {
|
||||||
match method {
|
match method {
|
||||||
$(stringify!($upper) => Ok(Self::$variant),)+
|
$(stringify!($upper) => Ok(Self::$variant),)+
|
||||||
_ => Err(format!("Unexpected HTTP method: `{}`", method)),
|
_ => Err(format!("HTTP method must be uppercase: `{}`", method)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_path(method: &Path) -> Result<Self, ()> {
|
||||||
|
match () {
|
||||||
|
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||||
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
method_type! {
|
standard_method_type! {
|
||||||
Get, GET,
|
Get, GET, get,
|
||||||
Post, POST,
|
Post, POST, post,
|
||||||
Put, PUT,
|
Put, PUT, put,
|
||||||
Delete, DELETE,
|
Delete, DELETE, delete,
|
||||||
Head, HEAD,
|
Head, HEAD, head,
|
||||||
Connect, CONNECT,
|
Connect, CONNECT, connect,
|
||||||
Options, OPTIONS,
|
Options, OPTIONS, options,
|
||||||
Trace, TRACE,
|
Trace, TRACE, trace,
|
||||||
Patch, PATCH,
|
Patch, PATCH, patch,
|
||||||
}
|
|
||||||
|
|
||||||
impl ToTokens for MethodType {
|
|
||||||
fn to_tokens(&self, stream: &mut TokenStream2) {
|
|
||||||
let ident = Ident::new(self.as_str(), Span::call_site());
|
|
||||||
stream.append(ident);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&syn::LitStr> for MethodType {
|
impl TryFrom<&syn::LitStr> for MethodType {
|
||||||
|
@ -74,22 +102,133 @@ impl TryFrom<&syn::LitStr> for MethodType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MethodType {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
||||||
|
let ident = Ident::new(self.as_str(), Span::call_site());
|
||||||
|
stream.append(ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum MethodTypeExt {
|
||||||
|
Standard(MethodType),
|
||||||
|
Custom(LitStr),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodTypeExt {
|
||||||
|
/// Returns a single method guard token stream.
|
||||||
|
fn to_tokens_single_guard(&self) -> TokenStream2 {
|
||||||
|
match self {
|
||||||
|
MethodTypeExt::Standard(method) => {
|
||||||
|
quote! {
|
||||||
|
.guard(::actix_web::guard::#method())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodTypeExt::Custom(lit) => {
|
||||||
|
quote! {
|
||||||
|
.guard(::actix_web::guard::Method(
|
||||||
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a multi-method guard chain token stream.
|
||||||
|
fn to_tokens_multi_guard(&self, or_chain: Vec<impl ToTokens>) -> TokenStream2 {
|
||||||
|
debug_assert!(
|
||||||
|
!or_chain.is_empty(),
|
||||||
|
"empty or_chain passed to multi-guard constructor"
|
||||||
|
);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
MethodTypeExt::Standard(method) => {
|
||||||
|
quote! {
|
||||||
|
.guard(
|
||||||
|
::actix_web::guard::Any(::actix_web::guard::#method())
|
||||||
|
#(#or_chain)*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodTypeExt::Custom(lit) => {
|
||||||
|
quote! {
|
||||||
|
.guard(
|
||||||
|
::actix_web::guard::Any(
|
||||||
|
::actix_web::guard::Method(
|
||||||
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
#(#or_chain)*
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a token stream containing the `.or` chain to be passed in to
|
||||||
|
/// [`MethodTypeExt::to_tokens_multi_guard()`].
|
||||||
|
fn to_tokens_multi_guard_or_chain(&self) -> TokenStream2 {
|
||||||
|
match self {
|
||||||
|
MethodTypeExt::Standard(method_type) => {
|
||||||
|
quote! {
|
||||||
|
.or(::actix_web::guard::#method_type())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MethodTypeExt::Custom(lit) => {
|
||||||
|
quote! {
|
||||||
|
.or(
|
||||||
|
::actix_web::guard::Method(
|
||||||
|
::actix_web::http::Method::from_bytes(#lit.as_bytes()).unwrap()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for MethodTypeExt {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
||||||
|
match self {
|
||||||
|
MethodTypeExt::Custom(lit_str) => {
|
||||||
|
let ident = Ident::new(lit_str.value().as_str(), Span::call_site());
|
||||||
|
stream.append(ident);
|
||||||
|
}
|
||||||
|
MethodTypeExt::Standard(method) => method.to_tokens(stream),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&syn::LitStr> for MethodTypeExt {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
|
||||||
|
match MethodType::try_from(value) {
|
||||||
|
Ok(method) => Ok(MethodTypeExt::Standard(method)),
|
||||||
|
Err(_) if value.value().chars().all(|c| c.is_ascii_uppercase()) => {
|
||||||
|
Ok(MethodTypeExt::Custom(value.clone()))
|
||||||
|
}
|
||||||
|
Err(err) => Err(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Args {
|
struct Args {
|
||||||
path: Box<dyn PathMarker>,
|
path: Box<dyn PathMarker>,
|
||||||
resource_name: Option<syn::LitStr>,
|
resource_name: Option<syn::LitStr>,
|
||||||
guards: Vec<Ident>,
|
guards: Vec<Path>,
|
||||||
wrappers: Vec<syn::Type>,
|
wrappers: Vec<syn::Expr>,
|
||||||
methods: HashSet<MethodType>,
|
methods: HashSet<MethodTypeExt>,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait PathMarker: quote::ToTokens {}
|
trait PathMarker: quote::ToTokens + std::fmt::Debug {}
|
||||||
|
|
||||||
impl PathMarker for syn::Ident {}
|
impl PathMarker for syn::Ident {}
|
||||||
impl PathMarker for syn::LitStr {}
|
impl PathMarker for syn::LitStr {}
|
||||||
impl PathMarker for syn::Expr {}
|
impl PathMarker for syn::Expr {}
|
||||||
|
|
||||||
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: Option<Box<dyn PathMarker>> = None;
|
let mut path: Option<Box<dyn PathMarker>> = None;
|
||||||
let mut resource_name = None;
|
let mut resource_name = None;
|
||||||
let mut guards = Vec::new();
|
let mut guards = Vec::new();
|
||||||
|
@ -98,109 +237,119 @@ impl Args {
|
||||||
|
|
||||||
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(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(Box::new(lit));
|
}) = nv.value
|
||||||
}
|
{
|
||||||
|
resource_name = Some(lit);
|
||||||
_ => {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
lit,
|
nv.value,
|
||||||
"Multiple paths specified! Should be only one!",
|
"Attribute name expects literal string",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
},
|
} else if nv.path.is_ident("path") {
|
||||||
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(
|
// if let syn::Lit::Str(lit) = nv.lit {
|
||||||
nv.lit,
|
match path {
|
||||||
"Attribute name expects literal string!",
|
None => {
|
||||||
));
|
if args.path.is_some() {
|
||||||
}
|
|
||||||
} else if nv.path.is_ident("path") {
|
|
||||||
if let syn::Lit::Str(lit) = nv.lit {
|
|
||||||
match path {
|
|
||||||
None => {
|
|
||||||
path = Some(Box::new(lit.parse::<syn::Expr>()?));
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
lit,
|
|
||||||
"Multiple paths specified! Should be only one!",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
nv.lit,
|
|
||||||
"Attribute path expects literal string!",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if nv.path.is_ident("guard") {
|
|
||||||
if let syn::Lit::Str(lit) = nv.lit {
|
|
||||||
guards.push(Ident::new(&lit.value(), Span::call_site()));
|
|
||||||
} else {
|
|
||||||
return Err(syn::Error::new_spanned(
|
|
||||||
nv.lit,
|
|
||||||
"Attribute guard expects literal string!",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
} else if nv.path.is_ident("wrap") {
|
|
||||||
if let syn::Lit::Str(lit) = nv.lit {
|
|
||||||
wrappers.push(lit.parse()?);
|
|
||||||
} else {
|
|
||||||
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(
|
|
||||||
&nv,
|
|
||||||
"HTTP method forbidden here. To handle multiple methods, use `route` instead",
|
|
||||||
));
|
|
||||||
} else if let syn::Lit::Str(ref lit) = nv.lit {
|
|
||||||
let method = MethodType::try_from(lit)?;
|
|
||||||
if !methods.insert(method) {
|
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
&nv.lit,
|
lit,
|
||||||
&format!(
|
"Multiple paths specified! Should be only one!",
|
||||||
"HTTP method defined more than once: `{}`",
|
|
||||||
lit.value()
|
|
||||||
),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
path = Some(Box::new(lit.parse::<syn::Expr>()?));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
nv.lit,
|
lit,
|
||||||
"Attribute method expects literal string!",
|
"Multiple paths specified! Should be only one!",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.value,
|
||||||
|
"Attribute path expects literal string!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("guard") {
|
||||||
|
if let syn::Expr::Lit(syn::ExprLit {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
..
|
||||||
|
}) = nv.value
|
||||||
|
{
|
||||||
|
guards.push(lit.parse::<Path>()?);
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.value,
|
||||||
|
"Attribute guard expects literal string",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("wrap") {
|
||||||
|
if let syn::Expr::Lit(syn::ExprLit {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
..
|
||||||
|
}) = nv.value
|
||||||
|
{
|
||||||
|
wrappers.push(lit.parse()?);
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.value,
|
||||||
|
"Attribute wrap expects type",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if nv.path.is_ident("method") {
|
||||||
|
if !is_route_macro {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
&nv,
|
||||||
|
"HTTP method forbidden here; to handle multiple methods, use `route` instead",
|
||||||
|
));
|
||||||
|
} else if let syn::Expr::Lit(syn::ExprLit {
|
||||||
|
lit: syn::Lit::Str(lit),
|
||||||
|
..
|
||||||
|
}) = nv.value.clone()
|
||||||
|
{
|
||||||
|
if !methods.insert(MethodTypeExt::try_from(&lit)?) {
|
||||||
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",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
arg => {
|
} else {
|
||||||
return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
|
return Err(syn::Error::new_spanned(
|
||||||
}
|
nv.path,
|
||||||
|
"Unknown attribute key is specified; allowed: guard, method and wrap",
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let path = if let Some(path) = path {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
args.path.unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Args {
|
Ok(Args {
|
||||||
path: path.unwrap(),
|
path,
|
||||||
resource_name,
|
resource_name,
|
||||||
guards,
|
guards,
|
||||||
wrappers,
|
wrappers,
|
||||||
|
@ -210,55 +359,23 @@ impl Args {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Route {
|
pub struct Route {
|
||||||
|
/// Name of the handler function being annotated.
|
||||||
name: syn::Ident,
|
name: syn::Ident,
|
||||||
args: Args,
|
|
||||||
|
/// Args passed to routing macro.
|
||||||
|
///
|
||||||
|
/// When using `#[routes]`, this will contain args for each specific routing macro.
|
||||||
|
args: Vec<Args>,
|
||||||
|
|
||||||
|
/// AST of the handler function being annotated.
|
||||||
ast: syn::ItemFn,
|
ast: syn::ItemFn,
|
||||||
resource_type: ResourceType,
|
|
||||||
|
|
||||||
/// The doc comment attributes to copy to generated struct, if any.
|
/// The doc comment attributes to copy to generated struct, if any.
|
||||||
doc_attributes: Vec<syn::Attribute>,
|
doc_attributes: Vec<syn::Attribute>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn guess_resource_type(typ: &syn::Type) -> ResourceType {
|
|
||||||
let mut guess = ResourceType::Sync;
|
|
||||||
|
|
||||||
if let syn::Type::ImplTrait(typ) = typ {
|
|
||||||
for bound in typ.bounds.iter() {
|
|
||||||
if let syn::TypeParamBound::Trait(bound) = bound {
|
|
||||||
for bound in bound.path.segments.iter() {
|
|
||||||
if bound.ident == "Future" {
|
|
||||||
guess = ResourceType::Async;
|
|
||||||
break;
|
|
||||||
} else if bound.ident == "Responder" {
|
|
||||||
guess = ResourceType::Sync;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
guess
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
|
||||||
if args.is_empty() {
|
|
||||||
return Err(syn::Error::new(
|
|
||||||
Span::call_site(),
|
|
||||||
format!(
|
|
||||||
r#"invalid service definition, expected #[{}("<some path>")]"#,
|
|
||||||
method
|
|
||||||
.map_or("route", |it| it.as_str())
|
|
||||||
.to_ascii_lowercase()
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
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.
|
||||||
|
@ -266,11 +383,12 @@ 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();
|
||||||
|
|
||||||
let args = Args::new(args, method)?;
|
let args = Args::new(args, method)?;
|
||||||
|
|
||||||
if args.methods.is_empty() {
|
if args.methods.is_empty() {
|
||||||
return Err(syn::Error::new(
|
return Err(syn::Error::new(
|
||||||
Span::call_site(),
|
Span::call_site(),
|
||||||
|
@ -278,25 +396,44 @@ impl Route {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let resource_type = if ast.sig.asyncness.is_some() {
|
if matches!(ast.sig.output, syn::ReturnType::Default) {
|
||||||
ResourceType::Async
|
return Err(syn::Error::new_spanned(
|
||||||
} else {
|
ast,
|
||||||
match ast.sig.output {
|
"Function has no return type. Cannot be used as handler",
|
||||||
syn::ReturnType::Default => {
|
));
|
||||||
return Err(syn::Error::new_spanned(
|
}
|
||||||
ast,
|
|
||||||
"Function has no return type. Cannot be used as handler",
|
Ok(Self {
|
||||||
));
|
name,
|
||||||
}
|
args: vec![args],
|
||||||
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
|
ast,
|
||||||
}
|
doc_attributes,
|
||||||
};
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multiple(args: Vec<Args>, ast: syn::ItemFn) -> syn::Result<Self> {
|
||||||
|
let name = ast.sig.ident.clone();
|
||||||
|
|
||||||
|
// Try and pull out the doc comments so that we can reapply them to the generated struct.
|
||||||
|
// Note that multi line doc comments are converted to multiple doc attributes.
|
||||||
|
let doc_attributes = ast
|
||||||
|
.attrs
|
||||||
|
.iter()
|
||||||
|
.filter(|attr| attr.path().is_ident("doc"))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if matches!(ast.sig.output, syn::ReturnType::Default) {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
ast,
|
||||||
|
"Function has no return type. Cannot be used as handler",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
name,
|
name,
|
||||||
args,
|
args,
|
||||||
ast,
|
ast,
|
||||||
resource_type,
|
|
||||||
doc_attributes,
|
doc_attributes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -307,40 +444,55 @@ impl ToTokens for Route {
|
||||||
let Self {
|
let Self {
|
||||||
name,
|
name,
|
||||||
ast,
|
ast,
|
||||||
args:
|
args,
|
||||||
Args {
|
doc_attributes,
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
let registrations: TokenStream2 = args
|
||||||
|
.iter()
|
||||||
|
.map(|args| {
|
||||||
|
let Args {
|
||||||
path,
|
path,
|
||||||
resource_name,
|
resource_name,
|
||||||
guards,
|
guards,
|
||||||
wrappers,
|
wrappers,
|
||||||
methods,
|
methods,
|
||||||
},
|
} = args;
|
||||||
resource_type,
|
|
||||||
doc_attributes,
|
|
||||||
} = self;
|
|
||||||
let resource_name = resource_name
|
|
||||||
.as_ref()
|
|
||||||
.map_or_else(|| name.to_string(), LitStr::value);
|
|
||||||
let method_guards = {
|
|
||||||
let mut others = methods.iter();
|
|
||||||
// unwrapping since length is checked to be at least one
|
|
||||||
let first = others.next().unwrap();
|
|
||||||
|
|
||||||
if methods.len() > 1 {
|
let resource_name = resource_name
|
||||||
quote! {
|
.as_ref()
|
||||||
.guard(
|
.map_or_else(|| name.to_string(), LitStr::value);
|
||||||
::actix_web::guard::Any(::actix_web::guard::#first())
|
|
||||||
#(.or(::actix_web::guard::#others()))*
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quote! {
|
|
||||||
.guard(::actix_web::guard::#first())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = path.as_ref();
|
let method_guards = {
|
||||||
|
debug_assert!(!methods.is_empty(), "Args::methods should not be empty");
|
||||||
|
|
||||||
|
let mut others = methods.iter();
|
||||||
|
let first = others.next().unwrap();
|
||||||
|
|
||||||
|
if methods.len() > 1 {
|
||||||
|
let other_method_guards = others
|
||||||
|
.map(|method_ext| method_ext.to_tokens_multi_guard_or_chain())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
first.to_tokens_multi_guard(other_method_guards)
|
||||||
|
} else {
|
||||||
|
first.to_tokens_single_guard()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
let __resource = ::actix_web::Resource::new(#path)
|
||||||
|
.name(#resource_name)
|
||||||
|
#method_guards
|
||||||
|
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
||||||
|
#(.wrap(#wrappers))*
|
||||||
|
.to(#name);
|
||||||
|
::actix_web::dev::HttpServiceFactory::register(__resource, __config);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
let stream = quote! {
|
let stream = quote! {
|
||||||
#(#doc_attributes)*
|
#(#doc_attributes)*
|
||||||
|
@ -350,17 +502,11 @@ impl ToTokens for Route {
|
||||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
#ast
|
#ast
|
||||||
let __resource = ::actix_web::Resource::new(#path)
|
#registrations
|
||||||
.name(#resource_name)
|
|
||||||
#method_guards
|
|
||||||
#(.guard(::actix_web::guard::fn_guard(#guards)))*
|
|
||||||
#(.wrap(#wrappers))*
|
|
||||||
.#resource_type(#name);
|
|
||||||
|
|
||||||
::actix_web::dev::HttpServiceFactory::register(__resource, __config)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
output.extend(stream);
|
output.extend(stream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -370,7 +516,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,
|
||||||
|
@ -385,6 +535,53 @@ pub(crate) fn with_method(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||||
|
let mut ast = match syn::parse::<syn::ItemFn>(input.clone()) {
|
||||||
|
Ok(ast) => ast,
|
||||||
|
// on parse error, make IDEs happy; see fn docs
|
||||||
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
let (methods, others) = ast
|
||||||
|
.attrs
|
||||||
|
.into_iter()
|
||||||
|
.map(|attr| match MethodType::from_path(attr.path()) {
|
||||||
|
Ok(method) => Ok((method, attr)),
|
||||||
|
Err(_) => Err(attr),
|
||||||
|
})
|
||||||
|
.partition::<Vec<_>, _>(Result::is_ok);
|
||||||
|
|
||||||
|
ast.attrs = others.into_iter().map(Result::unwrap_err).collect();
|
||||||
|
|
||||||
|
let methods = match methods
|
||||||
|
.into_iter()
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.map(|(method, attr)| {
|
||||||
|
attr.parse_args()
|
||||||
|
.and_then(|args| Args::new(args, Some(method)))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()
|
||||||
|
{
|
||||||
|
Ok(methods) if methods.is_empty() => {
|
||||||
|
return input_and_compile_error(
|
||||||
|
input,
|
||||||
|
syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"The #[routes] macro requires at least one `#[<method>(..)]` attribute.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Ok(methods) => methods,
|
||||||
|
Err(err) => return input_and_compile_error(input, err),
|
||||||
|
};
|
||||||
|
|
||||||
|
match Route::multiple(methods, ast) {
|
||||||
|
Ok(route) => route.into_token_stream().into(),
|
||||||
|
// on macro related error, make IDEs happy; see fn docs
|
||||||
|
Err(err) => input_and_compile_error(input, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Converts the error to a token stream and appends it to the original input.
|
/// Converts the error to a token stream and appends it to the original input.
|
||||||
///
|
///
|
||||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
|
|
@ -8,10 +8,10 @@ use actix_web::{
|
||||||
header::{HeaderName, HeaderValue},
|
header::{HeaderName, HeaderValue},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
},
|
},
|
||||||
web, App, Error, HttpResponse, Responder,
|
web, App, Error, HttpRequest, HttpResponse, Responder,
|
||||||
};
|
};
|
||||||
use actix_web_codegen_const_routes::{
|
use actix_web_codegen_const_routes::{
|
||||||
connect, delete, get, head, options, patch, post, put, route, trace,
|
connect, delete, get, head, options, patch, post, put, route, routes, trace,
|
||||||
};
|
};
|
||||||
use futures_core::future::LocalBoxFuture;
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
|
@ -100,18 +100,77 @@ async fn get_param_test(_: web::Path<String>) -> impl Responder {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[route("/multi", method = "GET", method = "POST", method = "HEAD")]
|
#[route("/hello", method = "HELLO")]
|
||||||
|
async fn custom_route_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route(
|
||||||
|
"/multi",
|
||||||
|
method = "GET",
|
||||||
|
method = "POST",
|
||||||
|
method = "HEAD",
|
||||||
|
method = "HELLO"
|
||||||
|
)]
|
||||||
async fn route_test() -> impl Responder {
|
async fn route_test() -> impl Responder {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[routes]
|
||||||
|
#[get("/routes/test")]
|
||||||
|
#[get("/routes/test2")]
|
||||||
|
#[post("/routes/test")]
|
||||||
|
async fn routes_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
// routes overlap with the more specific route first, therefore accessible
|
||||||
|
#[routes]
|
||||||
|
#[get("/routes/overlap/test")]
|
||||||
|
#[get("/routes/overlap/{foo}")]
|
||||||
|
async fn routes_overlapping_test(req: HttpRequest) -> impl Responder {
|
||||||
|
// foo is only populated when route is not /routes/overlap/test
|
||||||
|
match req.match_info().get("foo") {
|
||||||
|
None => assert!(req.uri() == "/routes/overlap/test"),
|
||||||
|
Some(_) => assert!(req.uri() != "/routes/overlap/test"),
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
// routes overlap with the more specific route last, therefore inaccessible
|
||||||
|
#[routes]
|
||||||
|
#[get("/routes/overlap2/{foo}")]
|
||||||
|
#[get("/routes/overlap2/test")]
|
||||||
|
async fn routes_overlapping_inaccessible_test(req: HttpRequest) -> impl Responder {
|
||||||
|
// foo is always populated even when path is /routes/overlap2/test
|
||||||
|
assert!(req.match_info().get("foo").is_some());
|
||||||
|
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
#[get("/custom_resource_name", name = "custom")]
|
#[get("/custom_resource_name", name = "custom")]
|
||||||
async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder {
|
async fn custom_resource_name_test<'a>(req: HttpRequest) -> impl Responder {
|
||||||
assert!(req.url_for_static("custom").is_ok());
|
assert!(req.url_for_static("custom").is_ok());
|
||||||
assert!(req.url_for_static("custom_resource_name_test").is_err());
|
assert!(req.url_for_static("custom_resource_name_test").is_err());
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod guard_module {
|
||||||
|
use actix_web::{guard::GuardContext, http::header};
|
||||||
|
|
||||||
|
pub fn guard(ctx: &GuardContext) -> bool {
|
||||||
|
ctx.header::<header::Accept>()
|
||||||
|
.map(|h| h.preference() == "image/*")
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test/guard", guard = "guard_module::guard")]
|
||||||
|
async fn guard_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ChangeStatusCode;
|
pub struct ChangeStatusCode;
|
||||||
|
|
||||||
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
||||||
|
@ -167,6 +226,19 @@ async fn get_wrap(_: web::Path<String>) -> impl Responder {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Using expression, not just path to type, in wrap attribute.
|
||||||
|
///
|
||||||
|
/// Regression from <https://github.com/actix/actix-web/issues/3118>.
|
||||||
|
#[route(
|
||||||
|
"/catalog",
|
||||||
|
method = "GET",
|
||||||
|
method = "HEAD",
|
||||||
|
wrap = "actix_web::middleware::Compress::default()"
|
||||||
|
)]
|
||||||
|
async fn get_catalog() -> impl Responder {
|
||||||
|
HttpResponse::Ok().body("123123123")
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn test_params() {
|
async fn test_params() {
|
||||||
let srv = actix_test::start(|| {
|
let srv = actix_test::start(|| {
|
||||||
|
@ -174,16 +246,7 @@ async fn test_params() {
|
||||||
.service(get_param_test)
|
.service(get_param_test)
|
||||||
.service(put_param_test)
|
.service(put_param_test)
|
||||||
.service(delete_param_test)
|
.service(delete_param_test)
|
||||||
.service(path)
|
|
||||||
.service(field_path)
|
|
||||||
});
|
});
|
||||||
let request = srv.request(http::Method::GET, srv.url(FIELD_PATH.0));
|
|
||||||
let response = request.send().await.unwrap();
|
|
||||||
assert_eq!(response.status(), http::StatusCode::OK);
|
|
||||||
|
|
||||||
let request = srv.request(http::Method::GET, srv.url(PATH));
|
|
||||||
let response = request.send().await.unwrap();
|
|
||||||
assert_eq!(response.status(), http::StatusCode::OK);
|
|
||||||
|
|
||||||
let request = srv.request(http::Method::GET, srv.url("/test/it"));
|
let request = srv.request(http::Method::GET, srv.url("/test/it"));
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
|
@ -211,7 +274,13 @@ async fn test_body() {
|
||||||
.service(patch_test)
|
.service(patch_test)
|
||||||
.service(test_handler)
|
.service(test_handler)
|
||||||
.service(route_test)
|
.service(route_test)
|
||||||
|
.service(routes_overlapping_test)
|
||||||
|
.service(routes_overlapping_inaccessible_test)
|
||||||
|
.service(routes_test)
|
||||||
.service(custom_resource_name_test)
|
.service(custom_resource_name_test)
|
||||||
|
.service(guard_test)
|
||||||
|
.service(path)
|
||||||
|
.service(field_path)
|
||||||
});
|
});
|
||||||
let request = srv.request(http::Method::GET, srv.url("/test"));
|
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
|
@ -267,9 +336,55 @@ async fn test_body() {
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
assert!(!response.status().is_success());
|
assert!(!response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/test2"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::POST, srv.url("/routes/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/not-set"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_client_error());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/overlap/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/overlap/bar"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/routes/overlap2/bar"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv
|
||||||
|
.request(http::Method::GET, srv.url("/test/guard"))
|
||||||
|
.insert_header(("Accept", "image/*"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url(FIELD_PATH.0));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::OK);
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url(PATH));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#[rustversion::stable(1.54)] // MSRV
|
#[rustversion::stable(1.68)] // MSRV
|
||||||
#[test]
|
#[test]
|
||||||
fn compile_macros() {
|
fn compile_macros() {
|
||||||
let t = trybuild::TestCases::new();
|
let t = trybuild::TestCases::new();
|
||||||
|
@ -9,9 +9,15 @@ fn compile_macros() {
|
||||||
t.pass("tests/trybuild/route-ok.rs");
|
t.pass("tests/trybuild/route-ok.rs");
|
||||||
t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
|
t.compile_fail("tests/trybuild/route-missing-method-fail.rs");
|
||||||
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs");
|
||||||
t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs");
|
|
||||||
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
|
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/route-custom-method.rs");
|
||||||
|
t.compile_fail("tests/trybuild/route-custom-lowercase.rs");
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/routes-ok.rs");
|
||||||
|
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
||||||
|
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/docstring-ok.rs");
|
t.pass("tests/trybuild/docstring-ok.rs");
|
||||||
|
|
||||||
t.pass("tests/trybuild/test-runtime.rs");
|
t.pass("tests/trybuild/test-runtime.rs");
|
||||||
|
|
19
tests/trybuild/route-custom-lowercase.rs
Normal file
19
tests/trybuild/route-custom-lowercase.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
use actix_web::http::Method;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[route("/", method = "hello")]
|
||||||
|
async fn index() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
use actix_web::App;
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
|
||||||
|
let request = srv.request(Method::from_str("hello").unwrap(), srv.url("/"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
29
tests/trybuild/route-custom-lowercase.stderr
Normal file
29
tests/trybuild/route-custom-lowercase.stderr
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
error: HTTP method must be uppercase: `hello`
|
||||||
|
--> tests/trybuild/route-custom-lowercase.rs:5:23
|
||||||
|
|
|
||||||
|
5 | #[route("/", method = "hello")]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
|
--> tests/trybuild/route-custom-lowercase.rs:14:55
|
||||||
|
|
|
||||||
|
14 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
|
note: required by a bound in `App::<T>::service`
|
||||||
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
||||||
|
| F: HttpServiceFactory + 'static,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
37
tests/trybuild/route-custom-method.rs
Normal file
37
tests/trybuild/route-custom-method.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use actix_web::http::Method;
|
||||||
|
use actix_web_codegen_const_routes::route;
|
||||||
|
|
||||||
|
#[route("/single", method = "CUSTOM")]
|
||||||
|
async fn index() -> String {
|
||||||
|
"Hello Single!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route("/multi", method = "GET", method = "CUSTOM")]
|
||||||
|
async fn custom() -> String {
|
||||||
|
"Hello Multi!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
use actix_web::App;
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| App::new().service(index).service(custom));
|
||||||
|
|
||||||
|
let request = srv.request(Method::GET, srv.url("/"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_client_error());
|
||||||
|
|
||||||
|
let request = srv.request(Method::from_str("CUSTOM").unwrap(), srv.url("/single"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(Method::GET, srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(Method::from_str("CUSTOM").unwrap(), srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
|
@ -1,11 +1,29 @@
|
||||||
error: HTTP method defined more than once: `GET`
|
error: HTTP method defined more than once: `GET`
|
||||||
--> $DIR/route-duplicate-method-fail.rs:3:35
|
--> tests/trybuild/route-duplicate-method-fail.rs:3:35
|
||||||
|
|
|
|
||||||
3 | #[route("/", method="GET", method="GET")]
|
3 | #[route("/", method="GET", method="GET")]
|
||||||
| ^^^^^
|
| ^^^^^
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
--> $DIR/route-duplicate-method-fail.rs:12:55
|
--> tests/trybuild/route-duplicate-method-fail.rs:12:55
|
||||||
|
|
|
|
||||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
|
note: required by a bound in `App::<T>::service`
|
||||||
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
||||||
|
| F: HttpServiceFactory + 'static,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
||||||
|
|
|
@ -6,8 +6,26 @@ error: The #[route(..)] macro requires at least one `method` attribute
|
||||||
|
|
|
|
||||||
= note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info)
|
= note: this error originates in the attribute macro `route` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
--> tests/trybuild/route-missing-method-fail.rs:12:55
|
--> tests/trybuild/route-missing-method-fail.rs:12:55
|
||||||
|
|
|
|
||||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
|
note: required by a bound in `App::<T>::service`
|
||||||
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
||||||
|
| F: HttpServiceFactory + 'static,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
error: Unexpected HTTP method: `UNEXPECTED`
|
|
||||||
--> $DIR/route-unexpected-method-fail.rs:3:21
|
|
||||||
|
|
|
||||||
3 | #[route("/", method="UNEXPECTED")]
|
|
||||||
| ^^^^^^^^^^^^
|
|
||||||
|
|
||||||
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
|
||||||
--> $DIR/route-unexpected-method-fail.rs:12:55
|
|
||||||
|
|
|
||||||
12 | let srv = actix_test::start(|| App::new().service(index));
|
|
||||||
| ^^^^^ the trait `HttpServiceFactory` is not implemented for `fn() -> impl std::future::Future {index}`
|
|
14
tests/trybuild/routes-missing-args-fail.rs
Normal file
14
tests/trybuild/routes-missing-args-fail.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[routes]
|
||||||
|
#[get]
|
||||||
|
async fn index() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
use actix_web::App;
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
}
|
45
tests/trybuild/routes-missing-args-fail.stderr
Normal file
45
tests/trybuild/routes-missing-args-fail.stderr
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
error: unexpected end of input, expected string literal
|
||||||
|
--> tests/trybuild/routes-missing-args-fail.rs:4:1
|
||||||
|
|
|
||||||
|
4 | #[get]
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the attribute macro `get` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error: invalid service definition, expected #[<method>("<path>")]
|
||||||
|
--> tests/trybuild/routes-missing-args-fail.rs:4:1
|
||||||
|
|
|
||||||
|
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
|
||||||
|
--> tests/trybuild/routes-missing-args-fail.rs:13:55
|
||||||
|
|
|
||||||
|
13 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
|
note: required by a bound in `App::<T>::service`
|
||||||
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
||||||
|
| F: HttpServiceFactory + 'static,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
13
tests/trybuild/routes-missing-method-fail.rs
Normal file
13
tests/trybuild/routes-missing-method-fail.rs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[routes]
|
||||||
|
async fn index() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
use actix_web::App;
|
||||||
|
|
||||||
|
let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
}
|
31
tests/trybuild/routes-missing-method-fail.stderr
Normal file
31
tests/trybuild/routes-missing-method-fail.stderr
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
error: The #[routes] macro requires at least one `#[<method>(..)]` attribute.
|
||||||
|
--> tests/trybuild/routes-missing-method-fail.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[routes]
|
||||||
|
| ^^^^^^^^^
|
||||||
|
|
|
||||||
|
= note: this error originates in the attribute macro `routes` (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String> {index}: HttpServiceFactory` is not satisfied
|
||||||
|
--> tests/trybuild/routes-missing-method-fail.rs:12:55
|
||||||
|
|
|
||||||
|
12 | let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
| ------- ^^^^^ the trait `HttpServiceFactory` is not implemented for fn item `fn() -> impl std::future::Future<Output = String> {index}`
|
||||||
|
| |
|
||||||
|
| required by a bound introduced by this call
|
||||||
|
|
|
||||||
|
= help: the following other types implement trait `HttpServiceFactory`:
|
||||||
|
(A, B)
|
||||||
|
(A, B, C)
|
||||||
|
(A, B, C, D)
|
||||||
|
(A, B, C, D, E)
|
||||||
|
(A, B, C, D, E, F)
|
||||||
|
(A, B, C, D, E, F, G)
|
||||||
|
(A, B, C, D, E, F, G, H)
|
||||||
|
(A, B, C, D, E, F, G, H, I)
|
||||||
|
and $N others
|
||||||
|
note: required by a bound in `App::<T>::service`
|
||||||
|
--> $WORKSPACE/actix-web/src/app.rs
|
||||||
|
|
|
||||||
|
| F: HttpServiceFactory + 'static,
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`
|
|
@ -1,6 +1,8 @@
|
||||||
use actix_web_codegen_const_routes::*;
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
#[route("/", method="UNEXPECTED")]
|
#[routes]
|
||||||
|
#[get("/")]
|
||||||
|
#[post("/")]
|
||||||
async fn index() -> String {
|
async fn index() -> String {
|
||||||
"Hello World!".to_owned()
|
"Hello World!".to_owned()
|
||||||
}
|
}
|
||||||
|
@ -14,4 +16,8 @@ async fn main() {
|
||||||
let request = srv.get("/");
|
let request = srv.get("/");
|
||||||
let response = request.send().await.unwrap();
|
let response = request.send().await.unwrap();
|
||||||
assert!(response.status().is_success());
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.post("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
}
|
}
|
|
@ -1,28 +1,44 @@
|
||||||
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
|
||||||
|
|
|
|
||||||
25 | #[delete("/five", method="GET")]
|
25 | #[delete("/five", method="GET")]
|
||||||
|
|
Loading…
Reference in a new issue