use std::{collections::BTreeSet, rc::Rc};
use itertools::Itertools;
use oas3::spec::ObjectSchema;
use super::{
ConversionOutput, ConverterContext,
union_types::{CollisionStrategy, variants_to_cache_key},
value_enums::ValueEnumBuilder,
};
use crate::{
generator::{
ast::{
Documentation, EnumDef, EnumMethod, EnumMethodKind, EnumToken, EnumVariantToken, RustPrimitive, RustType,
SerdeAttribute, TypeRef, VariantContent, VariantDef,
},
naming::{
constants::{KNOWN_ENUM_VARIANT, OTHER_ENUM_VARIANT},
identifiers::{ensure_unique, to_rust_type_name},
inference::derive_method_names,
},
},
utils::{SchemaExt, schema_ext::SchemaExtIters},
};
#[derive(Clone, Debug)]
pub(crate) struct RelaxedEnumBuilder {
context: Rc<ConverterContext>,
value_enum_builder: ValueEnumBuilder,
}
impl RelaxedEnumBuilder {
pub(crate) fn new(context: Rc<ConverterContext>) -> Self {
let case_insensitive = context.config().case_insensitive_enums();
let sort_variants = context.config().sort_enum_variants();
Self {
context,
value_enum_builder: ValueEnumBuilder::new(case_insensitive, sort_variants),
}
}
pub(crate) fn try_build_relaxed_enum(&self, name: &str, schema: &ObjectSchema) -> Option<ConversionOutput<RustType>> {
let known_variants = self.collect_known_variants(schema);
if known_variants.is_empty() {
return None;
}
Some(self.build_relaxed_enum_types(name, schema, &known_variants))
}
fn collect_known_variants(&self, schema: &ObjectSchema) -> Vec<VariantDef> {
let spec = self.context.graph().spec();
let unconstrained = schema
.any_of
.iter()
.resolve_all(spec)
.any(|s| s.is_unconstrained_string());
if !unconstrained {
return vec![];
}
schema.extract_enum_entries(spec)
}
fn build_relaxed_enum_types(
&self,
name: &str,
schema: &ObjectSchema,
known_variants: &[VariantDef],
) -> ConversionOutput<RustType> {
let base_name = to_rust_type_name(name);
let cache_key = variants_to_cache_key(known_variants);
let (known_enum_name, inner_enum_type) =
self.resolve_or_create_known_enum(&base_name, known_variants.to_owned(), &cache_key);
let methods = if self.context.config().no_helpers() {
vec![]
} else {
Self::build_known_value_constructors(&base_name, &known_enum_name, known_variants)
};
let outer_enum = Self::build_wrapper_enum(&base_name, &known_enum_name, schema, methods);
ConversionOutput::with_inline_types(outer_enum, inner_enum_type.into_iter().collect())
}
fn resolve_or_create_known_enum(
&self,
base_name: &str,
known_variants: Vec<VariantDef>,
cache_key: &[String],
) -> (String, Option<RustType>) {
let cached_result = {
let cache = self.context.cache();
cache
.get_enum_name(cache_key)
.map(|name| (name.clone(), cache.is_enum_generated(cache_key)))
};
match cached_result {
Some((name, true)) => (name, None),
Some((name, false)) => self.create_known_enum(name, known_variants, cache_key),
None => self.create_known_enum(format!("{base_name}Known"), known_variants, cache_key),
}
}
fn create_known_enum(
&self,
name: String,
known_variants: Vec<VariantDef>,
cache_key: &[String],
) -> (String, Option<RustType>) {
let def = self.value_enum_builder.build_enum_from_variants(
&name,
known_variants,
CollisionStrategy::Preserve,
Documentation::from_lines(["Known values for the string enum."]),
None,
);
let mut cache = self.context.cache_mut();
cache.register_enum(cache_key.to_vec(), name.clone());
cache.mark_name_used(name.clone());
(name, Some(def))
}
fn build_known_value_constructors(
wrapper_enum_name: &str,
known_type_name: &str,
variants: &[VariantDef],
) -> Vec<EnumMethod> {
let known_type = EnumToken::new(known_type_name);
let variant_strings = variants.iter().map(|v| v.name.to_string()).collect_vec();
let method_names = derive_method_names(wrapper_enum_name, &variant_strings);
let mut seen = BTreeSet::new();
variants
.iter()
.zip(&method_names)
.map(|(variant, base_name)| {
let method_name = ensure_unique(base_name, &seen);
seen.insert(method_name.clone());
EnumMethod::new(
method_name,
EnumMethodKind::KnownValueConstructor {
known_type: known_type.clone(),
known_variant: variant.name.clone(),
},
variant.docs.clone(),
)
})
.collect()
}
fn build_wrapper_enum(
name: &str,
known_type_name: &str,
schema: &ObjectSchema,
methods: Vec<EnumMethod>,
) -> RustType {
RustType::Enum(
EnumDef::builder()
.name(EnumToken::new(name))
.docs(Documentation::from_optional(schema.description.as_ref()))
.variants(vec![
VariantDef::builder()
.name(EnumVariantToken::new(KNOWN_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new(known_type_name)]))
.build(),
VariantDef::builder()
.name(EnumVariantToken::new(OTHER_ENUM_VARIANT))
.content(VariantContent::Tuple(vec![TypeRef::new(RustPrimitive::String)]))
.build(),
])
.serde_attrs(vec![SerdeAttribute::Untagged])
.methods(methods)
.generate_display(true)
.build(),
)
}
}