7 votes

La désérialisation d'un champ struct optionnel avec un type générique conduit à des bizarreries sémantiques

J'essaie de désérialiser JSON dans une structure qui contient un champ optionnel. authorization . Le JSON peut ou non inclure ce champ. Si c'est le cas, je procède à une désérialisation personnalisée dans un fichier hyper::header::Authorization<hyper::header::Scheme> . Parce que Authorization nécessite un type générique pour les Scheme Je suis tenu (comme je l'ai écrit) d'inclure le type générique dans ma structure.

Tous les tests sont réussis, mais le dernier ( de_json_none , celui pour JSON sans le champ d'autorisation) est sémantiquement bizarre parce que je dois cibler une variable avec une valeur définie. Scheme (soit Bearer comme indiqué ou Basic ), qui n'ont aucun sens pour ces données, bien qu'elles soient parfaitement valables du point de vue de Rust.

La raison en est claire, mais c'est quelque chose que je ne veux pas et que je ne sais pas comment résoudre.

Je veux écrire un gestionnaire Rocket qui ne correspond qu'aux données qui contiennent le champ d'autorisation de type Authorization<Bearer> en fixant le type de données à Headers<Bearer> . Pour l'instant, cela correspondrait également à des données qui n'ont pas de champ du tout. Je n'ai pas non plus de moyen clair d'appeler les données avec le champ manquant spécifiquement par type.

Je suis à la recherche de suggestions sur la manière de remanier ce code pour refléter le fait que Headers a en réalité trois incarnations distinctes, qui s'excluent mutuellement ( Basic , Bearer y None ). Peut-être devrais-je chercher à faire quelque chose avec un enum ?

extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use hyper::header::{Authorization, Header, Raw, Scheme};
use serde::{Deserialize, Deserializer};

#[derive(Debug, Deserialize, PartialEq)]
struct Headers<S>
where
    S: Scheme + 'static,
{
    #[serde(deserialize_with = "auth_header", default = "no_auth")]
    authorization: Option<Authorization<S>>,
    #[serde(rename = ":path")]
    path: String,
}

fn auth_header<'de, D, S>(deserializer: D) -> Result<Option<Authorization<S>>, D::Error>
where
    D: Deserializer<'de>,
    S: Scheme + 'static,
{
    let s = String::deserialize(deserializer)?;
    let auth = Authorization::parse_header(&Raw::from(s.into_bytes()));
    auth.map(|a| Some(a)).map_err(serde::de::Error::custom)
}

fn no_auth<S>() -> Option<Authorization<S>>
where
    S: Scheme + 'static,
{
    None
}

#[cfg(test)]
mod test {
    use hyper::header::{Basic, Bearer};
    use serde_json;
    use super::*;

    #[test]
    fn de_json_basic() {
        let data = r#"{
                        "authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: Some(Authorization(Basic {
                username: "Aladdin".to_owned(),
                password: Some("open sesame".to_owned()),
            })),
            path: "/service/".to_owned(),
        };

        let h: Headers<Basic> = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }

    #[test]
    fn de_json_bearer() {
        let data = r#"{
                        "authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: Some(Authorization(
                Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
            )),
            path: "/service/".to_owned(),
        };

        let h: Headers<Bearer> = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }

    #[test]
    fn de_json_none() {
        let data = r#"{
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: None,
            path: "/service/".to_owned(),
        };

        let h: Headers<Bearer> = serde_json::from_str(data).unwrap();
        // this also works, though neither should ideally
        // let h: Headers<Basic> = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }
}

2voto

Shepmaster Points 1732

Il n'y a pas de concept de None sans qu'il n'y ait de Some type . Le compilateur doit savoir combien d'espace il doit allouer à la valeur de soit cas :

struct ReallyBig([u8; 1024]);
struct ReallySmall(u8);

fn main() {
    let mut choice = None; // How much space to allocate?
}

Dans votre code, la taille de Authorization peut dépendre de la valeur choisie pour S . Depuis le Headers contient un Option<Authorization<S>> , la taille de Headers également peut dépendre du choix de S .

