How to design an Axum server with test-friendliness in mind? - rust

When I tried to build an application with axum, I failed to separate the framework from my handler. With Go, the classic way is define an Interface, implement it and register the handler to framework. In this way, it's easy to provide a mock handler to test with. However, I couldn't make it work with Axum. I defined a trait just like above, but it wouldn't compile:
use std::net::ToSocketAddrs;
use std::sync::{Arc, Mutex};
use serde_derive::{Serialize, Deserialize};
use serde_json::json;
use axum::{Server, Router, Json};
use axum::extract::Extension;
use axum::routing::BoxRoute;
use axum::handler::get;
#[tokio::main]
async fn main() {
let app = new_router(
Foo{}
);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
trait Handler {
fn get(&self, get: GetRequest) -> Result<GetResponse, String>;
}
struct Foo {}
impl Handler for Foo {
fn get(&self, req: GetRequest) -> Result<GetResponse, String> {
Ok(GetResponse{ message: "It works.".to_owned()})
}
}
fn new_router<T:Handler>(handler: T) -> Router<BoxRoute> {
Router::new()
.route("/", get(helper))
.boxed()
}
fn helper<T:Handler>(
Extension(mut handler): Extension<T>,
Json(req): Json<GetRequest>
) -> Json<GetResponse> {
Json(handler.get(req).unwrap())
}
#[derive(Debug, Serialize, Deserialize)]
struct GetRequest {
// omited
}
#[derive(Debug, Serialize, Deserialize)]
struct GetResponse {
message: String
// omited
}
error[E0599]: the method `boxed` exists for struct `Router<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>>>`, but its trait bounds were not satisfied
--> src/router.rs:25:10
|
25 | .boxed()
| ^^^^^ method cannot be called on `Router<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>>>` due to unsatisfied trait bounds
|
::: /Users/lebrancebw/.cargo/registry/src/github.com-1ecc6299db9ec823/axum-0.2.5/src/routing/mod.rs:876:1
|
876 | pub struct Layered<S> {
| --------------------- doesn't satisfy `<_ as tower_service::Service<Request<_>>>::Error = _`
|
= note: the following trait bounds were not satisfied:
`<axum::routing::Layered<Trace<axum::routing::Layered<AddExtension<Nested<Router<BoxRoute>, Route<axum::handler::OnMethod<fn() -> impl Future {direct}, _, (), EmptyRouter>, EmptyRouter<_>>>, T>>, SharedClassifier<ServerErrorsAsFailures>>> as tower_service::Service<Request<_>>>::Error = _`
I guess the key point is my design is apparently not "rustic". Is there a way to structure an Axum project that lends itself to testing easily?

The question is what you want to test. I will assume that you have some core logic and an HTTP layer. And you want to make sure that:
requests are routed correctly;
requests are parsed correctly;
the core logic is called with the expected parameters;
and return values from the core are correctly formatted into HTTP responses.
To test it you want to spawn an instance of the server with the core logic mocked out.
#lukemathwalker in his blog and book "Zero To Production In Rust" has a very nice description of how to spawn an app for testing through the actual TCP port. It is written for Actix-Web, but the idea applies to Axum as well.
You should not use axum::Server::bind, but rather use axum::Server::from_tcp to pass it a std::net::TcpListner which allows you to spawn a test server on any available port using `TcpListener::bind("127.0.0.1:0").
To make the core logic injectable (and mockable) I declare it as a struct and implement all the business methods on it. Like this:
pub struct Core {
public_url: Url,
secret_key: String,
storage: Storage,
client: SomeThirdPartyClient,
}
impl Core {
pub async fn create_something(
&self,
new_something: NewSomething,
) -> Result<Something, BusinessErr> {
...
}
With all these pieces you can write a function to start the server:
pub async fn run(listener: TcpListener, core: Core)
This function should encapsulate things like routing configuration, server logging configuration and so on.
Core can be provided to handlers using Extension Layer mechanism like this:
...
let shared_core = Arc::new(core);
...
let app = Router::new()
.route("/something", post(post_something))
...
.layer(AddExtensionLayer::new(shared_core));
Which in a handler can be declared in parameter list using extension extractor:
async fn post_something(
Extension(core): Extension<Arc<Core>>,
Json(new_something): Json<NewSomething>,
) -> impl IntoResponse {
core
.create_something(new_something)
.await
}
Axum examples contain one on error handling and dependency injection. You can check it here.
Last but not least, now you can mock Core out with a library like mockall, write spawn_app function that would return host and port where the server is run, run some requests against it and do assertions.
The video from Bogdan at Let's Get Rusty channel provides a good start with mockall.
I will be happy to provide more details if you feel something is missing from the answer.

Related

What is the most meaningful way to use futures with async/await inside of a Rocket route handler?

I have been building an http server in Rust to handle requests from a React client. I am using the rocket framework to manage the server configuration and the sqlx crate to create and perform actions on a connection pool. I need asynchronous handler functions to allow awaiting the database connection within the handler, but I can't seem to get the compiler to agree with the handler's return type. I've attempted to write a custom implementation of the Responder trait for sending my User struct as a json string, but the compiler continues to suggest that my struct has inadequate implementation. I've tried responding with serde_json, which is pre-equipped with the Responder implementation, but then the compiler complains that the future generated by the async handler lacks the implementation... I am incredibly new to this inspiring but perplexing language, so I can only hope this question isn't entirely foolish... Any suggestions you have based on prior experience with orchestrating communication between a Rust mysql connection and Rocket server would be outstandingly appreciated!
Here is the main server config:
pub async fn main() {
//
// Rocket Server Handler
//
let options = Options::Index | Options::DotFiles;
let sql_pool = db::main().await;
rocket::ignite()
.attach(CORS())
.mount("/api/login", routes![login])
.mount(
"/",
StaticFiles::new(concat!(env!("CARGO_MANIFEST_DIR"), "/static"), options),
)
.manage(DBConn {
connection: sql_pool,
})
.launch();
}
And here is the login route that won't compile:
#[post("/", format = "application/json", data = "<input>")]
pub async fn login(sql_pool: State<DBConn, '_>, input: Json<LoginInput>) -> Json<String> {
let input = input.into_inner();
println!("{:?}", input);
let is_valid = db::models::User::login_by_email(
&sql_pool.connection,
"jdiehl2236#gmail.com".to_string(),
"supersecret".to_string(),
)
.await;
let response = LoginResponse {
username: "userguy".to_string(),
email: "email#gmail.com".to_string(),
success: is_valid,
};
Json(serde_json::to_string(&response).unwrap())
}
And the compiler error:
the trait bound `impl Future<Output = rocket_contrib::json::Json<std::string::String>>: Responder<'_>` is not satisfied
the following other types implement trait `Responder<'r>`:
<&'r [u8] as Responder<'r>>
<&'r str as Responder<'r>>
<() as Responder<'r>>
<Flash<R> as Responder<'r>>
<JavaScript<R> as Responder<'r>>
<JsonValue as Responder<'a>>
<LoginResponse as Responder<'r>>
<MsgPack<R> as Responder<'r>>
and 29 othersrustcClick for full compiler diagnostic
handler.rs(202, 20): required by a bound in `handler::<impl Outcome<rocket::Response<'r>, rocket::http::Status, rocket::Data>>::from`

Generic async trait that returns the implemented Struct

I'm getting blocked on what I think it's a simple problem. I'm still learning Rust, and I want to do the following:
I want to create an async trait (using async-trait) that will instantiate a DB connection instance and it will return the struct that is implementing that trait.
mongo.rs
#[async_trait]
pub trait DB {
async fn init<T, E>() -> Result<T, E>;
}
Then: favorites.rs (See the implementation of the DB trait down below)
use async_trait::async_trait;
use mongodb::Collection;
use rocket::form::FromForm;
use rocket::serde::ser::StdError;
use serde::{Deserialize, Serialize};
use std::error::Error;
use uuid::Uuid;
pub struct FavoritesDB {
collection: Collection<Favorite>,
}
#[derive(Debug)]
pub enum FavoritesError {
UnknownError(Box<dyn Error>),
}
// Conflicts with the one down below
// impl From<Box<dyn Error>> for FavoritesError {
// fn from(err: Box<dyn Error>) -> FavoritesError {
// FavoritesError::UnknownError(err)
// }
// }
impl From<Box<dyn StdError>> for FavoritesError {
fn from(err: Box<dyn StdError>) -> FavoritesError {
FavoritesError::UnknownError(err)
}
}
#[async_trait]
impl mongo::DB for FavoritesDB {
async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError> {
let main_db = mongo::init::<Favorite>("Favorites").await?;
let db = FavoritesDB {
collection: main_db.collection,
};
Ok(db)
}
}
There are a list of problems with this:
1)
error[E0574]: expected struct, variant or union type, found type parameter `FavoritesDB`
--> src\db\favorites.rs:41:18
|
41 | let db = FavoritesDB {
| ^^^^^^^^^^^ not a struct, variant or union type
|
help: consider importing this struct instead
I've tried implementing From<Box<dyn tdError>> manually but it conflicts with what I have.
error[E0277]: `?` couldn't convert the error to `FavoritesError`
--> src\db\favorites.rs:40:65
|
40 | let main_db = mongo::init::<Favorite>("Favorites").await?;
| ^ the trait `From<Box<dyn StdError>>` is not implemented for `FavoritesError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait
= note: required because of the requirements on the impl of `FromResidual<Result<Infallible, Box<dyn StdError>>>` for `Result<FavoritesDB, FavoritesError>`
note: required by `from_residual`
--> C:\Users\asili\.rustup\toolchains\nightly-2021-11-15-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\ops\try_trait.rs:339:5
|
339 | fn from_residual(residual: R) -> Self;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: consider further restricting this bound
|
39 | async fn init<FavoritesDB, FavoritesError + std::convert::From<std::boxed::Box<dyn std::error::Error>>>() -> Result<FavoritesDB, FavoritesError> {
| ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Some errors have detailed explanations: E0277, E0282, E0574.
For more information about an error, try `rustc --explain E0277`.
Just for more context, here's the DB struct and impl (Currently connecting to a local MongoDB) included in mongo.rs
pub struct Database<T> {
client: mongodb::Database,
pub collection: Collection<T>,
}
impl<T> Database<T> {
pub async fn init() -> Result<mongodb::Database, Box<dyn Error>> {
let mut client_options = ClientOptions::parse("mongodb://localhost:27017").await?;
client_options.app_name = Some("My App".to_string());
// Get a handle to the deployment.
let client = Client::with_options(client_options)?;
let db = client.database("rust-svelte");
return Ok(db);
}
}
pub async fn init<T>(collection: &str) -> Result<Database<T>, Box<dyn Error>> {
let client = Database::<T>::init().await?;
let collection = client.collection::<T>(collection);
let db = Database { client, collection };
Ok(db)
}
I've been searching for a few days over SO and the Rust community and my Google-Rust-Fu isn't good enough to spot what's the problem. Any ideas?
You've declared init to take 2 generic parameters: T and E.
This means that the code that calls init has to provide the concrete types to fill in those parameters. For example, if someone was using your library, it would be totally feasible for them to write init::<i64, ()>(), and your code should deal with that.
Because of that, when you define your impl DB for FavouritesDB, you write this:
async fn init<FavoritesDB, FavoritesError>() -> Result<FavoritesDB, FavoritesError>
This is no different to writing:
async fn init<T, E>() -> Result<T, E>
you've just given the type parameters different names that happen to match a struct that you're probably trying to use.
A better pattern might be an associated type. Instead of the caller deciding what the concrete types are (as is the case with generics), with associated types, the implementation of the trait on the type sets the type.
This is common with things like Iterator. Iterator has no generic parameters, but a single associated type Item. This is because it wouldn't make sense to be able to impl Iterator<String> for MyStruct and impl Iterator<i64> for MyStruct at the same time. Instead, we want to implement Iterator for a type once, and that implementation carries with it the definition of the types it expects.
So something like this (I've omitted the async-ness for brevity since it doesn't seem to be a factor here):
trait DB {
type InitOk;
type InitErr;
fn init() -> Result<Self::InitOk, Self::InitErr>;
}
impl Db for FavouritesDB {
type InitOk = FavouritesDB;
type InitErr = FavouritesError;
fn init() -> Result<Self::InitOk, Self::InitErr> {
// now you can reference FavouritesDB the struct, rather than the generic parameter
}
}
I'd also add you may want to not have the InitOk type, and just return Self, but that's up to you if you think you might want a struct to be able to create a different type.
For part 2, Rust assumes nothing (other than Sized) about generic parameters. If you want Rust to force a generic to have some property, you have to add a bound.
The compiler is telling you here that it can't use the ? operator to convert automatically, because it doesn't know that your error type has a From<Box<dyn Error>> implementation.
If you know that every error type is going to implement that, you can add it as a bound on the associated type, like this:
trait DB {
type InitOk;
type InitErr: From<Box<dyn Error>>;
// ...
}

Unit testing actix actors, sending messages to different types of actor

I'm trying to use the Rust Actix actor framework where I have a thread polling some hardware, forming simple event messages and sending them to an Actor via its Addr. There's an enum KeyingEvent that forms the Message.
In my tests, I have an Actor that captures incoming KeyingEvents, and records them for the test to verify:
struct CapturingKeyingEventReceiver {
received_keying_events: Vec<KeyingEvent>,
}
impl CapturingKeyingEventReceiver {
fn new() -> Self {
Self {
received_keying_events: vec![],
}
}
}
impl Actor for CapturingKeyingEventReceiver {
type Context = Context<Self>;
}
impl Handler<KeyingEvent> for CapturingKeyingEventReceiver {
type Result = ();
fn handle(&mut self, keying_event: KeyingEvent, _ctx: &mut Self::Context) {
info!("Keying Event {}", keying_event);
self.received_keying_events.push(keying_event);
}
}
I start this and get its Addr with:
let capturing_keying_event_receiver = CapturingKeyingEventReceiver::new().start();
This is an Addr, and I could pass this to my threaded code's constructor....
However in my application code, I have a different Actor, KeyingEventRouter that also handles these messages and routes them to various other subsystems:
struct KeyingEventRouter {
...
}
impl Actor for KeyingEventRouter {
...
}
impl Handler<KeyingEvent> for KeyingEventRouter {
...
}
The problem I have is in my threaded code constructor, how can I pass a generic Addr<???> as the destination actor to which the thread should send messages? - I've tried specifying the parameter in the constructor as:
pub fn new(keying_event_receiver: Addr<dyn Actor>) -> Self {
...
But Rust disallows this:
help: specify the associated type: `Actor<Context = Type>`
I'd like it to be able to send these messages to any actor that is a Handler<KeyingEvent>. How do I allow the constructor to take an Addr<CapturingKeyingEventReceiver> in my test, and an Addr<KeyingEventRouter> in my application code?
In Java (handwaving a bit) I'd create an interface KeyingEventReceiver, have CapturingKeyingEventReceiver and KeyingEventRouter inherit from it, and pass an Addr<KeyingEventReceiver>..... but how do you say 'impl Actor from KeyingEventReceiver' since KeyingEventReceiver has to be a concrete struct?
You are looking for Recipient
You would need to do:
let addr = CapturingKeyingEventReceiver::new().start();
/// https://docs.rs/actix/0.12.0/src/actix/address/mod.rs.html#142-150
let keying_event_receiver = addr.recipient();
pub fn new(keying_event_receiver: Recipient<KeyingEvent>) -> Self {
keying_event_receiver.send(...)
You can also see these tests on the actix crate: https://docs.rs/actix/0.12.0/src/actix/address/mod.rs.html#401

Can I use a trait with default implementations for all functions directly without a concrete implementation? [duplicate]

When calling a default implementation on a trait which does not take self, why does it neeed an implementing type to be annotated?
A minimal, reproducible example is below (playground):
mod builder {
pub trait Builder: Sized {
fn new() -> Simple {
Simple
}
}
pub struct Simple;
impl Builder for Simple {}
}
pub fn main() {
let _ = builder::Builder::new();
/* Working version */
// use builder::Builder;
// let _ = builder::Simple::new();
}
Which gives:
error[E0283]: type annotations needed
--> src/main.rs:14:13
|
3 | fn new() -> Simple {
| ------------------ required by `builder::Builder::new`
...
14 | let _ = builder::Builder::new();
| ^^^^^^^^^^^^^^^^^^^^^ cannot infer type
|
= note: cannot satisfy `_: builder::Builder`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0283`.
error: could not compile `playground`.
To learn more, run the command again with --verbose.
The compiler explanation for E0283 does not mention a default implementation, which I agree it makes sense. But for default implementations, why is a type required?
This is not only a default implementation but the very specific case in which this default implementation does not even mention Self/self in its parameters, result and body.
I find much more easy to understand a rule saying that a type is required every time we use a trait, in any case, rather that « except if the default implementation does not even mention Self/self in its parameters, result and body ».
For this very specific use case, where you do not want to explicitly name a type when calling the function you need, I suggest using a free function.
mod builder {
// ...
pub fn make_default() -> Simple {
Simple
}
// ...
}
pub fn main() {
let _ = builder::make_default();
}
Provided methods in Rust are not like static methods in Java. Even a function with no arguments and a default implementation can be overridden by implementors. Consider adding another type that implements Builder but overrides the new function:
struct Noisy {}
impl builder::Builder for Noisy {
fn new() -> builder::Simple {
println!("Ahahahah I'm creating a Simple!!!11!");
builder::Simple
}
}
fn main() {
// wait, should this call builder::Simple::new() or Noisy::new()?
let _ = builder::Builder::new();
}
If you want the effect of a Java static function that always has the same behavior, you should use a free function, as prog-fh's answer also suggests.

How to store a hyper::server::Server as a field in a struct?

I have a library which uses hyper internally. I want the user to be able to create an Appwhich contains a Server internally that handles HTTP connections.
use hyper::server::conn::AddrIncoming;
use hyper::server::Server;
use hyper::service::service_fn_ok;
use std::net::SocketAddr;
pub struct App {
inner: Server<AddrIncoming, ()>,
}
impl App {
pub fn new() -> Self {
let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
let inner = Server::bind(&addr).serve(|| service_fn_ok(|_req| unimplemented!()));
App { inner }
}
}
(Playground link)
The error is, as expected:
error[E0308]: mismatched types
--> src/lib.rs:15:15
|
15 | App { inner }
| ^^^^^ expected (), found closure
|
= note: expected type `hyper::server::Server<_, ()>`
found type `hyper::server::Server<_, [closure#src/lib.rs:13:47: 13:88]>`
It's not well documented, but the second type parameter for Server is the kind of MakeService it uses.
I can't figure out how to refer to the closure in the type of inner. Is there some way that I can box the closure to make the code compile? Is there a way to implement MakeService by hand, instead of using a closure?
The hyper docs refer to the function make_service_fn, which returns a MakeServiceFn, but the type isn't public, so I can't use it in the type of inner.
The problem is due to a type mismatch. In Rust, a type parameter is part of the type of a struct, so the type parameters for the server in your struct must match the ones you defined in your struct. In your case they don't.
There are 2 solutions to your problem.
Add a type parameter for the second server parameter to your struct
pub struct App<T> {
inner: Server<AddrIncoming, T>,
}
Now you'll be able to create apps with different types for the second type parameter of the server
Find the type of the second argument of the server that you are creating
In your case, the type of the second argument is ``, so you would declare your struct like this:
type Service = ?; // This is really hard to find in this case.
pub struct App {
inner: Server<AddrIncoming, Service>,
}
Conclusion
In your case, I would recommend the first one because the type of the second type parameter of Server is hard to find and could very well change during the development of your program, so it's much easier to just have a type parameter on your struct.
However, sometimes you won't be able to use certain method on your server if you don't know that its type parameters don't implement certain traits, so you can add these traits to your type parameter like this:
pub struct App<T: Service> {
inner: Server<AddrIncoming, T>,
}
It is recommended not to put the type parameters on the struct itself and to only put them on the impl blocks:
pub struct App<T> {
inner: Server<AddrIncoming, T>,
}
impl App<T: Service> {
// Here you'll be able to use the method from Server where T has to be a Service.
}
You can also do the same for functions like this:
pub struct App<T> {
inner: Server<AddrIncoming, T>,
}
fn some_function(app: App<T: Service>) {
// Here you'll be able to use the method from Server where T has to be a Service
}

Resources