tslink 0.4.2

Creates typescript definitions based on rust code
Documentation
use serde::Deserialize;
use std::{collections::HashMap, fmt, io::Error};
use toml::Table;

const TSLINK_CARGO_KEY: &str = "tslink";
const PACKAGE_CARGO_KEY: &str = "package";
const METADATA_CARGO_KEY: &str = "metadata";

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub enum SnakeCaseNaming {
    Methods,
    Fields,
}

#[derive(Deserialize, Debug, Clone, Default)]
pub enum EnumRepresentation {
    DiscriminatedUnion,
    #[default]
    Flat,
    Union,
}

impl TryFrom<&str> for EnumRepresentation {
    type Error = Error;
    fn try_from(value: &str) -> Result<EnumRepresentation, Self::Error> {
        if value == EnumRepresentation::DiscriminatedUnion.to_string() {
            Ok(EnumRepresentation::DiscriminatedUnion)
        } else if value == EnumRepresentation::Flat.to_string() {
            Ok(EnumRepresentation::Flat)
        } else if value == EnumRepresentation::Union.to_string() {
            Ok(EnumRepresentation::Union)
        } else {
            Err(Error::other(format!(
                "Unknown option for enum_representation option: \"{value}\""
            )))
        }
    }
}

impl fmt::Display for EnumRepresentation {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::DiscriminatedUnion => "discriminated",
                Self::Flat => "flat",
                Self::Union => "union",
            }
        )
    }
}

impl TryFrom<&str> for SnakeCaseNaming {
    type Error = Error;
    fn try_from(value: &str) -> Result<SnakeCaseNaming, Self::Error> {
        if value == SnakeCaseNaming::Methods.to_string() {
            Ok(SnakeCaseNaming::Methods)
        } else if value == SnakeCaseNaming::Fields.to_string() {
            Ok(SnakeCaseNaming::Fields)
        } else {
            Err(Error::other(format!(
                "Unknown option for snake_case_naming option: \"{value}\""
            )))
        }
    }
}

impl fmt::Display for SnakeCaseNaming {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Methods => "methods",
                Self::Fields => "fields",
            }
        )
    }
}

#[derive(Deserialize, Debug, Default)]
pub struct Cfg {
    pub node: Option<String>,
    pub snake_case_naming: Option<String>,
    pub exception_suppression: bool,
    pub int_over_32_as_big_int: bool,
    pub type_map: HashMap<String, String>,
    pub enum_representation: EnumRepresentation,
}

impl Cfg {
    pub fn new(cargo: &Table) -> Self {
        if cargo.contains_key(TSLINK_CARGO_KEY) {
            // Support 0.4.1 > versions
            cargo.get(TSLINK_CARGO_KEY)
        } else {
            // Default settings place after 0.4.1
            cargo
                .get(PACKAGE_CARGO_KEY)
                .and_then(|p| p.get(METADATA_CARGO_KEY))
                .and_then(|m| m.get(TSLINK_CARGO_KEY))
        }
        .and_then(|v| v.as_table())
        .map(|settings| Cfg {
            node: settings
                .get("node")
                .and_then(|v| v.as_str().map(|v| v.to_string())),
            snake_case_naming: settings
                .get("snake_case_naming")
                .and_then(|v| v.as_str().map(|v| v.to_string())),
            exception_suppression: settings
                .get("exception_suppression")
                .and_then(|v| v.as_bool())
                .unwrap_or_default(),
            int_over_32_as_big_int: settings
                .get("int_over_32_as_big_int")
                .and_then(|v| v.as_bool())
                .unwrap_or_default(),
            type_map: settings
                .get("type_map")
                .and_then(|v| v.as_table())
                .map(|m| {
                    m.iter()
                        .filter_map(|(key, value)| {
                            value.as_str().map(|v| (key.clone(), v.to_string()))
                        })
                        .collect()
                })
                .unwrap_or_default(),
            enum_representation: settings
                .get("enum_representation")
                .and_then(|v| v.as_str().map(|s| s.try_into().unwrap_or_default()))
                .unwrap_or_default(),
        })
        .unwrap_or_default()
    }
}