Même si vous n'obtenez aucune valeur, vous devez choisir d'analyser un type spécifique. Peut-être le changerez-vous plus tard manuellement d'un type None à un Some en construisant les valeurs appropriées - si l'espace alloué n'était pas suffisant, il y aurait des problèmes !

Pour cette raison, je ne vois pas comment votre solution pourrait fonctionner. Les types sont statiques - vous devez savoir au moment de la compilation si le décodage de ce JSON va se traduire par Authorization o Bearer et ce n'est tout simplement pas possible.

Normalement, je vous conseille d'utiliser la répartition dynamique avec une fonction Box<Scheme> . Cela ne fonctionnera pas ici car Scheme n'est pas sûr pour l'objet.

Dans ce cas, je vous suggère d'implémenter votre propre enum en enveloppant soit Basic o Box et mettre en œuvre Scheme pour cela. Cela ne fonctionne pas facilement parce que Scheme::scheme doit renvoyer un unique mais vous soutenez en fait deux mots-clés !

L'étape suivante consiste à mettre en œuvre notre propre Header :

extern crate hyper;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;

use hyper::header::{Authorization, Header, Raw, Basic, Bearer};
use serde::{Deserialize, Deserializer};
use std::fmt;

#[derive(Debug, Clone, PartialEq)]
enum MyAuthorization {
    Basic(Authorization<Basic>),
    Bearer(Authorization<Bearer>),
}

impl Header for MyAuthorization {
    fn header_name() -> &'static str {
        // Should always be the same header name, right?
        Authorization::<Basic>::header_name()
    }

    fn parse_header(raw: &Raw) -> hyper::error::Result<Self> {
        Authorization::<Basic>::parse_header(raw)
            .map(MyAuthorization::Basic)
            .or_else(|_| {
                Authorization::<Bearer>::parse_header(raw).map(MyAuthorization::Bearer)
            })
    }

    fn fmt_header(&self, f: &mut hyper::header::Formatter) -> fmt::Result {
        match *self {
            MyAuthorization::Basic(ref a) => a.fmt_header(f),
            MyAuthorization::Bearer(ref a) => a.fmt_header(f),
        }
    }
}

#[derive(Debug, Deserialize, PartialEq)]
struct Headers {
    #[serde(deserialize_with = "auth_header", default)]
    authorization: Option<MyAuthorization>,
    #[serde(rename = ":path")]
    path: String,
}

fn auth_header<'de, D>(deserializer: D) -> Result<Option<MyAuthorization>, D::Error>
where
    D: Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    let auth = MyAuthorization::parse_header(&Raw::from(s.into_bytes()));
    auth.map(Some).map_err(serde::de::Error::custom)
}

#[cfg(test)]
mod test {
    use hyper::header::{Basic, Bearer};
    use serde_json;
    use super::*;

    #[test]
    fn de_json_basic() {
        let data = r#"{
                        "authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: Some(MyAuthorization::Basic(Authorization(Basic {
                username: "Aladdin".to_owned(),
                password: Some("open sesame".to_owned()),
            }))),
            path: "/service/".to_owned(),
        };

        let h: Headers = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }

    #[test]
    fn de_json_bearer() {
        let data = r#"{
                        "authorization": "Bearer fpKL54jvWmEGVoRdCNjG",
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: Some(MyAuthorization::Bearer(Authorization(
                Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() },
            ))),
            path: "/service/".to_owned(),
        };

        let h: Headers = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }

    #[test]
    fn de_json_none() {
        let data = r#"{
                        ":path": "/service/",
                        ":method": "GET"
                      }"#;

        let message = Headers {
            authorization: None,
            path: "/service/".to_owned(),
        };

        let h: Headers = serde_json::from_str(data).unwrap();

        assert_eq!(message, h);
    }
}

Vous pouvez vérifier auprès des responsables d'Hyper si c'est la façon attendue de faire une telle chose.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X