DEV Community

Cover image for Rust Axum - Extracting Query Param of Vec
Pongsakorn Win Semsuwan
Pongsakorn Win Semsuwan

Posted on

Rust Axum - Extracting Query Param of Vec

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 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.

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



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)
}


Enter fullscreen mode Exit fullscreen mode

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 will give you access to only request parts and 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



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()))
  }
}


Enter fullscreen mode Exit fullscreen mode

Here's how we use it



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)
}


Enter fullscreen mode Exit fullscreen mode

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

FromRequest

The is very similar to above, the only different is function signature.



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()))
    }
}


Enter fullscreen mode Exit fullscreen mode


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)
}


Enter fullscreen mode Exit fullscreen mode

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

Top comments (2)

Collapse
 
alexmikhalev profile image
AlexMikhalev

axum query implements deref, given struct:

#[derive(Debug, Serialize, Deserialize, Clone, Default)]
pub struct SearchQuery {
    pub search_term: String,
    pub skip: Option<usize>,
    pub limit: Option<usize>,
    pub role: Option<String>,
}
Enter fullscreen mode Exit fullscreen mode

let search_query = search_query.deref().clone();
will return struct, I think the same applies to Vec. No need for axum_extra or custom extractor.

Collapse
 
baptlm profile image
Baptiste Le Morlec • Edited

The Query implementation of axum uses serde_urlencoded and it would not support a key in your struct that would have the type Vec<String> for example.
There is a test in the repository that show it will return an error for this use case : github.com/nox/serde_urlencoded/bl...