What type does service take in for the actix web library?

I have this test code:

let app = test::init_service(
            App::new()
                .app_data(db_wrapped)
                .service(<route>))
                .await;

I want to be able to abstracted it into something like this:

async fn make_app<T: ???>(routes: Vec<T>) -> WebService {
        test::init_service(
            App::new()
        .service(routes)).await
 }

I looked at the documentation, and found it took a value with the trait: IntoPatterns, but I can't seem to import it.

Edit: I should have taken a closer look at the documentation. I would need the actix_router library, I would assume

You are looking at the wrong docs. That's a free function, not the service method of App. That's here and it takes an HttpServiceFactory. The implementors of which are also enumerated in the documentation of that trait.

2 Likes

You want to use App::configure for abstracting App creation into it's own function that you can share between your tests and main function, for example:

1 Like

What do you mean by free function? Also can service take in a vector of HttpServiceFactory and how do you return an app? Sorry for all these questions

Why would my make_app function not work? Is it just how actix is setup?

A “free” function is not a method of any type or trait (that is, it is not defined inside of an impl). It is just a standalone fn item.

1 Like

thank you

Seems like you are able to get make_app to work. I was sure that it would only work for vectors with a single route (i.e. because web::resource("/test").to(|| async { "OK" }) has a distinct type from web::resource("/test2").to(|| async { "OK" }), but that is not the case).

As I was saying in my previous reply though, returning an App from a function is impossible, because AppEntry is private and you can't instantiate App other than through App::new. That's what App::configure is for.

1 Like

Thank you for the help.

I know this a bit old, but I found an issue with the function. Would you happen to know how to solve the issue?

The issue:
For my request, I return a web::Json object which has to implement Responder instead of the http response in your example. I tried making a vector of those routes (eg. vec![getUser, createUser, registerUser]), but it would defer its type to the first element in the vector (eg. let routes: getUser = ...). I tried making the vector with [web::resource("/test").to(registerUser), ....], but I am getting issues that it doesn't implement handler.
If you have any questions or want a more explicit coding example, please let me know.
Thank you again for all the help if you have been.

Please provide a minimal example, I can't reproduce your problem. This compiles on my machine:

/*
[dependencies]
actix-web = "4"
actix-http = "3.7"
*/

use actix_web::body::BoxBody;
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
use actix_web::test;
use actix_web::web;
use actix_web::{App, HttpRequest, HttpResponse, Responder};

use actix_http::Request;

async fn make_app<T>(
    routes: Vec<T>,
) -> impl Service<Request, Response = ServiceResponse<BoxBody>, Error = actix_web::Error>
where
    T: HttpServiceFactory + 'static,
{
    test::init_service(App::new().service(routes)).await
}

async fn index(_: HttpRequest) -> impl Responder {
    web::Json(())
}

async fn index2(_: HttpRequest) -> HttpResponse {
    HttpResponse::NoContent().finish()
}

#[actix_web::main]
async fn main() {
    let routes = vec![
        web::resource("/test").to(|| async { "OK" }),
        web::resource("/test2").to(|| async { "OK" }),
        web::resource("/test3").to(index),
        web::resource("/test4").route(web::get().to(index2)),
    ];

    let app = make_app(routes).await;
}

This is a minimal example of the routes and the testing. It might be because the routes are pre-defined, but I am not sure.

Ok, so what I expected before:

is true for handlers created with the get, post, etc. macros. Every handler has its own distinct type. AFAICT you need to use Resources instead of the macro-created handlers:

use actix_web::body::BoxBody;
use actix_web::dev::{HttpServiceFactory, Service, ServiceResponse};
use actix_web::test;
use actix_web::web;
use actix_web::{App, HttpRequest, Responder};

use actix_http::Request;

async fn make_app<T: HttpServiceFactory + 'static>(
    routes: Vec<T>,
) -> impl Service<Request, Response = ServiceResponse<BoxBody>, Error = actix_web::Error> {
    test::init_service(App::new().service(routes)).await
}

async fn index_get(_: HttpRequest) -> impl Responder {
    web::Json(())
}

async fn index_post(_: HttpRequest) -> impl Responder {
    web::Json(())
}

#[actix_web::main]
async fn main() {
    let routes = vec![
        web::resource("/route/index_get").route(web::get().to(index_get)),
        web::resource("/route/index_post").route(web::post().to(index_post)),
    ];

    let app = make_app(routes).await;
}

I tried to work around this by creating HttpServiceFactory trait objects from the handlers, but HttpServiceFactory::register is not dynamically dispatchable, hence this won't work unfortunately.

3 Likes

thank you so much for the help

1 Like