use std::collections::{BTreeMap, BTreeSet};
use oas3::spec::ObjectSchema;
use super::{SchemaExt, hashing::CanonicalSchema, struct_summaries::StructSummary};
use crate::generator::{
ast::{EnumToken, RustType, StructToken},
naming::{
identifiers::{ensure_unique, to_rust_type_name},
inference::InferenceExt,
},
};
#[derive(Default, Debug, Clone)]
struct TypeIdentityCache {
schema_to_type: BTreeMap<CanonicalSchema, String>,
enum_to_type: BTreeMap<Vec<String>, String>,
union_refs_to_type: BTreeMap<(BTreeSet<String>, Option<String>), String>,
used_names: BTreeSet<String>,
precomputed_names: BTreeMap<CanonicalSchema, String>,
precomputed_enum_names: BTreeMap<Vec<String>, String>,
}
impl TypeIdentityCache {
fn set_precomputed_names(
&mut self,
names: BTreeMap<CanonicalSchema, String>,
enum_names: BTreeMap<Vec<String>, String>,
) {
self.precomputed_names = names;
self.precomputed_enum_names = enum_names;
}
fn schema_type(&self, canonical: &CanonicalSchema) -> Option<String> {
self.schema_to_type.get(canonical).cloned()
}
fn enum_type(&self, values: &[String]) -> Option<String> {
self
.enum_to_type
.get(values)
.or_else(|| self.precomputed_enum_names.get(values))
.cloned()
}
fn generated_enum_type(&self, values: &[String]) -> Option<String> {
self.enum_to_type.get(values).cloned()
}
fn enum_registered(&self, values: &[String]) -> bool {
self.enum_to_type.contains_key(values)
}
fn register_enum(&mut self, values: Vec<String>, name: String) {
self.enum_to_type.insert(values, name);
}
fn union_type(&self, refs: &BTreeSet<String>, discriminator: Option<&str>) -> Option<String> {
self
.union_refs_to_type
.get(&(refs.clone(), discriminator.map(String::from)))
.cloned()
}
fn register_union(&mut self, refs: BTreeSet<String>, discriminator: Option<String>, name: String) {
self.union_refs_to_type.insert((refs, discriminator), name);
}
fn mark_used(&mut self, name: String) {
self.used_names.insert(name);
}
fn is_used(&self, name: &str) -> bool {
self.used_names.contains(name)
}
fn record_schema_name(&mut self, canonical: CanonicalSchema, name: String) {
self.schema_to_type.insert(canonical, name);
}
fn has_schema_name(&self, canonical: &CanonicalSchema, name: &str) -> bool {
self.schema_to_type.get(canonical).is_some_and(|n| n == name)
}
fn preferred_name(&self, canonical: &CanonicalSchema, base_name: &str) -> String {
self
.precomputed_names
.get(canonical)
.cloned()
.unwrap_or_else(|| self.make_unique_name(base_name))
}
fn make_unique_name(&self, base: &str) -> String {
let rust_name = to_rust_type_name(base);
ensure_unique(&rust_name, &self.used_names)
}
}
#[derive(Default, Debug, Clone)]
pub(crate) struct GeneratedTypeStore {
pub(crate) generated_types: Vec<RustType>,
}
impl GeneratedTypeStore {
fn push(&mut self, mut nested_types: Vec<RustType>, type_def: RustType) {
self.generated_types.append(&mut nested_types);
self.generated_types.push(type_def);
}
fn into_inner(self) -> Vec<RustType> {
self.generated_types
}
}
#[derive(Default, Debug, Clone)]
struct StructSummaryIndex {
summaries: BTreeMap<String, StructSummary>,
}
impl StructSummaryIndex {
fn register(&mut self, type_name: &str, summary: StructSummary) {
self.summaries.insert(type_name.to_string(), summary);
}
fn get(&self, type_name: &str) -> Option<&StructSummary> {
self.summaries.get(type_name)
}
}
#[derive(Debug, Clone)]
pub(crate) struct SharedSchemaCache {
identity: TypeIdentityCache,
pub(crate) generated: GeneratedTypeStore,
summaries: StructSummaryIndex,
}
impl SharedSchemaCache {
pub(crate) fn new() -> Self {
Self {
identity: TypeIdentityCache::default(),
generated: GeneratedTypeStore::default(),
summaries: StructSummaryIndex::default(),
}
}
pub(crate) fn set_precomputed_names(
&mut self,
names: BTreeMap<CanonicalSchema, String>,
enum_names: BTreeMap<Vec<String>, String>,
) {
self.identity.set_precomputed_names(names, enum_names);
}
pub(crate) fn get_type_name(&self, schema: &ObjectSchema) -> anyhow::Result<Option<String>> {
let canonical = CanonicalSchema::from_schema(schema)?;
Ok(self.identity.schema_type(&canonical))
}
pub(crate) fn get_enum_name(&self, values: &[String]) -> Option<String> {
self.identity.enum_type(values)
}
pub(crate) fn lookup_enum_name(&self, schema: &ObjectSchema) -> Option<String> {
if schema.is_relaxed_enum_pattern() {
return None;
}
let values = schema.extract_enum_values()?;
self.get_enum_name(&values)
}
pub(crate) fn is_enum_generated(&self, values: &[String]) -> bool {
self.identity.enum_registered(values)
}
pub(crate) fn register_enum(&mut self, values: Vec<String>, name: String) {
self.identity.register_enum(values, name);
}
pub(crate) fn get_union_name(&self, refs: &BTreeSet<String>, discriminator: Option<&str>) -> Option<String> {
self.identity.union_type(refs, discriminator)
}
pub(crate) fn register_union(&mut self, refs: BTreeSet<String>, discriminator: Option<String>, name: String) {
self.identity.register_union(refs, discriminator, name);
}
pub(crate) fn mark_name_used(&mut self, name: String) {
self.identity.mark_used(name);
}
pub(crate) fn register_top_level_schema(&mut self, schema: &ObjectSchema, name: &str) -> anyhow::Result<()> {
let canonical = CanonicalSchema::from_schema(schema)?;
let rust_name = to_rust_type_name(name);
self.identity.record_schema_name(canonical, rust_name.clone());
self.identity.mark_used(rust_name);
Ok(())
}
pub(crate) fn get_preferred_name(&self, schema: &ObjectSchema, base_name: &str) -> anyhow::Result<String> {
let canonical = CanonicalSchema::from_schema(schema)?;
Ok(self.identity.preferred_name(&canonical, base_name))
}
pub(crate) fn register_type(
&mut self,
schema: &ObjectSchema,
base_name: &str,
nested_types: Vec<RustType>,
type_def: RustType,
) -> anyhow::Result<String> {
let canonical = CanonicalSchema::from_schema(schema)?;
if !schema.is_relaxed_enum_pattern()
&& let Some(values) = schema.extract_enum_values()
&& let Some(existing_name) = self.identity.generated_enum_type(&values)
{
self.identity.record_schema_name(canonical, existing_name.clone());
return Ok(existing_name);
}
let mut name = base_name.to_string();
if self.identity.is_used(&name) {
if let Some(existing_name) = self.identity.schema_type(&canonical) {
return Ok(existing_name);
}
name = self.identity.make_unique_name(&name);
}
self.identity.mark_used(name.clone());
self.identity.record_schema_name(canonical, name.clone());
if !schema.has_relaxed_anyof_enum()
&& let Some(values) = schema.extract_enum_values()
{
self.identity.register_enum(values, name.clone());
}
let mut final_type_def = type_def;
match &mut final_type_def {
RustType::Struct(s) => s.name = StructToken::from(name.clone()),
RustType::Enum(e) => e.name = EnumToken::new(&name),
_ => {}
}
self.generated.push(nested_types, final_type_def);
Ok(name)
}
pub(crate) fn make_unique_name(&self, base: &str) -> String {
self.identity.make_unique_name(base)
}
pub(crate) fn name_conflicts_with_different_schema(&self, name: &str, schema: &ObjectSchema) -> anyhow::Result<bool> {
if !self.identity.is_used(name) {
return Ok(false);
}
let canonical = CanonicalSchema::from_schema(schema)?;
Ok(!self.identity.has_schema_name(&canonical, name))
}
pub(crate) fn into_types(self) -> Vec<RustType> {
self.generated.into_inner()
}
pub(crate) fn register_struct_summary(&mut self, type_name: &str, summary: StructSummary) {
self.summaries.register(type_name, summary);
}
pub(crate) fn get_struct_summary(&self, type_name: &str) -> Option<&StructSummary> {
self.summaries.get(type_name)
}
}