[actix_web] When a simple thing proves hard, is it a sign I'm doing it wrong?

I've just read the Klabnik/Nichols Rust book. All good. I have a little idea for some web service, so I think of using Rust for fun. I read the Actix documentation.

Then I add some integration tests and see that each test sets up the app/host over and over, and it differs from my production host, and I would prefer it be identical.

Here's a sample test from Actix docs:

    #[actix_web::test]
    async fn test_index_get() {
        let app = test::init_service(App::new().service(index)).await;
        let req = test::TestRequest::default()
            .insert_header(ContentType::plaintext())
            .to_request();
        let resp = test::call_service(&app, req).await;
        assert!(resp.status().is_success());
    }

I see that init_service() and I think of DRY and how I could put my much more complex host/routing setup into a function, then call that to get an App instance. This is where it gets tough.

I need to create an App so I begin reverse engineering it. It's great that I can inspect the source for Actix, but soon I'm lost in trait bounds, associated types and generics. I mean it's crazy, or is compared to C# or TS.

I end up writing the code below. To do this, I had to change my working Actix app setup code to assign to a variable and then use the VSCode debugger to hover over it and see what it is, then copy and paste from the tooltip!

The idea is to call this little factory helper fn from all my tests and my main.rs to configure my host.

fn create_app() -> App<
    impl ServiceFactory<
        Config = AppConfig,
        Request = actix_http::Request,
        Response = actix_web::dev::ServiceResponse,
        Error = actix_web::Error,
        InitError = (),
    >,
> { ... // setup App }

When I save the file, the compiler is unhappy and I need to resolve many of those types, and I find some are in strangely-named places like active_web::dev and another in a crate that I don't have referenced, actix_http.

If it's totally normal to do what I'm doing so far, then why would I need to pull in this other crate which seems a "deeper" level, beneath the main Actix abstractions?

At this point I think, this was really hard, and it feels like stroking a cat backwards.

  • Am I forcing ways of doing things from other languages onto Rust?
  • Should I be doing this?
  • Is this what Rust is like and I am just a foreign noob?

Perhaps I just need to reference that crate and I'm panicking just before I make it out the woods. I was unlucky and this is a bit of an outlier.

But also I don't want to create bad habits or bring non-idiomatic thinking over to Rust. I code alone, you see.

Thanks

1 Like

In this case you're supposed to use the App::configure method to put your service configuration in a reusable function. You're not supposed to dig into actix's inner workings, which do get gnarly.

let app = actix_web::test::init_service(
    actix_web::App::new()
        .configure(your_service_configuration_function)
).await;
5 Likes