最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

rust - Parsing types from third-party crates with serde - Stack Overflow

programmeradmin3浏览0评论

I encountered a problem while creating a config parser for a CLI utility. Due to Rust's trait implementation rules, I can only implement a trait for a type in the crate where the trait is defined or in the crate where the type is defined. However, I want to parse some termcolor::Colors from a config file. The code compiles without any problems when I parse the colors to a String:

use serde::Deserialize;
use std::collections::HashMap;

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Config {
    colors: HashMap<String, String>,
}

const TOML_DATA: &str = r#"
[colors]
color1 = "Cyan"
color2 = "Green"
not_valid_color = "Qwerty"
"#;

fn main() {
    let cfg = toml::from_str::<Config>(TOML_DATA);
    println!("{:#?}", cfg);
}

I found a working solution for parsing a single field:

use serde::Deserialize;
use termcolor::Color;

#[derive(Debug, Deserialize)]
#[serde(remote = "Color")]
enum ColorWrapper {
    Black,
    Blue,
    Green,
    Red,
    Cyan,
    Magenta,
    Yellow,
    White,
    Ansi256(u8),
    Rgb(u8, u8, u8),
    __Nonexhaustive,
}

#[derive(Debug, Deserialize)]
#[allow(dead_code)]
struct Config {
    #[serde(with = "ColorWrapper")]
    color: Color,
}

const TOML_DATA: &str = r#"
color = "Cyan"
"#;

fn main() {
    let cfg = toml::from_str::<Config>(TOML_DATA);
    println!("{:#?}", cfg);
}

But this approach has some constraints, such as requiring the original enum or struct to be fully public, or preventing me from parsing all colors into a hash table like this:

use serde::Deserialize;
use std::collections::HashMap;
use termcolor::Color;

#[derive(Debug, Deserialize)]
struct Config {
    #[serde(with = "ColorWrapper")]
    colors: HashMap<String, Color>,
}

I can also imagine the following solution, but it seems too verbose:

use serde::Deserialize;
use std::collections::HashMap;
use std::str::FromStr;
use termcolor::Color;
use thiserror::Error;

#[derive(Debug, Deserialize)]
struct RawConfig {
    colors: HashMap<String, String>,
}

#[derive(Debug)]
#[allow(dead_code)]
struct Config {
    colors: HashMap<String, Color>,
}

#[derive(Debug, Error)]
#[error("cannot parse config")]
struct ConfigParseError;

impl TryInto<Config> for RawConfig {
    type Error = ConfigParseError;

    fn try_into(self) -> Result<Config, Self::Error> {
        let colors = self
            .colors
            .iter()
            .try_fold(
                HashMap::new(),
                |mut acc, (key, value)| match Color::from_str(value) {
                    Ok(val) => {
                        acc.insert(String::clone(key), val);
                        Ok(acc)
                    }
                    Err(_) => Err(ConfigParseError),
                },
            )?;
        Ok(Config { colors })
    }
}

const TOML_DATA: &str = r#"
[colors]
color1 = "Cyan"
color2 = "Green"
#not_valid_color = "Qwerty"
"#;

fn main() {
    let raw_cfg: RawConfig = toml::from_str(TOML_DATA).expect("cannot parse toml data");
    println!("{:#?}", <RawConfig as TryInto<Config>>::try_into(raw_cfg));
}

How can I parse types which don't provide a Deserialize implementation from third-party crates with serde?

发布评论

评论列表(0)

  1. 暂无评论