use std::collections::BTreeMap;
use serde::Deserialize;
use strum::IntoEnumIterator;
use crate::{TraitProvision, TypespaceTrait, TypespaceTraitSet};
#[derive(Debug, Deserialize)]
#[non_exhaustive]
pub struct Settings {
#[serde(default)]
pub std: Std,
#[serde(default)]
pub optional_nullable: OptionalNullable,
#[serde(default = "Settings::default_map_type")]
pub map_type: ContainerType,
#[serde(default = "Settings::default_set_type")]
pub set_type: ContainerType,
#[serde(default = "Settings::default_vec_type")]
pub vec_type: ContainerType,
#[serde(default)]
pub required_traits: TypespaceTraitSet,
#[serde(default)]
pub desired_traits: TypespaceTraitSet,
#[serde(default)]
pub extra_derives: Vec<String>,
#[serde(default)]
pub extra_attrs: Vec<String>,
#[serde(default)]
pub struct_builder: bool,
#[doc(hidden)]
#[serde(default)]
pub typify_compat: bool,
}
fn ordered_lookup_traits() -> TypespaceTraitSet {
[
TypespaceTrait::Eq,
TypespaceTrait::PartialEq,
TypespaceTrait::Ord,
TypespaceTrait::PartialOrd,
]
.into_iter()
.collect()
}
fn hash_lookup_traits() -> TypespaceTraitSet {
[
TypespaceTrait::Eq,
TypespaceTrait::PartialEq,
TypespaceTrait::Hash,
]
.into_iter()
.collect()
}
const OWNING_NEVER: [TypespaceTrait; 3] = [
TypespaceTrait::Display,
TypespaceTrait::FromStr,
TypespaceTrait::Copy,
];
const HASHING_NEVER: [TypespaceTrait; 6] = [
TypespaceTrait::Display,
TypespaceTrait::FromStr,
TypespaceTrait::Copy,
TypespaceTrait::Ord,
TypespaceTrait::PartialOrd,
TypespaceTrait::Hash,
];
impl Settings {
fn default_map_type() -> ContainerType {
ContainerType::btree_map()
}
fn default_set_type() -> ContainerType {
ContainerType::vec().with_obligations([ordered_lookup_traits()])
}
fn default_vec_type() -> ContainerType {
ContainerType::vec()
}
pub fn minimal() -> Self {
Self {
std: Std::FullyQualified,
optional_nullable: OptionalNullable::default(),
map_type: Self::default_map_type(),
set_type: Self::default_set_type(),
vec_type: Self::default_vec_type(),
required_traits: TypespaceTraitSet::empty(),
desired_traits: TypespaceTraitSet::empty(),
extra_derives: Default::default(),
extra_attrs: Default::default(),
struct_builder: false,
typify_compat: false,
}
}
pub fn typical() -> Self {
Self {
required_traits: [
TypespaceTrait::Clone,
TypespaceTrait::Debug,
TypespaceTrait::Serialize,
TypespaceTrait::Deserialize,
]
.into_iter()
.collect(),
struct_builder: true,
..Self::minimal()
}
}
pub fn maximal() -> Self {
Self {
required_traits: [
TypespaceTrait::Clone,
TypespaceTrait::Debug,
TypespaceTrait::Serialize,
TypespaceTrait::Deserialize,
TypespaceTrait::JsonSchema,
]
.into_iter()
.collect(),
desired_traits: [
TypespaceTrait::Display,
TypespaceTrait::FromStr,
TypespaceTrait::Eq,
TypespaceTrait::PartialEq,
TypespaceTrait::Ord,
TypespaceTrait::PartialOrd,
TypespaceTrait::Hash,
TypespaceTrait::Default,
TypespaceTrait::Copy,
]
.into_iter()
.collect(),
..Self::typical()
}
}
pub fn with_std(mut self, std: Std) -> Self {
self.std = std;
self
}
pub fn with_optional_nullable(mut self, optional_nullable: OptionalNullable) -> Self {
self.optional_nullable = optional_nullable;
self
}
pub fn with_map_type(mut self, map_type: ContainerType) -> Self {
self.map_type = map_type;
self
}
pub fn with_set_type(mut self, set_type: ContainerType) -> Self {
self.set_type = set_type;
self
}
pub fn with_vec_type(mut self, vec_type: ContainerType) -> Self {
self.vec_type = vec_type;
self
}
pub fn with_required_trait(mut self, trait_impl: TypespaceTrait) -> Self {
self.required_traits.add(trait_impl);
self
}
pub fn with_desired_trait(mut self, trait_impl: TypespaceTrait) -> Self {
self.desired_traits.add(trait_impl);
self
}
pub fn with_derive(mut self, derive: impl Into<String>) -> Self {
self.extra_derives.push(derive.into());
self
}
pub fn with_attr(mut self, attr: impl Into<String>) -> Self {
self.extra_attrs.push(attr.into());
self
}
pub fn with_struct_builder(mut self, struct_builder: bool) -> Self {
self.struct_builder = struct_builder;
self
}
#[doc(hidden)]
pub fn with_typify_compat(mut self, typify_compat: bool) -> Self {
self.typify_compat = typify_compat;
self
}
}
#[derive(Clone, Deserialize)]
#[serde(try_from = "ContainerTypeRepr")]
pub struct ContainerType {
path: syn::Type,
prelude_path: Option<syn::Type>,
obligations: Vec<TypespaceTraitSet>,
provisions: ProvisionTable,
}
impl ContainerType {
pub fn new(path: &str, obligations: impl IntoIterator<Item = TypespaceTraitSet>) -> Self {
Self::opaque(parse_path(path), obligations.into_iter().collect())
}
fn opaque(path: syn::Type, obligations: Vec<TypespaceTraitSet>) -> Self {
Self::from_parts(path, obligations, ProvisionTable::opaque())
}
fn from_parts(
path: syn::Type,
obligations: Vec<TypespaceTraitSet>,
provisions: ProvisionTable,
) -> Self {
Self {
path,
prelude_path: None,
obligations,
provisions,
}
}
fn with_prelude(mut self, prelude_path: syn::Type) -> Self {
self.prelude_path = Some(prelude_path);
self
}
pub fn btree_map() -> Self {
Self::new(
"::std::collections::BTreeMap",
[ordered_lookup_traits(), TypespaceTraitSet::empty()],
)
.with_provisions(&[TypespaceTrait::Default], &OWNING_NEVER)
}
pub fn hash_map() -> Self {
Self::new(
"::std::collections::HashMap",
[hash_lookup_traits(), TypespaceTraitSet::empty()],
)
.with_provisions(&[TypespaceTrait::Default], &HASHING_NEVER)
}
pub fn vec() -> Self {
Self::new("::std::vec::Vec", [TypespaceTraitSet::empty()])
.with_provisions(&[TypespaceTrait::Default], &OWNING_NEVER)
.with_prelude(parse_path("Vec"))
}
pub fn option() -> Self {
Self::new("::std::option::Option", [TypespaceTraitSet::empty()])
.with_provisions(
&[TypespaceTrait::Default],
&[TypespaceTrait::Display, TypespaceTrait::FromStr],
)
.with_prelude(parse_path("Option"))
}
pub fn btree_set() -> Self {
Self::new("::std::collections::BTreeSet", [ordered_lookup_traits()])
.with_provisions(&[TypespaceTrait::Default], &OWNING_NEVER)
}
pub fn hash_set() -> Self {
Self::new("::std::collections::HashSet", [hash_lookup_traits()])
.with_provisions(&[TypespaceTrait::Default], &HASHING_NEVER)
}
pub fn path(&self) -> &syn::Type {
&self.path
}
pub fn obligations(&self) -> &[TypespaceTraitSet] {
&self.obligations
}
pub fn obligation(&self, index: usize) -> &TypespaceTraitSet {
&self.obligations[index]
}
pub fn provision(&self, trait_: TypespaceTrait) -> TraitProvision {
self.provisions.get(trait_)
}
pub fn provisions(&self) -> impl Iterator<Item = (TypespaceTrait, TraitProvision)> + '_ {
self.provisions.iter()
}
pub fn with_path(self, path: &str) -> Self {
self.with_parsed_path(parse_path(path))
}
fn with_parsed_path(mut self, path: syn::Type) -> Self {
self.path = path;
self.prelude_path = None;
self
}
pub fn with_obligations(
mut self,
obligations: impl IntoIterator<Item = TypespaceTraitSet>,
) -> Self {
self.obligations = obligations.into_iter().collect();
self
}
pub fn with_provision(mut self, trait_: TypespaceTrait, provision: TraitProvision) -> Self {
self.provisions.set(trait_, provision);
self
}
pub fn with_provisions(mut self, always: &[TypespaceTrait], never: &[TypespaceTrait]) -> Self {
self.provisions = ProvisionTable::new(never, always);
self
}
fn with_provision_overrides(
mut self,
provides: BTreeMap<TypespaceTrait, TraitProvision>,
) -> Self {
self.provisions = self.provisions.overridden(provides);
self
}
pub(crate) fn rendered_path(&self, std: &Std) -> &syn::Type {
match (std, &self.prelude_path) {
(Std::Unqualified, Some(path)) => path,
_ => &self.path,
}
}
}
fn parse_path(path: &str) -> syn::Type {
syn::parse_str::<syn::Type>(path).expect("valid type path")
}
pub(crate) fn path_text(path: &syn::Type) -> String {
compact_path_text(path).unwrap_or_else(|| {
use quote::ToTokens;
path.to_token_stream().to_string()
})
}
fn compact_path_text(path: &syn::Type) -> Option<String> {
let syn::Type::Path(syn::TypePath {
qself: None, path, ..
}) = path
else {
return None;
};
path.segments
.iter()
.all(|segment| matches!(segment.arguments, syn::PathArguments::None))
.then(|| {
let leading = if path.leading_colon.is_some() {
"::"
} else {
""
};
let segments = path
.segments
.iter()
.map(|segment| segment.ident.to_string())
.collect::<Vec<_>>();
format!("{leading}{}", segments.join("::"))
})
}
impl PartialEq for ContainerType {
fn eq(&self, other: &Self) -> bool {
path_text(&self.path) == path_text(&other.path)
&& self.prelude_path.as_ref().map(path_text)
== other.prelude_path.as_ref().map(path_text)
&& self.obligations == other.obligations
&& self.provisions == other.provisions
}
}
impl Eq for ContainerType {}
impl std::fmt::Debug for ContainerType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContainerType")
.field("path", &path_text(&self.path))
.field("prelude_path", &self.prelude_path.as_ref().map(path_text))
.field("obligations", &self.obligations)
.field("provisions", &self.provisions)
.finish()
}
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
struct ContainerTypeRepr {
#[serde(default)]
like: Option<ContainerKind>,
#[serde(default)]
path: Option<String>,
#[serde(default)]
obligations: Option<Vec<TypespaceTraitSet>>,
#[serde(default)]
provides: BTreeMap<TypespaceTrait, TraitProvision>,
}
#[derive(Deserialize)]
#[serde(rename_all = "kebab-case")]
enum ContainerKind {
BtreeMap,
HashMap,
Vec,
Option,
BtreeSet,
HashSet,
}
impl ContainerKind {
fn preset(&self) -> ContainerType {
match self {
Self::BtreeMap => ContainerType::btree_map(),
Self::HashMap => ContainerType::hash_map(),
Self::Vec => ContainerType::vec(),
Self::Option => ContainerType::option(),
Self::BtreeSet => ContainerType::btree_set(),
Self::HashSet => ContainerType::hash_set(),
}
}
}
impl TryFrom<ContainerTypeRepr> for ContainerType {
type Error = String;
fn try_from(repr: ContainerTypeRepr) -> Result<Self, Self::Error> {
let ContainerTypeRepr {
like,
path,
obligations,
provides,
} = repr;
let path = path
.map(|path| {
syn::parse_str::<syn::Type>(&path)
.map_err(|err| format!("invalid container type {path:?}: {err}"))
})
.transpose()?;
let declared = match (like, path, obligations) {
(Some(chosen), path, obligations) => {
let with_path = match path {
Some(path) => chosen.preset().with_parsed_path(path),
None => chosen.preset(),
};
match obligations {
Some(obligations) => with_path.with_obligations(obligations),
None => with_path,
}
}
(None, Some(path), Some(obligations)) => Self::opaque(path, obligations),
(None, _, _) => {
return Err("a container declaration states the preset it behaves \
as with `like`, or states its own `path` and \
`obligations`"
.to_string());
}
};
Ok(declared.with_provision_overrides(provides))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct ProvisionTable(BTreeMap<TypespaceTrait, TraitProvision>);
impl ProvisionTable {
fn new(never: &[TypespaceTrait], always: &[TypespaceTrait]) -> Self {
Self(
TypespaceTrait::iter()
.map(|trait_| {
let provision = match (never.contains(&trait_), always.contains(&trait_)) {
(true, _) => TraitProvision::Never,
(false, true) => TraitProvision::Always,
(false, false) => TraitProvision::IfParameters,
};
(trait_, provision)
})
.collect(),
)
}
fn opaque() -> Self {
let forwarded = [
TypespaceTrait::Clone,
TypespaceTrait::Debug,
TypespaceTrait::Serialize,
TypespaceTrait::Deserialize,
TypespaceTrait::Default,
];
let never = TypespaceTrait::iter()
.filter(|tt| !forwarded.contains(tt))
.collect::<Vec<_>>();
Self::new(&never, &[TypespaceTrait::Default])
}
fn get(&self, trait_: TypespaceTrait) -> TraitProvision {
self.0[&trait_]
}
fn set(&mut self, trait_: TypespaceTrait, provision: TraitProvision) {
self.0.insert(trait_, provision);
}
fn iter(&self) -> impl Iterator<Item = (TypespaceTrait, TraitProvision)> + '_ {
self.0
.iter()
.map(|(trait_, provision)| (*trait_, *provision))
}
fn overridden(self, provides: BTreeMap<TypespaceTrait, TraitProvision>) -> Self {
provides
.into_iter()
.fold(self, |mut table, (trait_, provision)| {
table.set(trait_, provision);
table
})
}
}
#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Std {
#[default]
FullyQualified,
Unqualified,
}
#[allow(clippy::large_enum_variant)]
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum OptionalNullable {
#[default]
ConflateAsAbsent,
ConflateAsNull,
DoubleOption,
CustomType(ContainerType),
}