Axum – testing API endpoints

Let’s look at different types of routes in Axum and how to create them AND test them.

As an Aide-mémoire let’s create something I/we can refer back to.

Webdock – Fast Cloud VPS Linux Hosting

Aim :

List the types of endpoint, and show how to test them with cURL, and within the Rust code itself.

If you’re new to Axum, check out timClicks tour of Axum

And also look up David Pedersen, the creator of Axum.

David Pedersen - Axum

3 common requests:

1 .Path Extract : https://docs.rs/axum/latest/axum/extract/struct.Path.html

2 .Query Extract : https://docs.rs/axum/latest/axum/extract/struct.Query.html

3. JSON Extract https://docs.rs/axum/latest/axum/struct.Json.html

I’ll post the full code at the end, but lets focus on the key line of code for each of the above.

For a more comprehensive list, check the Axum Docs

Example Code :

Path Extract

https://docs.rs/axum/latest/axum/extract/struct.Path.html#example

let app = Router::new().route("/users/:user_id", get(user_info));
curl -X GET http://localhost:3000/users/{insert_valid_uuid_here}
curl -X GET http://localhost:3000/users/550e8400-e29b-41d4-a716-446655440000

Query Extract

https://docs.rs/axum/latest/axum/extract/struct.Query.html#example-1

let app = Router::new().route("/list_things", get(list_things));
let result: Query<ExampleParams> = Query::try_from_uri(&uri).unwrap();
curl -X GET "http://example.com/path?foo=hello&bar=42"

More Axum Query Extract examples

Webdock – Fast Cloud VPS Linux Hosting

JSON Extract


  .route("/json", get(json_handler))
async fn json_handler(Query(params): Query<HashMap<String, String>>) -> Json<Value> {
    info!("handling request");
    let q: Option<&String> = params.get("q");

    Json(json!({
            "message": "Hello, Internet!",
            "q": q,
        }) )
}
❯ curl -X GET "http://0.0.0.0:3000/json?q=your_query_string"

{"message":"Hello, Internet!","q":"your_query_string"}%  

POST JSON

What if we want to post multiple keys and values to the API endpoint?

How to POST JSON to Axum API and how to EXTRACT the JSON with Axum
curl -X POST -H "Content-Type: application/json" -d '{"name": "John Doe", "age": 25, "iq":100}' http://localhost:8000/post

Full code :

#![allow(unused)]
use axum::{
    async_trait,
    extract::{FromRef, FromRequestParts, State},
    http::{request::Parts, StatusCode},
    response::IntoResponse,
    routing::{get, post},
    Router,
};

use serde::Deserialize;

#[derive(Deserialize, Debug)]
struct MyData {
    name: String,
    age: u32,
    iq: u8,
}

async fn handle_post(data: axum::extract::Json<MyData>) -> impl IntoResponse {
    let my_data = data.0;
    println!("Received data: {:?}", my_data);
    (StatusCode::OK, "Data received successfully")
}

#[tokio::main]
async fn main() {
    // Create an Axum router
    let app = Router::new().route("/post", post(handle_post));

    // Start the server
    axum::Server::bind(&"0.0.0.0:8000".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}
[package]
name = "axumpost"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
axum = "0.6"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1.0"
❯ cargo run
   Compiling axumpost v0.1.0 (/home/rag/Documents/rust/axumpost)
    Finished dev [unoptimized + debuginfo] target(s) in 1.94s
     Running `/home/rag/Documents/rust/axumpost/target/debug/axumpost`
Received data: MyData { name: "John Doe", age: 25, iq: 100 }

The key part here is :

axum::extract::Json<MyData>

Where MyData is your struct in JSON format

Test based on official Axum example code:

https://github.com/tokio-rs/axum/tree/main/examples/parse-body-based-on-content-type

We’lll add code to enable use to test the main.rs with reqwest, whenever we run “cargo test”

We’ll need to add “reqwest” crate, and then the code below at the bottom of main.rs as follows:

[tokio::test] ~ cargo test

Add this code into main :

#[tokio::test]
async fn test_endpoint() {
    // Assuming your server is running on port 3000
    let url = "http://127.0.0.1:3000";

    // Test with JSON payload
    let json_payload = serde_json::json!({"foo": "test"});
    let response = reqwest::Client::new()
        .post(url)
        .json(&json_payload)
        .send()
        .await
        .expect("Failed to send request");
    assert!(response.status().is_success());

    // Test with form payload
    let form_payload = [("foo", "test")];
    let response = reqwest::Client::new()
        .post(url)
        .form(&form_payload)
        .send()
        .await
        .expect("Failed to send request");
    assert!(response.status().is_success());
}

Be sure to specify “json” as a feature when you add reqwest:

reqwest = { version = "0.11", features = ["json"] }
[dependencies]
axum = { path = "../../axum" }
reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.108"
tokio = { version = "1.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

do “cargo run” in one terminal, then in a new terminal from same directory, run “cargo test

1 passed!

Nice!

passed_nice

Conclusion

We’ve tested Axum endpoints with cURL and using cargo test and “reqwest” crate!

Official Axum usage example : https://github.com/tokio-rs/axum/tree/main#usage-example

Bitcoin Programming

Previous article

BDK Wallet Descriptors