feat: mv realaravinth/actix-web/actix-web-codegen .
This commit is contained in:
parent
733e8810d0
commit
a684688a81
20 changed files with 2574 additions and 0 deletions
1348
Cargo.lock
generated
Normal file
1348
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
33
Cargo.toml
Normal file
33
Cargo.toml
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
[package]
|
||||||
|
name = "actix-web-codegen-const-routes"
|
||||||
|
version = "4.0.0"
|
||||||
|
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"
|
||||||
|
authors = [
|
||||||
|
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||||
|
"Rob Ede <robjtede@icloud.com>",
|
||||||
|
"Aravinth Manivannan <realaravinth@batsense.net>",
|
||||||
|
]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-router = "0.5.0"
|
||||||
|
proc-macro2 = "1"
|
||||||
|
quote = "1"
|
||||||
|
syn = { version = "1", features = ["full", "parsing"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
actix-macros = "0.2.3"
|
||||||
|
actix-rt = "2.2"
|
||||||
|
actix-test = "0.1.0-beta.13"
|
||||||
|
actix-utils = "3.0.0"
|
||||||
|
actix-web = "4.0.0"
|
||||||
|
|
||||||
|
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||||
|
trybuild = "1"
|
||||||
|
rustversion = "1"
|
195
src/lib.rs
Normal file
195
src/lib.rs
Normal file
|
@ -0,0 +1,195 @@
|
||||||
|
//! Routing and runtime macros for Actix Web.
|
||||||
|
//!
|
||||||
|
//! # Actix Web Re-exports
|
||||||
|
//! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to
|
||||||
|
//! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this
|
||||||
|
//! crate before the actix-web dependency is updated. Therefore, code examples here will show
|
||||||
|
//! explicit imports. Check the latest [actix-web attributes docs] to see which macros
|
||||||
|
//! are re-exported.
|
||||||
|
//!
|
||||||
|
//! # Runtime Setup
|
||||||
|
//! Used for setting up the actix async runtime. See [macro@main] macro docs.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! #[actix_web_codegen_const_routes::main] // or `#[actix_web::main]` in Actix Web apps
|
||||||
|
//! async fn main() {
|
||||||
|
//! async { println!("Hello world"); }.await
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Single Method Handler
|
||||||
|
//! There is a macro to set up a handler for each of the most common HTTP methods that also define
|
||||||
|
//! additional guards and route-specific middleware.
|
||||||
|
//!
|
||||||
|
//! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE]
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use actix_web::HttpResponse;
|
||||||
|
//! # use actix_web_codegen_const_routes::get;
|
||||||
|
//! #[get("/test")]
|
||||||
|
//! async fn get_handler() -> HttpResponse {
|
||||||
|
//! HttpResponse::Ok().finish()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Multiple Method Handlers
|
||||||
|
//! Similar to the single method handler macro but takes one or more arguments for the HTTP methods
|
||||||
|
//! it should respond to. See [macro@route] macro docs.
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # use actix_web::HttpResponse;
|
||||||
|
//! # use actix_web_codegen_const_routes::route;
|
||||||
|
//! #[route("/test", method = "GET", method = "HEAD")]
|
||||||
|
//! async fn get_and_head_handler() -> HttpResponse {
|
||||||
|
//! HttpResponse::Ok().finish()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Multiple Path Handlers
|
||||||
|
//! There are no macros to generate multi-path handlers. Let us know in [this issue].
|
||||||
|
//!
|
||||||
|
//! [this issue]: https://github.com/actix/actix-web/issues/1709
|
||||||
|
//!
|
||||||
|
//! [actix-web attributes docs]: https://docs.rs/actix-web/latest/actix_web/#attributes
|
||||||
|
//! [GET]: macro@get
|
||||||
|
//! [POST]: macro@post
|
||||||
|
//! [PUT]: macro@put
|
||||||
|
//! [HEAD]: macro@head
|
||||||
|
//! [CONNECT]: macro@macro@connect
|
||||||
|
//! [OPTIONS]: macro@options
|
||||||
|
//! [TRACE]: macro@trace
|
||||||
|
//! [PATCH]: macro@patch
|
||||||
|
//! [DELETE]: macro@delete
|
||||||
|
|
||||||
|
#![recursion_limit = "512"]
|
||||||
|
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||||
|
#![warn(future_incompatible)]
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
|
||||||
|
mod route;
|
||||||
|
|
||||||
|
/// Creates resource handler, allowing multiple HTTP method guards.
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// ```plain
|
||||||
|
/// #[route("path", method="HTTP_METHOD"[, attributes])]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
/// - `"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 of handler is used.
|
||||||
|
/// - `method = "HTTP_METHOD"`: Registers HTTP method to provide guard for. Upper-case string,
|
||||||
|
/// "GET", "POST" for example.
|
||||||
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Function name can be specified as any expression that is going to be accessible to the generate
|
||||||
|
/// code, e.g `my_guard` or `my_module::my_guard`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web::HttpResponse;
|
||||||
|
/// # use actix_web_codegen_const_routes::route;
|
||||||
|
/// #[route("/test", method = "GET", method = "HEAD")]
|
||||||
|
/// async fn example() -> HttpResponse {
|
||||||
|
/// HttpResponse::Ok().finish()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn route(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::with_method(None, args, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! method_macro {
|
||||||
|
($variant:ident, $method:ident) => {
|
||||||
|
#[doc = concat!("Creates route handler with `actix_web::guard::", stringify!($variant), "`.")]
|
||||||
|
///
|
||||||
|
/// # Syntax
|
||||||
|
/// ```plain
|
||||||
|
#[doc = concat!("#[", stringify!($method), r#"("path"[, attributes])]"#)]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Attributes
|
||||||
|
/// - `"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 of handler is used.
|
||||||
|
/// - `guard = "function_name"`: Registers function as guard using `actix_web::guard::fn_guard`.
|
||||||
|
/// - `wrap = "Middleware"`: Registers a resource middleware.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// Function name can be specified as any expression that is going to be accessible to the
|
||||||
|
/// generate code, e.g `my_guard` or `my_module::my_guard`.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// # use actix_web::HttpResponse;
|
||||||
|
#[doc = concat!("# use actix_web_codegen_const_routes::", stringify!($method), ";")]
|
||||||
|
#[doc = concat!("#[", stringify!($method), r#"("/")]"#)]
|
||||||
|
/// async fn example() -> HttpResponse {
|
||||||
|
/// HttpResponse::Ok().finish()
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn $method(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
route::with_method(Some(route::MethodType::$variant), args, input)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
method_macro!(Get, get);
|
||||||
|
method_macro!(Post, post);
|
||||||
|
method_macro!(Put, put);
|
||||||
|
method_macro!(Delete, delete);
|
||||||
|
method_macro!(Head, head);
|
||||||
|
method_macro!(Connect, connect);
|
||||||
|
method_macro!(Options, options);
|
||||||
|
method_macro!(Trace, trace);
|
||||||
|
method_macro!(Patch, patch);
|
||||||
|
|
||||||
|
/// Marks async main function as the Actix Web system entry-point.
|
||||||
|
///
|
||||||
|
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||||
|
/// still necessary for actor support (since actors use a `System`). Read more in the
|
||||||
|
/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// #[actix_web::main]
|
||||||
|
/// async fn main() {
|
||||||
|
/// async { println!("Hello world"); }.await
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn main(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut output: TokenStream = (quote! {
|
||||||
|
#[::actix_web::rt::main(system = "::actix_web::rt::System")]
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
output.extend(item);
|
||||||
|
output
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks async test functions to use the actix system entry-point.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```
|
||||||
|
/// #[actix_web::test]
|
||||||
|
/// async fn test() {
|
||||||
|
/// assert_eq!(async { "Hello world" }.await, "Hello world");
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||||
|
let mut output: TokenStream = (quote! {
|
||||||
|
#[::actix_web::rt::test(system = "::actix_web::rt::System")]
|
||||||
|
})
|
||||||
|
.into();
|
||||||
|
|
||||||
|
output.extend(item);
|
||||||
|
output
|
||||||
|
}
|
398
src/route.rs
Normal file
398
src/route.rs
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
use std::{collections::HashSet, convert::TryFrom};
|
||||||
|
|
||||||
|
use actix_router::ResourceDef;
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||||
|
use quote::{format_ident, quote, ToTokens, TokenStreamExt};
|
||||||
|
use syn::{parse_macro_input, AttributeArgs, Ident, LitStr, NestedMeta};
|
||||||
|
|
||||||
|
enum ResourceType {
|
||||||
|
Async,
|
||||||
|
Sync,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for ResourceType {
|
||||||
|
fn to_tokens(&self, stream: &mut TokenStream2) {
|
||||||
|
let ident = format_ident!("to");
|
||||||
|
stream.append(ident);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! method_type {
|
||||||
|
(
|
||||||
|
$($variant:ident, $upper:ident,)+
|
||||||
|
) => {
|
||||||
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum MethodType {
|
||||||
|
$(
|
||||||
|
$variant,
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MethodType {
|
||||||
|
fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
$(Self::$variant => stringify!($variant),)+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse(method: &str) -> Result<Self, String> {
|
||||||
|
match method {
|
||||||
|
$(stringify!($upper) => Ok(Self::$variant),)+
|
||||||
|
_ => Err(format!("Unexpected HTTP method: `{}`", method)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
method_type! {
|
||||||
|
Get, GET,
|
||||||
|
Post, POST,
|
||||||
|
Put, PUT,
|
||||||
|
Delete, DELETE,
|
||||||
|
Head, HEAD,
|
||||||
|
Connect, CONNECT,
|
||||||
|
Options, OPTIONS,
|
||||||
|
Trace, TRACE,
|
||||||
|
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 {
|
||||||
|
type Error = syn::Error;
|
||||||
|
|
||||||
|
fn try_from(value: &syn::LitStr) -> Result<Self, Self::Error> {
|
||||||
|
Self::parse(value.value().as_str())
|
||||||
|
.map_err(|message| syn::Error::new_spanned(value, message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Args {
|
||||||
|
path: Box<dyn PathMarker>,
|
||||||
|
resource_name: Option<syn::LitStr>,
|
||||||
|
guards: Vec<Ident>,
|
||||||
|
wrappers: Vec<syn::Type>,
|
||||||
|
methods: HashSet<MethodType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait PathMarker: quote::ToTokens {}
|
||||||
|
|
||||||
|
impl PathMarker for syn::Ident {}
|
||||||
|
impl PathMarker for syn::LitStr {}
|
||||||
|
impl PathMarker for syn::Expr {}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
fn new(args: AttributeArgs, method: Option<MethodType>) -> syn::Result<Self> {
|
||||||
|
let mut path: Option<Box<dyn PathMarker>> = None;
|
||||||
|
let mut resource_name = None;
|
||||||
|
let mut guards = Vec::new();
|
||||||
|
let mut wrappers = Vec::new();
|
||||||
|
let mut methods = HashSet::new();
|
||||||
|
|
||||||
|
let is_route_macro = method.is_none();
|
||||||
|
if let Some(method) = method {
|
||||||
|
methods.insert(method);
|
||||||
|
}
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
match arg {
|
||||||
|
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
|
||||||
|
None => {
|
||||||
|
let _ = ResourceDef::new(lit.value());
|
||||||
|
path = Some(Box::new(lit));
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
lit,
|
||||||
|
"Multiple paths specified! Should be only one!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
|
||||||
|
if nv.path.is_ident("name") {
|
||||||
|
if let syn::Lit::Str(lit) = nv.lit {
|
||||||
|
resource_name = Some(lit);
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.lit,
|
||||||
|
"Attribute name expects literal string!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} 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(
|
||||||
|
&nv.lit,
|
||||||
|
&format!(
|
||||||
|
"HTTP method defined more than once: `{}`",
|
||||||
|
lit.value()
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.lit,
|
||||||
|
"Attribute method expects literal string!",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
nv.path,
|
||||||
|
"Unknown attribute key is specified. Allowed: guard, method and wrap",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
arg => {
|
||||||
|
return Err(syn::Error::new_spanned(arg, "Unknown attribute."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Args {
|
||||||
|
path: path.unwrap(),
|
||||||
|
resource_name,
|
||||||
|
guards,
|
||||||
|
wrappers,
|
||||||
|
methods,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Route {
|
||||||
|
name: syn::Ident,
|
||||||
|
args: Args,
|
||||||
|
ast: syn::ItemFn,
|
||||||
|
resource_type: ResourceType,
|
||||||
|
|
||||||
|
/// The doc comment attributes to copy to generated struct, if any.
|
||||||
|
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 {
|
||||||
|
pub fn new(
|
||||||
|
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();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
let args = Args::new(args, method)?;
|
||||||
|
if args.methods.is_empty() {
|
||||||
|
return Err(syn::Error::new(
|
||||||
|
Span::call_site(),
|
||||||
|
"The #[route(..)] macro requires at least one `method` attribute",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let resource_type = if ast.sig.asyncness.is_some() {
|
||||||
|
ResourceType::Async
|
||||||
|
} else {
|
||||||
|
match ast.sig.output {
|
||||||
|
syn::ReturnType::Default => {
|
||||||
|
return Err(syn::Error::new_spanned(
|
||||||
|
ast,
|
||||||
|
"Function has no return type. Cannot be used as handler",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
name,
|
||||||
|
args,
|
||||||
|
ast,
|
||||||
|
resource_type,
|
||||||
|
doc_attributes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToTokens for Route {
|
||||||
|
fn to_tokens(&self, output: &mut TokenStream2) {
|
||||||
|
let Self {
|
||||||
|
name,
|
||||||
|
ast,
|
||||||
|
args:
|
||||||
|
Args {
|
||||||
|
path,
|
||||||
|
resource_name,
|
||||||
|
guards,
|
||||||
|
wrappers,
|
||||||
|
methods,
|
||||||
|
},
|
||||||
|
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 {
|
||||||
|
quote! {
|
||||||
|
.guard(
|
||||||
|
::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 stream = quote! {
|
||||||
|
#(#doc_attributes)*
|
||||||
|
#[allow(non_camel_case_types, missing_docs)]
|
||||||
|
pub struct #name;
|
||||||
|
|
||||||
|
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||||
|
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||||
|
#ast
|
||||||
|
let __resource = ::actix_web::Resource::new(#path)
|
||||||
|
.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_method(
|
||||||
|
method: Option<MethodType>,
|
||||||
|
args: TokenStream,
|
||||||
|
input: TokenStream,
|
||||||
|
) -> TokenStream {
|
||||||
|
let args = parse_macro_input!(args as syn::AttributeArgs);
|
||||||
|
|
||||||
|
let 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),
|
||||||
|
};
|
||||||
|
|
||||||
|
match Route::new(args, ast, method) {
|
||||||
|
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.
|
||||||
|
///
|
||||||
|
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||||
|
/// recover and show more precise errors within the macro body.
|
||||||
|
///
|
||||||
|
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||||
|
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||||
|
let compile_err = TokenStream::from(err.to_compile_error());
|
||||||
|
item.extend(compile_err);
|
||||||
|
item
|
||||||
|
}
|
293
tests/test_macro.rs
Normal file
293
tests/test_macro.rs
Normal file
|
@ -0,0 +1,293 @@
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use actix_utils::future::{ok, Ready};
|
||||||
|
use actix_web::{
|
||||||
|
dev::{Service, ServiceRequest, ServiceResponse, Transform},
|
||||||
|
http::{
|
||||||
|
self,
|
||||||
|
header::{HeaderName, HeaderValue},
|
||||||
|
StatusCode,
|
||||||
|
},
|
||||||
|
web, App, Error, HttpResponse, Responder,
|
||||||
|
};
|
||||||
|
use actix_web_codegen_const_routes::{connect, delete, get, head, options, patch, post, put, route, trace};
|
||||||
|
use futures_core::future::LocalBoxFuture;
|
||||||
|
|
||||||
|
// Make sure that we can name function as 'config'
|
||||||
|
#[get("/config")]
|
||||||
|
async fn config() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
const PATH: &str = "/path";
|
||||||
|
#[get(path = "PATH")]
|
||||||
|
async fn path() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FieldPath(&'static str);
|
||||||
|
|
||||||
|
const FIELD_PATH: FieldPath = FieldPath("/path/new/");
|
||||||
|
#[get(path = "FIELD_PATH.0")]
|
||||||
|
async fn field_path() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
async fn test_handler() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/test")]
|
||||||
|
async fn put_test() -> impl Responder {
|
||||||
|
HttpResponse::Created()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[patch("/test")]
|
||||||
|
async fn patch_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/test")]
|
||||||
|
async fn post_test() -> impl Responder {
|
||||||
|
HttpResponse::NoContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[head("/test")]
|
||||||
|
async fn head_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[connect("/test")]
|
||||||
|
async fn connect_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[options("/test")]
|
||||||
|
async fn options_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[trace("/test")]
|
||||||
|
async fn trace_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
fn auto_async() -> impl Future<Output = Result<HttpResponse, actix_web::Error>> {
|
||||||
|
ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test")]
|
||||||
|
fn auto_sync() -> impl Future<Output = Result<HttpResponse, actix_web::Error>> {
|
||||||
|
ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[put("/test/{param}")]
|
||||||
|
async fn put_param_test(_: web::Path<String>) -> impl Responder {
|
||||||
|
HttpResponse::Created()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/test/{param}")]
|
||||||
|
async fn delete_param_test(_: web::Path<String>) -> impl Responder {
|
||||||
|
HttpResponse::NoContent()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test/{param}")]
|
||||||
|
async fn get_param_test(_: web::Path<String>) -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[route("/multi", method = "GET", method = "POST", method = "HEAD")]
|
||||||
|
async fn route_test() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/custom_resource_name", name = "custom")]
|
||||||
|
async fn custom_resource_name_test<'a>(req: actix_web::HttpRequest) -> impl Responder {
|
||||||
|
assert!(req.url_for_static("custom").is_ok());
|
||||||
|
assert!(req.url_for_static("custom_resource_name_test").is_err());
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChangeStatusCode;
|
||||||
|
|
||||||
|
impl<S, B> Transform<S, ServiceRequest> for ChangeStatusCode
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type Transform = ChangeStatusCodeMiddleware<S>;
|
||||||
|
type InitError = ();
|
||||||
|
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||||
|
|
||||||
|
fn new_transform(&self, service: S) -> Self::Future {
|
||||||
|
ok(ChangeStatusCodeMiddleware { service })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ChangeStatusCodeMiddleware<S> {
|
||||||
|
service: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, B> Service<ServiceRequest> for ChangeStatusCodeMiddleware<S>
|
||||||
|
where
|
||||||
|
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||||
|
S::Future: 'static,
|
||||||
|
B: 'static,
|
||||||
|
{
|
||||||
|
type Response = ServiceResponse<B>;
|
||||||
|
type Error = Error;
|
||||||
|
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
actix_web::dev::forward_ready!(service);
|
||||||
|
|
||||||
|
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut res = fut.await?;
|
||||||
|
let headers = res.headers_mut();
|
||||||
|
let header_name = HeaderName::from_lowercase(b"custom-header").unwrap();
|
||||||
|
let header_value = HeaderValue::from_str("hello").unwrap();
|
||||||
|
headers.insert(header_name, header_value);
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/test/wrap", wrap = "ChangeStatusCode")]
|
||||||
|
async fn get_wrap(_: web::Path<String>) -> impl Responder {
|
||||||
|
// panic!("actually never gets called because path failed to extract");
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_params() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(get_param_test)
|
||||||
|
.service(put_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 response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::OK);
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PUT, srv.url("/test/it"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::CREATED);
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::DELETE, srv.url("/test/it"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), http::StatusCode::NO_CONTENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_body() {
|
||||||
|
let srv = actix_test::start(|| {
|
||||||
|
App::new()
|
||||||
|
.service(post_test)
|
||||||
|
.service(put_test)
|
||||||
|
.service(head_test)
|
||||||
|
.service(connect_test)
|
||||||
|
.service(options_test)
|
||||||
|
.service(trace_test)
|
||||||
|
.service(patch_test)
|
||||||
|
.service(test_handler)
|
||||||
|
.service(route_test)
|
||||||
|
.service(custom_resource_name_test)
|
||||||
|
});
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::HEAD, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::CONNECT, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::OPTIONS, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::TRACE, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PATCH, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PUT, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
assert_eq!(response.status(), http::StatusCode::CREATED);
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::POST, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
assert_eq!(response.status(), http::StatusCode::NO_CONTENT);
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::POST, srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::HEAD, srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::PATCH, srv.url("/multi"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(!response.status().is_success());
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/custom_resource_name"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_rt::test]
|
||||||
|
async fn test_auto_async() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(auto_async));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test"));
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn test_wrap() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(get_wrap));
|
||||||
|
|
||||||
|
let request = srv.request(http::Method::GET, srv.url("/test/wrap"));
|
||||||
|
let mut response = request.send().await.unwrap();
|
||||||
|
assert_eq!(response.status(), StatusCode::NOT_FOUND);
|
||||||
|
assert!(response.headers().contains_key("custom-header"));
|
||||||
|
let body = response.body().await.unwrap();
|
||||||
|
let body = String::from_utf8(body.to_vec()).unwrap();
|
||||||
|
assert!(body.contains("wrong number of parameters"));
|
||||||
|
}
|
18
tests/trybuild.rs
Normal file
18
tests/trybuild.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#[rustversion::stable(1.54)] // MSRV
|
||||||
|
#[test]
|
||||||
|
fn compile_macros() {
|
||||||
|
let t = trybuild::TestCases::new();
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/simple.rs");
|
||||||
|
t.compile_fail("tests/trybuild/simple-fail.rs");
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/route-ok.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-unexpected-method-fail.rs");
|
||||||
|
t.compile_fail("tests/trybuild/route-malformed-path-fail.rs");
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/docstring-ok.rs");
|
||||||
|
|
||||||
|
t.pass("tests/trybuild/test-runtime.rs");
|
||||||
|
}
|
17
tests/trybuild/docstring-ok.rs
Normal file
17
tests/trybuild/docstring-ok.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use actix_web::{Responder, HttpResponse, App};
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
/// doc comments shouldn't break anything
|
||||||
|
#[get("/")]
|
||||||
|
async fn index() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(index));
|
||||||
|
|
||||||
|
let request = srv.get("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
17
tests/trybuild/route-duplicate-method-fail.rs
Normal file
17
tests/trybuild/route-duplicate-method-fail.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[route("/", method="GET", method="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));
|
||||||
|
|
||||||
|
let request = srv.get("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
11
tests/trybuild/route-duplicate-method-fail.stderr
Normal file
11
tests/trybuild/route-duplicate-method-fail.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
error: HTTP method defined more than once: `GET`
|
||||||
|
--> $DIR/route-duplicate-method-fail.rs:3:35
|
||||||
|
|
|
||||||
|
3 | #[route("/", method="GET", method="GET")]
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error[E0277]: the trait bound `fn() -> impl std::future::Future {index}: HttpServiceFactory` is not satisfied
|
||||||
|
--> $DIR/route-duplicate-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}`
|
33
tests/trybuild/route-malformed-path-fail.rs
Normal file
33
tests/trybuild/route-malformed-path-fail.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use actix_web_codegen_const_routes::get;
|
||||||
|
|
||||||
|
#[get("/{")]
|
||||||
|
async fn zero() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{foo")]
|
||||||
|
async fn one() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{}")]
|
||||||
|
async fn two() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/*")]
|
||||||
|
async fn three() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{tail:\\d+}*")]
|
||||||
|
async fn four() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
|
||||||
|
async fn five() -> &'static str {
|
||||||
|
"malformed resource def"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
42
tests/trybuild/route-malformed-path-fail.stderr
Normal file
42
tests/trybuild/route-malformed-path-fail.stderr
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/route-malformed-path-fail.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[get("/{")]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: pattern "{" contains malformed dynamic segment
|
||||||
|
|
||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/route-malformed-path-fail.rs:8:1
|
||||||
|
|
|
||||||
|
8 | #[get("/{foo")]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: pattern "{foo" contains malformed dynamic segment
|
||||||
|
|
||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/route-malformed-path-fail.rs:13:1
|
||||||
|
|
|
||||||
|
13 | #[get("/{}")]
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: Wrong path pattern: "/{}" regex parse error:
|
||||||
|
((?s-m)^/(?P<>[^/]+))$
|
||||||
|
^
|
||||||
|
error: empty capture group name
|
||||||
|
|
||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/route-malformed-path-fail.rs:23:1
|
||||||
|
|
|
||||||
|
23 | #[get("/{tail:\\d+}*")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: custom regex is not supported for tail match
|
||||||
|
|
||||||
|
error: custom attribute panicked
|
||||||
|
--> $DIR/route-malformed-path-fail.rs:28:1
|
||||||
|
|
|
||||||
|
28 | #[get("/{a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}/{i}/{j}/{k}/{l}/{m}/{n}/{o}/{p}/{q}")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= help: message: Only 16 dynamic segments are allowed, provided: 17
|
17
tests/trybuild/route-missing-method-fail.rs
Normal file
17
tests/trybuild/route-missing-method-fail.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[route("/")]
|
||||||
|
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.get("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
13
tests/trybuild/route-missing-method-fail.stderr
Normal file
13
tests/trybuild/route-missing-method-fail.stderr
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
error: The #[route(..)] macro requires at least one `method` attribute
|
||||||
|
--> tests/trybuild/route-missing-method-fail.rs:3:1
|
||||||
|
|
|
||||||
|
3 | #[route("/")]
|
||||||
|
| ^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
= 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
|
||||||
|
--> tests/trybuild/route-missing-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}`
|
17
tests/trybuild/route-ok.rs
Normal file
17
tests/trybuild/route-ok.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[route("/", method="GET", method="HEAD")]
|
||||||
|
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.get("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
17
tests/trybuild/route-unexpected-method-fail.rs
Normal file
17
tests/trybuild/route-unexpected-method-fail.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[route("/", method="UNEXPECTED")]
|
||||||
|
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.get("/");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
11
tests/trybuild/route-unexpected-method-fail.stderr
Normal file
11
tests/trybuild/route-unexpected-method-fail.stderr
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
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}`
|
37
tests/trybuild/simple-fail.rs
Normal file
37
tests/trybuild/simple-fail.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[get("/one", other)]
|
||||||
|
async fn one() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post(/two)]
|
||||||
|
async fn two() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
static PATCH_PATH: &str = "/three";
|
||||||
|
|
||||||
|
#[patch(PATCH_PATH)]
|
||||||
|
async fn three() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/four", "/five")]
|
||||||
|
async fn four() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/five", method="GET")]
|
||||||
|
async fn five() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIX: &str = "/six";
|
||||||
|
|
||||||
|
#[get("/six", path="SIX")]
|
||||||
|
async fn six() -> String {
|
||||||
|
"Hello World!".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
35
tests/trybuild/simple-fail.stderr
Normal file
35
tests/trybuild/simple-fail.stderr
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
error: Unknown attribute.
|
||||||
|
--> $DIR/simple-fail.rs:3:15
|
||||||
|
|
|
||||||
|
3 | #[get("/one", other)]
|
||||||
|
| ^^^^^
|
||||||
|
|
||||||
|
error: expected identifier or literal
|
||||||
|
--> $DIR/simple-fail.rs:8:8
|
||||||
|
|
|
||||||
|
8 | #[post(/two)]
|
||||||
|
| ^
|
||||||
|
|
||||||
|
error: Unknown attribute.
|
||||||
|
--> $DIR/simple-fail.rs:15:9
|
||||||
|
|
|
||||||
|
15 | #[patch(PATCH_PATH)]
|
||||||
|
| ^^^^^^^^^^
|
||||||
|
|
||||||
|
error: Multiple paths specified! Should be only one!
|
||||||
|
--> $DIR/simple-fail.rs:20:19
|
||||||
|
|
|
||||||
|
20 | #[delete("/four", "/five")]
|
||||||
|
| ^^^^^^^
|
||||||
|
|
||||||
|
error: HTTP method forbidden here. To handle multiple methods, use `route` instead
|
||||||
|
--> $DIR/simple-fail.rs:25:19
|
||||||
|
|
|
||||||
|
25 | #[delete("/five", method="GET")]
|
||||||
|
| ^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: Multiple paths specified! Should be only one!
|
||||||
|
--> $DIR/simple-fail.rs:32:20
|
||||||
|
|
|
||||||
|
32 | #[get("/six", path="SIX")]
|
||||||
|
| ^^^^^
|
16
tests/trybuild/simple.rs
Normal file
16
tests/trybuild/simple.rs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
use actix_web::{Responder, HttpResponse, App};
|
||||||
|
use actix_web_codegen_const_routes::*;
|
||||||
|
|
||||||
|
#[get("/config")]
|
||||||
|
async fn config() -> impl Responder {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() {
|
||||||
|
let srv = actix_test::start(|| App::new().service(config));
|
||||||
|
|
||||||
|
let request = srv.get("/config");
|
||||||
|
let response = request.send().await.unwrap();
|
||||||
|
assert!(response.status().is_success());
|
||||||
|
}
|
6
tests/trybuild/test-runtime.rs
Normal file
6
tests/trybuild/test-runtime.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
#[actix_web::test]
|
||||||
|
async fn my_test() {
|
||||||
|
assert!(async { 1 }.await, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Reference in a new issue