Rust Axum - Extracting Query Param of Vec
Tags: rust, axum
Published: 2023-03-29 07:38:50

Hi, 
If you are a rust web dev beginner just like me, chances you will run into a task whereas you want to extract array of things from query param like this
`../api/games?categories=arcade,puzzle,...`

You will soon find out that axum predefined [Query Extractor](https://docs.rs/axum/latest/axum/extract/index.html#common-extractors) does not support that. It supports only one value for each param. Then you will run in to this Github issue which the idea is right but the sample code is outdated (or at least for me)
https://github.com/tokio-rs/axum/issues/434
https://github.com/tokio-rs/axum/discussions/1719


There are 2 solutions
1. Use `axum-extra` crate 
2. Implement your own custom extractor 

These two different is as follows
- For `axum-extra` the url that from the client has to be in `../api/games?categories=arcade,puzzle,...&` format.
- For your own custom extractor, which you will use `serde-qs`, the url will be in the format of `../api/games?categories[]=arcade&categories[]=puzzle,...`

For me, the first option is cleaner (and easier to implement) but I will show you both option

## 1. Use axum-extra crate
This one is very easy. Go to your terminal and run `cargo add axum-extra -F query` and the crate shall be installed.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzf5vrqwywjdrp5e18vx.png)

Then in your route, just switch from common extractor to this one instead by adding `use axum_extra::extract::Query;`  instead of normal `use axum::extract::Query;`

Here's sample code

```rust
use axum::{http::StatusCode};
use serde::Deserialize;
use axum_extra::extract::Query;

use crate::{errors::app_error::AppError};

#[derive(Deserialize, Debug)]
pub struct GameParam {
  pub categories: Vec<String>
}

pub async fn get_games(Query(params): Query<GameParam>) -> Result<StatusCode, AppError> {
  let category = params.categories;
  dbg!(&category);
  Ok(StatusCode::OK)
}
```



## 2. Implement your own custom extractor

If by any chances, axum_extra is not enough for you, you can implement your own extractor 

There are two way of implementing custom extractor. The different is that [FromRequestParts](https://docs.rs/axum/latest/axum/extract/index.html#implementing-fromrequestparts) will give you access to only request parts and [FromRequest](https://docs.rs/axum/latest/axum/extract/index.html#implementing-fromrequest) will give you access to the whole request body

### FromRequestParts
Here is the code for extractor. Notice that we add generic T that must implement Deserialize trait
```rust
use axum::{
  async_trait,
  extract::FromRequestParts,
  http::{
      StatusCode,
      request::Parts,
  },
};

pub struct Qs2<T>(pub T);

#[async_trait]
impl<S, T> FromRequestParts<S> for Qs2<T>
where
  S: Send + Sync,
  T: serde::de::DeserializeOwned,
{
  type Rejection = (StatusCode, &'static str);

  async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
    let query = parts.uri.query().unwrap();
    Ok(Self(serde_qs::from_str(query).unwrap()))
  }
}
```

Here's how we use it
```rust
use axum::{http::StatusCode};
use serde::Deserialize;

use crate::{errors::app_error::AppError, extractors::{qs2::Qs2}};

#[derive(Deserialize, Debug)]
pub struct GameParam {
  pub categories: Vec<String>
}

pub async fn get_games(Qs2(params): Qs2<GameParam>) -> Result<StatusCode, AppError> {

  let categories = params.categories;
  dbg!(&categories);
  Ok(StatusCode::OK)
}
```

Here's what the result looks like when we call `../games?categories[]=arcade&categories[]=puzzle`

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qhcjo3f6npbhwrpkfjht.png)

### FromRequest

The is very similar to above, the only different is function signature. 
```rust
use std::convert::Infallible;

use axum::{async_trait, extract::FromRequest, http::Request};

pub struct Qs<T>(pub T);

#[async_trait]
impl<S, B, T> FromRequest<S, B> for Qs<T>
where
    // these bounds are required by `async_trait`
    B: Send + 'static,
    S: Send + Sync,
    T: serde::de::DeserializeOwned,
{
    type Rejection = Infallible;

    async fn from_request(req: Request<B>, state: &S) -> Result<Self, Self::Rejection> {
        let query = req.uri().query().unwrap();
        Ok(Self(serde_qs::from_str(query).unwrap()))
    }
}
```

```rust
use axum::{http::StatusCode};
use serde::Deserialize;

use crate::{errors::app_error::AppError, extractors::{qs::Qs}};

#[derive(Deserialize, Debug)]
pub struct GameParam {
  pub categories: Vec<String>
}

pub async fn get_games(Qs(params): Qs<GameParam>) -> Result<StatusCode, AppError> {

  let categories = params.categories;
  dbg!(&categories);
  Ok(StatusCode::OK)
}
```


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wv3v4jfwtuisqajpp7ho.png)



That's it! Hope this help fellow Rust learning just like me out there!