specta-serde 0.0.12

Serde support for Specta
Documentation
#![allow(missing_docs)]

use crate::{Error, inflection::RenameRule};
use specta::datatype::{Attributes, DataType};

const CONTAINER_RENAME_SERIALIZE: &str = "serde:container:rename_serialize";
const CONTAINER_RENAME_DESERIALIZE: &str = "serde:container:rename_deserialize";
const CONTAINER_RENAME_ALL_SERIALIZE: &str = "serde:container:rename_all_serialize";
const CONTAINER_RENAME_ALL_DESERIALIZE: &str = "serde:container:rename_all_deserialize";
const CONTAINER_RENAME_ALL_FIELDS_SERIALIZE: &str = "serde:container:rename_all_fields_serialize";
const CONTAINER_RENAME_ALL_FIELDS_DESERIALIZE: &str =
    "serde:container:rename_all_fields_deserialize";
const CONTAINER_TAG: &str = "serde:container:tag";
const CONTAINER_CONTENT: &str = "serde:container:content";
const CONTAINER_UNTAGGED: &str = "serde:container:untagged";
const CONTAINER_DEFAULT: &str = "serde:container:default";
const CONTAINER_TRANSPARENT: &str = "serde:container:transparent";
const CONTAINER_FROM_TYPE_SRC: &str = "serde:container:from_type_src";
const CONTAINER_FROM_RESOLVED: &str = "serde:container:from_resolved";
const CONTAINER_TRY_FROM_TYPE_SRC: &str = "serde:container:try_from_type_src";
const CONTAINER_TRY_FROM_RESOLVED: &str = "serde:container:try_from_resolved";
const CONTAINER_INTO_TYPE_SRC: &str = "serde:container:into_type_src";
const CONTAINER_INTO_RESOLVED: &str = "serde:container:into_resolved";
const CONTAINER_VARIANT_IDENTIFIER: &str = "serde:container:variant_identifier";
const CONTAINER_FIELD_IDENTIFIER: &str = "serde:container:field_identifier";

const VARIANT_RENAME_SERIALIZE: &str = "serde:variant:rename_serialize";
const VARIANT_RENAME_DESERIALIZE: &str = "serde:variant:rename_deserialize";
const VARIANT_ALIASES: &str = "serde:variant:aliases";
const VARIANT_RENAME_ALL_SERIALIZE: &str = "serde:variant:rename_all_serialize";
const VARIANT_RENAME_ALL_DESERIALIZE: &str = "serde:variant:rename_all_deserialize";
const VARIANT_SKIP_SERIALIZING: &str = "serde:variant:skip_serializing";
const VARIANT_SKIP_DESERIALIZING: &str = "serde:variant:skip_deserializing";
const VARIANT_HAS_SERIALIZE_WITH: &str = "serde:variant:has_serialize_with";
const VARIANT_HAS_DESERIALIZE_WITH: &str = "serde:variant:has_deserialize_with";
const VARIANT_HAS_WITH: &str = "serde:variant:has_with";
const VARIANT_OTHER: &str = "serde:variant:other";
const VARIANT_UNTAGGED: &str = "serde:variant:untagged";

const FIELD_RENAME_SERIALIZE: &str = "serde:field:rename_serialize";
const FIELD_RENAME_DESERIALIZE: &str = "serde:field:rename_deserialize";
pub(crate) const FIELD_ALIASES: &str = "serde:field:aliases";
const FIELD_DEFAULT: &str = "serde:field:default";
const FIELD_FLATTEN: &str = "serde:field:flatten";
const FIELD_SKIP_SERIALIZING: &str = "serde:field:skip_serializing";
const FIELD_SKIP_DESERIALIZING: &str = "serde:field:skip_deserializing";
pub(crate) const FIELD_SKIP_SERIALIZING_IF: &str = "serde:field:skip_serializing_if";
const FIELD_HAS_SERIALIZE_WITH: &str = "serde:field:has_serialize_with";
const FIELD_HAS_DESERIALIZE_WITH: &str = "serde:field:has_deserialize_with";
const FIELD_HAS_WITH: &str = "serde:field:has_with";

const CONTAINER_ATTR_KEYS: &[&str] = &[
    CONTAINER_RENAME_SERIALIZE,
    CONTAINER_RENAME_DESERIALIZE,
    CONTAINER_RENAME_ALL_SERIALIZE,
    CONTAINER_RENAME_ALL_DESERIALIZE,
    CONTAINER_RENAME_ALL_FIELDS_SERIALIZE,
    CONTAINER_RENAME_ALL_FIELDS_DESERIALIZE,
    CONTAINER_TAG,
    CONTAINER_CONTENT,
    CONTAINER_UNTAGGED,
    CONTAINER_DEFAULT,
    CONTAINER_TRANSPARENT,
    CONTAINER_FROM_TYPE_SRC,
    CONTAINER_FROM_RESOLVED,
    CONTAINER_TRY_FROM_TYPE_SRC,
    CONTAINER_TRY_FROM_RESOLVED,
    CONTAINER_INTO_TYPE_SRC,
    CONTAINER_INTO_RESOLVED,
    CONTAINER_VARIANT_IDENTIFIER,
    CONTAINER_FIELD_IDENTIFIER,
];

