diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index cd1ad4c66..19b0aeaf8 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -328,6 +328,9 @@ pub struct Route { /// Name of the handler function being annotated. name: syn::Ident, + /// function generic + type_generic: Option, + /// Args passed to routing macro. /// /// When using `#[routes]`, this will contain args for each specific routing macro. @@ -344,6 +347,13 @@ impl Route { pub fn new(args: RouteArgs, ast: syn::ItemFn, method: Option) -> syn::Result { let name = ast.sig.ident.clone(); + let generics = ast.sig.generics.params.clone(); + let type_generic = if let Some(syn::GenericParam::Type(ty)) = generics.into_iter().next() { + Some(ty) + } else { + None + }; + // 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 @@ -370,6 +380,7 @@ impl Route { } Ok(Self { + type_generic, name, args: vec![args], ast, @@ -380,6 +391,13 @@ impl Route { fn multiple(args: Vec, ast: syn::ItemFn) -> syn::Result { let name = ast.sig.ident.clone(); + let generics = ast.sig.generics.params.clone(); + let type_generic = if let Some(syn::GenericParam::Type(ty)) = generics.into_iter().next() { + Some(ty) + } else { + None + }; + // 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 @@ -398,6 +416,7 @@ impl Route { Ok(Self { name, + type_generic, args, ast, doc_attributes, @@ -409,6 +428,7 @@ impl ToTokens for Route { fn to_tokens(&self, output: &mut TokenStream2) { let Self { name, + type_generic, ast, args, doc_attributes, @@ -421,6 +441,17 @@ impl ToTokens for Route { #[cfg(feature = "compat-routing-macros-force-pub")] let vis = syn::Visibility::Public(::default()); + let (struct_generic, trait_generic, impl_type_generic) = + if let Some(syn::TypeParam { ident, bounds, .. }) = type_generic { + ( + Some(quote! { <#ident> (core::marker::PhantomData) }), + Some(quote! { <#ident: #bounds + 'static> }), + Some(quote! { <#ident> }), + ) + } else { + (None, None, None) + }; + let registrations: TokenStream2 = args .iter() .map(|args| { @@ -453,13 +484,19 @@ impl ToTokens for Route { } }; + let type_generic = if let Some(syn::TypeParam { ident, .. }) = type_generic { + Some(quote! { ::<#ident> }) + } else { + None + }; + quote! { let __resource = ::actix_web::Resource::new(#path) .name(#resource_name) #method_guards #(.guard(::actix_web::guard::fn_guard(#guards)))* #(.wrap(#wrappers))* - .to(#name); + .to(#name #type_generic); ::actix_web::dev::HttpServiceFactory::register(__resource, __config); } }) @@ -468,9 +505,11 @@ impl ToTokens for Route { let stream = quote! { #(#doc_attributes)* #[allow(non_camel_case_types)] - #vis struct #name; + #[derive(Default)] + #vis struct #name #struct_generic; - impl ::actix_web::dev::HttpServiceFactory for #name { + impl #trait_generic ::actix_web::dev::HttpServiceFactory for #name #impl_type_generic + { fn register(self, __config: &mut actix_web::dev::AppService) { #ast #registrations diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 8ad38cb70..2409943f0 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -5,10 +5,10 @@ description = "Actix Web is a powerful, pragmatic, and extremely fast web framew authors = ["Nikolay Kim ", "Rob Ede "] keywords = ["actix", "http", "web", "framework", "async"] categories = [ - "network-programming", - "asynchronous", - "web-programming::http-server", - "web-programming::websocket", + "network-programming", + "asynchronous", + "web-programming::http-server", + "web-programming::websocket", ] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web" @@ -18,17 +18,17 @@ rust-version.workspace = true [package.metadata.docs.rs] features = [ - "macros", - "openssl", - "rustls-0_20", - "rustls-0_21", - "rustls-0_22", - "rustls-0_23", - "compress-brotli", - "compress-gzip", - "compress-zstd", - "cookies", - "secure-cookies", + "macros", + "openssl", + "rustls-0_20", + "rustls-0_21", + "rustls-0_22", + "rustls-0_23", + "compress-brotli", + "compress-gzip", + "compress-zstd", + "cookies", + "secure-cookies", ] [package.metadata.cargo_check_external_types] @@ -58,15 +58,15 @@ allowed_external_types = [ [features] default = [ - "macros", - "compress-brotli", - "compress-gzip", - "compress-zstd", - "cookies", - "http2", - "unicode", - "compat", - "ws", + "macros", + "compress-brotli", + "compress-gzip", + "compress-zstd", + "cookies", + "http2", + "unicode", + "compat", + "ws", ] # Brotli algorithm content-encoding support @@ -203,6 +203,10 @@ required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] name = "basic" required-features = ["compress-gzip"] +[[example]] +name = "issue" +path = "examples/2866.rs" + [[example]] name = "uds" required-features = ["compress-gzip"] diff --git a/actix-web/examples/2866.rs b/actix-web/examples/2866.rs new file mode 100644 index 000000000..696dadc9d --- /dev/null +++ b/actix-web/examples/2866.rs @@ -0,0 +1,60 @@ +use actix_web::{ + dev::Server, + get, + web::{self, Data}, + App, HttpServer, Responder, +}; +use serde::Serialize; + +#[derive(Debug, Serialize, Clone, Copy)] +pub struct User { + id: u64, +} + +pub trait UserRepository { + fn get_user(&self) -> User; +} + +#[derive(Clone)] +struct UserClient; + +impl UserRepository for UserClient { + fn get_user(&self) -> User { + User { id: 99 } + } +} + +// when uncommenting following the line, the type checking is unaccepted +// because of cannot infer type parameter T +#[get("/")] +async fn index(client: web::Data) -> impl Responder { + let user = client.into_inner().get_user(); + web::Json(user) +} + +#[get("hello/{who}")] +async fn hello(who: web::Path) -> impl Responder { + format!("

hello {who}

") +} + +pub fn create_server( + search: T, +) -> Result { + let server = HttpServer::new(move || { + App::new() + .app_data(Data::new(search.clone())) + // .route("/", web::get().to(index::)) + .service(index::(core::marker::PhantomData::)) + .service(hello) + }) + .bind("127.0.0.1:8080")? + .run(); + Ok(server) +} + +#[actix_web::main] +async fn main() -> std::io::Result<()> { + println!("\x1b[1;2;36mserving on http://localhost:8080\x1b[0m"); + let user_client = UserClient; + create_server(user_client).unwrap().await +}