5

In the actix-web documentation is only an example of how to receive uniquely named query params.

But how can I receive multiple query params of the same name? For example:

http://localhost:8088/test?id=1&id=2&id=3

How do I have to change following code so it accepts multiple ids and how can I read them?

use actix_web::web;
use serde::Deserialize;

#[derive(Deserialize)]
struct Info {
    id: String,
}

#[get("/test")]
async fn index(info: web::Query<Info>) -> impl Responder  {
    println!("Id: {}!", info.id);
    
    "ok"
}
zingi
  • 1,141
  • 13
  • 23
  • No idea if it'd work but have you tried declaring `id` as a `Vec`? – Masklinn Sep 11 '20 at 09:46
  • @Masklinn Yeah I tried. Then I get the error: `Query deserialize error: invalid type: string "1", expected a sequence` – zingi Sep 11 '20 at 09:53
  • In others languages `http://localhost:8088/test?id=1&id=2&id=3` isn't valid, you need to do `http://localhost:8088/test?id[]=1&id[]=2&id[]=3`. If it's help. – Obzi Sep 11 '20 at 10:17
  • Ah, looks like serde_qs has a much more prescriptive PHP-style format where you need to specify that qs keys are for an array. You might need to "handroll" it by using [`HttpRequest::query_string`](https://actix.rs/actix-web/actix_web/struct.HttpRequest.html#method.query_string) and something which can return the raw "pairs" of name and value e.g. url or qstring. Either that or if you control the client then use the PHP-style format (just call your field / param `id[]` instead of `id`) – Masklinn Sep 11 '20 at 10:29
  • Thanks for your input. But when I try `http://localhost:8088/test?id[]=1&id[]=2&id[]=3`, I get the error: **Query deserialize error: missing field `id`**. I also found a related issue on [github](https://github.com/actix/actix-web/issues/1301) and it seems, there is no trivial solution yet. I guess I'll just use one string as qp with a custom delimiter and split the ids later. – zingi Sep 11 '20 at 10:53

1 Answers1

3

Having a look at this question, it seems like there is no definitive standard for what you want. I dont know if actix has such an extractor. I would work on my Deserialize impl.

use std::fmt;
use serde::de::{ Deserialize, Deserializer, Visitor, MapAccess};

impl<'de> Deserialize<'de> for Info {
    fn deserialize<D>(deserializer: D) -> Result<Info, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct FieldVisitor;

        impl<'de> Visitor<'de> for FieldVisitor {
            type Value = Info;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("`id`")
            }

            fn visit_map<V>(self, mut map: V) -> Result<Info, V::Error>
            where
                V: MapAccess<'de>
            {
                let mut ids:  Vec<String> = Vec::default();
                while let Some(key) = map.next_key()? {
                    match key {
                        "id" => {
                            ids.push(map.next_value::<String>()?)
                        }
                        _ => unreachable!()
                    }
                }
                Ok(Info {
                    id: ids
                })
            }
        }
        deserializer.deserialize_identifier(FieldVisitor)
    }
}

#[derive(Debug)]
struct Info {
    id: Vec<String>,
}
Njuguna Mureithi
  • 3,506
  • 1
  • 21
  • 41