Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 42 additions & 3 deletions actix-web-codegen/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,9 @@ pub struct Route {
/// Name of the handler function being annotated.
name: syn::Ident,

/// function generic
type_generic: Option<syn::TypeParam>,

/// Args passed to routing macro.
///
/// When using `#[routes]`, this will contain args for each specific routing macro.
Expand All @@ -344,6 +347,13 @@ impl Route {
pub fn new(args: RouteArgs, ast: syn::ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
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
Expand All @@ -370,6 +380,7 @@ impl Route {
}

Ok(Self {
type_generic,
name,
args: vec![args],
ast,
Expand All @@ -380,6 +391,13 @@ impl Route {
fn multiple(args: Vec<Args>, ast: syn::ItemFn) -> syn::Result<Self> {
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
Expand All @@ -398,6 +416,7 @@ impl Route {

Ok(Self {
name,
type_generic,
args,
ast,
doc_attributes,
Expand All @@ -409,6 +428,7 @@ impl ToTokens for Route {
fn to_tokens(&self, output: &mut TokenStream2) {
let Self {
name,
type_generic,
ast,
args,
doc_attributes,
Expand All @@ -421,6 +441,17 @@ impl ToTokens for Route {
#[cfg(feature = "compat-routing-macros-force-pub")]
let vis = syn::Visibility::Public(<Token![pub]>::default());

let (struct_generic, trait_generic, impl_type_generic) =
if let Some(syn::TypeParam { ident, bounds, .. }) = type_generic {
(
Some(quote! { <#ident> (core::marker::PhantomData<T>) }),
Some(quote! { <#ident: #bounds + 'static> }),
Some(quote! { <#ident> }),
)
} else {
(None, None, None)
};

let registrations: TokenStream2 = args
.iter()
.map(|args| {
Expand Down Expand Up @@ -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);
}
})
Expand All @@ -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
Expand Down
52 changes: 28 additions & 24 deletions actix-web/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ description = "Actix Web is a powerful, pragmatic, and extremely fast web framew
authors = ["Nikolay Kim <[email protected]>", "Rob Ede <[email protected]>"]
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"
Expand All @@ -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]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"]
Expand Down
60 changes: 60 additions & 0 deletions actix-web/examples/2866.rs
Original file line number Diff line number Diff line change
@@ -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<T: UserRepository>(client: web::Data<T>) -> impl Responder {
let user = client.into_inner().get_user();
web::Json(user)
}

#[get("hello/{who}")]
async fn hello(who: web::Path<String>) -> impl Responder {
format!("<h1>hello {who}</h1>")
}

pub fn create_server<T: UserRepository + Send + Sync + 'static + Clone>(
search: T,
) -> Result<Server, std::io::Error> {
let server = HttpServer::new(move || {
App::new()
.app_data(Data::new(search.clone()))
// .route("/", web::get().to(index::<T>))
.service(index::<T>(core::marker::PhantomData::<T>))
.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
}
Loading