use oas3::spec::{ObjectOrReference, ObjectSchema, SchemaType, SchemaTypeSet};
use crate::generator::{
ast::{RustType, TypeRef},
converter::cache::SharedSchemaCache,
};
pub(crate) struct ConversionOutput<T> {
pub result: T,
pub inline_types: Vec<RustType>,
}
impl<T> ConversionOutput<T> {
pub(crate) fn new(result: T) -> Self {
Self {
result,
inline_types: vec![],
}
}
pub(crate) fn with_inline_types(result: T, inline_types: Vec<RustType>) -> Self {
Self { result, inline_types }
}
}
pub(crate) fn handle_inline_creation<F, C>(
schema: &ObjectSchema,
base_name: &str,
forced_name: Option<String>,
mut cache: Option<&mut SharedSchemaCache>,
cached_name_check: C,
generator: F,
) -> anyhow::Result<ConversionOutput<TypeRef>>
where
F: FnOnce(&str, Option<&mut SharedSchemaCache>) -> anyhow::Result<ConversionOutput<RustType>>,
C: FnOnce(&SharedSchemaCache) -> Option<String>,
{
if let Some(cache) = &cache {
if let Some(existing_name) = cache.get_type_name(schema)? {
return Ok(ConversionOutput::new(TypeRef::new(existing_name)));
}
if let Some(name) = cached_name_check(cache) {
return Ok(ConversionOutput::new(TypeRef::new(name)));
}
}
let name = if let Some(forced) = forced_name {
forced
} else if let Some(cache) = &cache {
cache.get_preferred_name(schema, base_name)?
} else {
base_name.to_string()
};
let result = generator(&name, cache.as_deref_mut())?;
if let Some(cache) = cache {
let type_name = cache.register_type(schema, &name, result.inline_types, result.result.clone())?;
Ok(ConversionOutput::new(TypeRef::new(type_name)))
} else {
let mut all_types = vec![result.result];
all_types.extend(result.inline_types);
Ok(ConversionOutput::with_inline_types(TypeRef::new(name), all_types))
}
}
pub(crate) trait SchemaExt {
fn is_primitive(&self) -> bool;
fn is_null(&self) -> bool;
fn is_nullable_object(&self) -> bool;
fn is_array(&self) -> bool;
fn is_string(&self) -> bool;
fn is_object(&self) -> bool;
fn is_numeric(&self) -> bool;
fn is_nullable_array(&self) -> bool;
fn is_single_type(&self, schema_type: SchemaType) -> bool;
fn single_type(&self) -> Option<SchemaType>;
fn non_null_type(&self) -> Option<SchemaType>;
fn is_inline_struct(&self, prop_schema_ref: &ObjectOrReference<ObjectSchema>) -> bool;
fn is_discriminated_base_type(&self) -> bool;
}
impl SchemaExt for ObjectSchema {
fn is_primitive(&self) -> bool {
self.properties.is_empty()
&& self.one_of.is_empty()
&& self.any_of.is_empty()
&& self.all_of.is_empty()
&& (self.schema_type.is_some() || self.enum_values.len() <= 1)
}
fn is_null(&self) -> bool {
self.is_single_type(SchemaType::Null)
}
fn is_nullable_object(&self) -> bool {
if self.is_null() {
return true;
}
if let Some(SchemaTypeSet::Multiple(types)) = &self.schema_type {
types.contains(&SchemaType::Null)
&& types.contains(&SchemaType::Object)
&& self.properties.is_empty()
&& self.additional_properties.is_none()
} else {
false
}
}
fn is_array(&self) -> bool {
self.is_single_type(SchemaType::Array)
}
fn is_string(&self) -> bool {
self.is_single_type(SchemaType::String)
}
fn is_object(&self) -> bool {
self.is_single_type(SchemaType::Object)
}
fn is_numeric(&self) -> bool {
matches!(
&self.schema_type,
Some(SchemaTypeSet::Single(SchemaType::Number | SchemaType::Integer))
)
}
fn is_nullable_array(&self) -> bool {
match &self.schema_type {
Some(SchemaTypeSet::Multiple(types)) => {
types.len() == 2 && types.contains(&SchemaType::Array) && types.contains(&SchemaType::Null)
}
_ => false,
}
}
fn is_single_type(&self, schema_type: SchemaType) -> bool {
matches!(
&self.schema_type,
Some(SchemaTypeSet::Single(t)) if *t == schema_type
)
}
fn single_type(&self) -> Option<SchemaType> {
match &self.schema_type {
Some(SchemaTypeSet::Single(t)) => Some(*t),
_ => None,
}
}
fn non_null_type(&self) -> Option<SchemaType> {
match &self.schema_type {
Some(SchemaTypeSet::Multiple(types)) if types.len() == 2 && types.contains(&SchemaType::Null) => {
types.iter().find(|t| **t != SchemaType::Null).copied()
}
_ => None,
}
}
fn is_inline_struct(&self, prop_schema_ref: &ObjectOrReference<ObjectSchema>) -> bool {
if matches!(prop_schema_ref, ObjectOrReference::Ref { .. }) {
return false;
}
if !self.enum_values.is_empty() {
return false;
}
if !self.one_of.is_empty() || !self.any_of.is_empty() {
return false;
}
if self.is_array() {
return false;
}
if self.is_primitive() {
return false;
}
!self.properties.is_empty()
}
fn is_discriminated_base_type(&self) -> bool {
self
.discriminator
.as_ref()
.and_then(|d| d.mapping.as_ref().map(|m| !m.is_empty()))
.unwrap_or(false)
&& !self.properties.is_empty()
}
}