const VARIANT_ATTR_KEYS: &[&str] = &[
    VARIANT_RENAME_SERIALIZE,
    VARIANT_RENAME_DESERIALIZE,
    VARIANT_ALIASES,
    VARIANT_RENAME_ALL_SERIALIZE,
    VARIANT_RENAME_ALL_DESERIALIZE,
    VARIANT_SKIP_SERIALIZING,
    VARIANT_SKIP_DESERIALIZING,
    VARIANT_HAS_SERIALIZE_WITH,
    VARIANT_HAS_DESERIALIZE_WITH,
    VARIANT_HAS_WITH,
    VARIANT_OTHER,
    VARIANT_UNTAGGED,
];

const FIELD_ATTR_KEYS: &[&str] = &[
    FIELD_RENAME_SERIALIZE,
    FIELD_RENAME_DESERIALIZE,
    FIELD_ALIASES,
    FIELD_DEFAULT,
    FIELD_FLATTEN,
    FIELD_SKIP_SERIALIZING,
    FIELD_SKIP_DESERIALIZING,
    FIELD_SKIP_SERIALIZING_IF,
    FIELD_HAS_SERIALIZE_WITH,
    FIELD_HAS_DESERIALIZE_WITH,
    FIELD_HAS_WITH,
];

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ConversionType {
    pub type_src: String,
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct SerdeContainerAttrs {
    pub rename_serialize: Option<String>,
    pub rename_deserialize: Option<String>,
    pub rename_all_serialize: Option<RenameRule>,
    pub rename_all_deserialize: Option<RenameRule>,
    pub rename_all_fields_serialize: Option<RenameRule>,
    pub rename_all_fields_deserialize: Option<RenameRule>,
    pub tag: Option<String>,
    pub content: Option<String>,
    pub untagged: bool,
    pub default: bool,
    pub transparent: bool,
    pub from: Option<ConversionType>,
    pub try_from: Option<ConversionType>,
    pub into: Option<ConversionType>,
    pub resolved_from: Option<DataType>,
    pub resolved_try_from: Option<DataType>,
    pub resolved_into: Option<DataType>,
    pub variant_identifier: bool,
    pub field_identifier: bool,
}

impl SerdeContainerAttrs {
    pub fn from_attributes(attributes: &Attributes) -> Result<Option<Self>, Error> {
        has_any_attr(attributes, CONTAINER_ATTR_KEYS)
            .then(|| {
                Ok(Self {
                    rename_serialize: get_string(attributes, CONTAINER_RENAME_SERIALIZE),
                    rename_deserialize: get_string(attributes, CONTAINER_RENAME_DESERIALIZE),
                    rename_all_serialize: get_rename_rule(
                        attributes,
                        CONTAINER_RENAME_ALL_SERIALIZE,
                    )?,
                    rename_all_deserialize: get_rename_rule(
                        attributes,
                        CONTAINER_RENAME_ALL_DESERIALIZE,
                    )?,
                    rename_all_fields_serialize: get_rename_rule(
                        attributes,
                        CONTAINER_RENAME_ALL_FIELDS_SERIALIZE,
                    )?,
                    rename_all_fields_deserialize: get_rename_rule(
                        attributes,
                        CONTAINER_RENAME_ALL_FIELDS_DESERIALIZE,
                    )?,
                    tag: get_string(attributes, CONTAINER_TAG),
                    content: get_string(attributes, CONTAINER_CONTENT),
                    untagged: has_attr(attributes, CONTAINER_UNTAGGED),
                    default: has_attr(attributes, CONTAINER_DEFAULT),
                    transparent: has_attr(attributes, CONTAINER_TRANSPARENT),
                    from: get_string(attributes, CONTAINER_FROM_TYPE_SRC)
                        .map(|type_src| ConversionType { type_src }),
                    try_from: get_string(attributes, CONTAINER_TRY_FROM_TYPE_SRC)
                        .map(|type_src| ConversionType { type_src }),
                    into: get_string(attributes, CONTAINER_INTO_TYPE_SRC)
                        .map(|type_src| ConversionType { type_src }),
                    resolved_from: get_datatype(attributes, CONTAINER_FROM_RESOLVED),
                    resolved_try_from: get_datatype(attributes, CONTAINER_TRY_FROM_RESOLVED),
                    resolved_into: get_datatype(attributes, CONTAINER_INTO_RESOLVED),
                    variant_identifier: has_attr(attributes, CONTAINER_VARIANT_IDENTIFIER),
                    field_identifier: has_attr(attributes, CONTAINER_FIELD_IDENTIFIER),
                })
            })
            .transpose()
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct SerdeVariantAttrs {
    pub rename_serialize: Option<String>,
    pub rename_deserialize: Option<String>,
    pub aliases: Vec<String>,
    pub rename_all_serialize: Option<RenameRule>,
    pub rename_all_deserialize: Option<RenameRule>,
    pub skip_serializing: bool,
    pub skip_deserializing: bool,
    pub has_serialize_with: bool,
    pub has_deserialize_with: bool,
    pub has_with: bool,
    pub other: bool,
    pub untagged: bool,
}

impl SerdeVariantAttrs {
    pub fn from_attributes(attributes: &Attributes) -> Result<Option<Self>, Error> {
        has_any_attr(attributes, VARIANT_ATTR_KEYS)
            .then(|| {
                Ok(Self {
                    rename_serialize: get_string(attributes, VARIANT_RENAME_SERIALIZE),
                    rename_deserialize: get_string(attributes, VARIANT_RENAME_DESERIALIZE),
                    aliases: get_strings(attributes, VARIANT_ALIASES),
                    rename_all_serialize: get_rename_rule(
                        attributes,
                        VARIANT_RENAME_ALL_SERIALIZE,
                    )?,
                    rename_all_deserialize: get_rename_rule(
                        attributes,
                        VARIANT_RENAME_ALL_DESERIALIZE,
                    )?,
                    skip_serializing: has_attr(attributes, VARIANT_SKIP_SERIALIZING),
                    skip_deserializing: has_attr(attributes, VARIANT_SKIP_DESERIALIZING),
                    has_serialize_with: has_attr(attributes, VARIANT_HAS_SERIALIZE_WITH),
                    has_deserialize_with: has_attr(attributes, VARIANT_HAS_DESERIALIZE_WITH),
                    has_with: has_attr(attributes, VARIANT_HAS_WITH),
                    other: has_attr(attributes, VARIANT_OTHER),
                    untagged: has_attr(attributes, VARIANT_UNTAGGED),
                })
            })
            .transpose()
    }
}

#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)]
pub struct SerdeFieldAttrs {
    pub rename_serialize: Option<String>,
    pub rename_deserialize: Option<String>,
    pub aliases: Vec<String>,
    pub default: bool,
    pub flatten: bool,
    pub skip_serializing: bool,
    pub skip_deserializing: bool,
    pub skip_serializing_if: Option<String>,
    pub has_serialize_with: bool,
    pub has_deserialize_with: bool,
    pub has_with: bool,
}

impl SerdeFieldAttrs {
    pub fn from_attributes(attributes: &Attributes) -> Result<Option<Self>, Error> {
        has_any_attr(attributes, FIELD_ATTR_KEYS)
            .then(|| {
                Ok(Self {
                    rename_serialize: get_string(attributes, FIELD_RENAME_SERIALIZE),
                    rename_deserialize: get_string(attributes, FIELD_RENAME_DESERIALIZE),
                    aliases: get_strings(attributes, FIELD_ALIASES),
                    default: has_attr(attributes, FIELD_DEFAULT),
                    flatten: has_attr(attributes, FIELD_FLATTEN),
                    skip_serializing: has_attr(attributes, FIELD_SKIP_SERIALIZING),
                    skip_deserializing: has_attr(attributes, FIELD_SKIP_DESERIALIZING),
                    skip_serializing_if: get_string(attributes, FIELD_SKIP_SERIALIZING_IF),
                    has_serialize_with: has_attr(attributes, FIELD_HAS_SERIALIZE_WITH),
                    has_deserialize_with: has_attr(attributes, FIELD_HAS_DESERIALIZE_WITH),
                    has_with: has_attr(attributes, FIELD_HAS_WITH),
                })
            })
            .transpose()
    }
}

fn get_string(attributes: &Attributes, key: &str) -> Option<String> {
    attributes.get_named_as::<String>(key).cloned()
}

fn get_strings(attributes: &Attributes, key: &str) -> Vec<String> {
    attributes
        .get_named_as::<Vec<String>>(key)
        .cloned()
        .unwrap_or_default()
}

fn has_attr(attributes: &Attributes, key: &str) -> bool {
    attributes.contains_key(key)
}

fn get_datatype(attributes: &Attributes, key: &str) -> Option<DataType> {
    attributes.get_named_as::<DataType>(key).cloned()
}

fn get_rename_rule(attributes: &Attributes, key: &str) -> Result<Option<RenameRule>, Error> {
    match get_string(attributes, key) {
        Some(value) => RenameRule::from_str(&value)
            .map(Some)
            .map_err(|_| Error::invalid_rename_rule(key.to_string(), value.clone())),
        None => Ok(None),
    }
}

fn has_any_attr(attributes: &Attributes, keys: &[&str]) -> bool {
    keys.iter().copied().any(|key| attributes.contains_key(key))
}