Десериализация ржавчины и серде с использованием дженериков - PullRequest
0 голосов
/ 24 февраля 2019

Я пытаюсь использовать дженерики для десериализации структур из файла для использования с созданным Swagger API.Итак, я взломал это, что почти работает, но я не могу распаковать внешний объект Struct из указателя «Owned», как вы можете видеть в тестах.

Возможно, это неправильная стратегия, но проблема в том, что у меня есть различные файлы yaml, которые я хочу прочитать и десериализовать подсказку правильной структуры для десериализации как.Я не хочу реализовывать функцию «readfile» для каждой структуры, так как их много.Поэтому я пытаюсь заставить эту универсальную библиотеку работать, которая должна десериализоваться в правильную структуру и использовать с API Swagger.

Это очень близко к работе, но я не могу развернуть Outer<ExternalStructA> в ExternalStructA.

Owned(ExternalStructA { x: 1, y: 2 })
Owned(ExternalStructB { a: 1, b: 2 })

lib.rs:

#[cfg(test)]
mod tests {
    use crate::generics_yaml_deserializer::Outer;
    use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

    #[derive(Debug, Serialize, Deserialize)]
    pub struct ExternalStructA {
        x: u32,
        y: u32,
    }

    #[derive(Debug, Serialize, Deserialize)]
    pub struct ExternalStructB {
        a: u64,
        b: u64,
    }

    #[test]
    fn deserialize() {
        let a = r#"---
ptr:
  x: 1
  y: 2
     "#;

        let b = r#"---
ptr:
  a: 1
  b: 2
        "#;

        let resulta: Outer<ExternalStructA> = serde_yaml::from_str(a).unwrap();
        assert_eq!(1, resulta.ptr.x); // I can't seem to get into ptr ExternalStructA
        let resultb: Outer<ExternalStructB> = serde_yaml::from_str(b).unwrap();
        assert_eq!(1, resultb.ptr.a); // I can't seem to get into ptr ExternalStructB 
    }
}

mod generics_yaml_deserializer {
    use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
    use std::error::Error;

    // empty holding struct which owns a owned ptr
    #[derive(Deserialize, Debug)]
    pub struct Outer<'a, T: 'a + ?Sized> {
        #[serde(bound(deserialize = "Ptr<'a, T>: Deserialize<'de>"))]
        pub ptr: Ptr<'a, T>,
    }

    #[derive(Debug)]
    pub enum Ptr<'a, T: 'a + ?Sized> {
        Ref(&'a T),
        Owned(Box<T>),
    }

    impl<'de, 'a, T: 'a + ?Sized> Deserialize<'de> for Ptr<'a, T>
    where
        Box<T>: Deserialize<'de>,
    {
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
            where
                D: Deserializer<'de>,
        {
            Deserialize::deserialize(deserializer).map(Ptr::Owned)
        }
    }
}

Грузовые зависимости:

serde = { version = "1.0", features = ["derive"] }
serde_derive = "1.0"
serde_yaml = "0.7.5"
serde_json = "1.0"

Обновление:

У меня был частичный успех при получении Structс:

let resulta: Outer<ExternalStructA> = serde_yaml::from_str(a).unwrap();
    match resulta.ptr {
        Ptr::Owned(e) => {assert_eq!(1, e.x);},
        Ptr::Ref(e) => {println!("error")},
        Ptr::Owned(_) => {println!("error")}
    };
}

Но когда я пытаюсь реализовать это как функцию с использованием универсальной типизации, я получаю много ошибок, главная из которых:

the trait `for<'de> tests::_IMPL_DESERIALIZE_FOR_ExternalStructA::_serde::Deserialize<'de>` is not implemented for `T`

Добавлен нерабочий кодмод generics_yaml_deserializer

fn readfile<T>(filename: String) -> Result<Box<T>, Box<std::error::Error>> {
    let f = std::fs::File::open(filename)?;
    let config_data: Outer<T> = serde_yaml::from_reader(f)?;
    Ok(Box::new(config_data))
}

fn readconfig<T>(filename: String) -> Result<Box<T>, &'static str> {
    // read the config file
    let config_data = readfile(filename);
    match config_data {
        Ok(e) => {
            Ok(Box::new(e))
        },
        Err(_) => {
            Err("nadda")
        }
    }
}

Ответы [ 2 ]

0 голосов
/ 24 февраля 2019

просто объявите, что T равно DeserializeOwned:

fn readfile<T: de::DeserializeOwned>(filename: String) -> Result<Box<T>, Box<std::error::Error>> {
    let f = std::fs::File::open(filename)?;
    let config_data: Outer<T> = serde_yaml::from_reader(f)?;
    match config_data.ptr {
        Ptr::Owned(data) => Ok(data),
        _ => unimplemented!(),
    }
}

то же самое с readconfig

0 голосов
/ 24 февраля 2019

При использовании параметра типа, например T, здесь:

fn readfile<T>(filename: String) -> Result<Box<T>, Box<std::error::Error>>;

Тип бетона T определяется вызывающим абонентом.Компилятор не просто смотрит на все доступные типы и делает предположение о том, что имеет смысл.

Прежде всего, вы должны сказать компилятору, что любой T, переданный здесь, действительно будет работать.Это означает, что ограничение T должно быть десериализуемым в течение совместимого времени жизни:

// filename should be &str here
fn readfile<'a, T: ?Sized>(filename: &str) -> Result<Box<Outer<'a, T>>, Box<std::error::Error>>
where
    for<'de> T: Deserialize<'de> + 'a
{
    let f = std::fs::File::open(filename)?;
    let config_data: Outer<T> = serde_yaml::from_reader(f)?;
    Ok(Box::new(config_data))
}

// filename should be &str here
fn readconfig<'a, T: ?Sized>(filename: &str) -> Result<Box<Outer<'a, T>>, &'static str>
where
    for<'de> T: Deserialize<'de> + 'a
{
    // read the config file
    let config_data = readfile(filename);
    match config_data {
        Ok(e) => {
            Ok(Box::new(*e)) // need to deref the Box before reboxing
        },
        Err(_) => {
            Err("nadda")
        }
    }
}

Далее, когда вы вызываете это, вам нужно указать конкретный тип:

let result: Box<Outer<ExternalStructA>> = readconfig("config.yaml")?;

Это не удастся, если вход не может быть проанализирован в Box<Outer<ExternalStructA>>, и в этом случае вы можете попытаться разобрать его в Box<Outer<ExternalStructB>>, возможно, используя Result::or_else.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...