use std::rc::Rc;
use oas3::spec::ObjectSchema;
use super::{
ConversionOutput, SchemaExt, discriminator::DiscriminatorConverter, fields::FieldConverter,
type_resolver::TypeResolver,
};
use crate::generator::{
ast::{Documentation, RustType, StructDef, StructKind, StructToken},
converter::ConverterContext,
naming::{constants::DISCRIMINATED_BASE_SUFFIX, identifiers::to_rust_type_name},
};
#[derive(Clone, Debug)]
pub(crate) struct StructConverter {
context: Rc<ConverterContext>,
type_resolver: TypeResolver,
field_converter: FieldConverter,
discriminator_converter: DiscriminatorConverter,
}
impl StructConverter {
pub(crate) fn new(context: Rc<ConverterContext>) -> Self {
let type_resolver = TypeResolver::new(context.clone());
let field_converter = FieldConverter::new(&context);
let discriminator_converter = DiscriminatorConverter::new(context.clone());
Self {
context,
type_resolver,
field_converter,
discriminator_converter,
}
}
fn struct_name(name: &str, schema: &ObjectSchema) -> StructToken {
if schema.is_discriminated_base_type() {
StructToken::from(format!("{}{DISCRIMINATED_BASE_SUFFIX}", to_rust_type_name(name)))
} else {
StructToken::from_raw(name)
}
}
fn build_struct(
&self,
name: StructToken,
schema: &ObjectSchema,
schema_name: Option<&str>,
kind: StructKind,
) -> anyhow::Result<ConversionOutput<RustType>> {
let field_result = self
.field_converter
.collect_fields(name.as_str(), schema, schema_name)?;
let (additional_serde_attrs, additional_field) = self.field_converter.build_additional_properties(schema)?;
let mut fields = field_result.result;
if let Some(extra) = additional_field {
fields.push(extra);
}
let (serde_attrs, outer_attrs) = FieldConverter::struct_attributes(&fields, additional_serde_attrs);
let struct_def = StructDef::builder()
.name(name)
.docs(Documentation::from_optional(schema.description.as_ref()))
.fields(fields)
.serde_attrs(serde_attrs)
.outer_attrs(outer_attrs)
.kind(kind)
.build();
Ok(ConversionOutput::with_inline_types(
RustType::Struct(struct_def),
field_result.inline_types,
))
}
fn convert_discriminated_child(
&self,
name: &str,
merged_schema: &ObjectSchema,
parent_schema: &ObjectSchema,
) -> anyhow::Result<Vec<RustType>> {
if parent_schema.discriminator.is_none() {
anyhow::bail!("Parent schema for discriminated child '{name}' is not a valid discriminator base");
}
let struct_token = StructToken::from_raw(name);
let field_result = self
.field_converter
.collect_fields(struct_token.as_str(), merged_schema, Some(name))?;
let (additional_serde_attrs, additional_field) = self.field_converter.build_additional_properties(merged_schema)?;
let mut fields = field_result.result;
if let Some(extra) = additional_field {
fields.push(extra);
}
let (serde_attrs, outer_attrs) = FieldConverter::struct_attributes(&fields, additional_serde_attrs);
let struct_def = StructDef::builder()
.name(struct_token)
.docs(Documentation::from_optional(merged_schema.description.as_ref()))
.fields(fields)
.serde_attrs(serde_attrs)
.outer_attrs(outer_attrs)
.kind(StructKind::Schema)
.build();
Ok(
std::iter::once(RustType::Struct(struct_def))
.chain(field_result.inline_types)
.collect(),
)
}
pub(crate) fn convert_struct(
&self,
name: &str,
schema: &ObjectSchema,
kind: Option<StructKind>,
) -> anyhow::Result<ConversionOutput<RustType>> {
let struct_name = Self::struct_name(name, schema);
let result = self.build_struct(
struct_name.clone(),
schema,
Some(name),
kind.unwrap_or(StructKind::Schema),
)?;
self.context.cache.borrow_mut().register_struct_def(
struct_name.as_str(),
match &result.result {
RustType::Struct(def) => def.clone(),
_ => unreachable!(),
},
);
Ok(result)
}
pub(crate) fn finalize_struct_types(
&self,
name: &str,
schema: &ObjectSchema,
main_type: RustType,
inline_types: Vec<RustType>,
) -> anyhow::Result<Vec<RustType>> {
let discriminated_enum = schema
.is_discriminated_base_type()
.then(|| {
let base_struct_name = match &main_type {
RustType::Struct(def) => def.name.clone(),
_ => StructToken::from(format!("{}{DISCRIMINATED_BASE_SUFFIX}", to_rust_type_name(name))),
};
self
.type_resolver
.discriminated_enum(name, schema, base_struct_name.as_str())
})
.transpose()?;
Ok(
discriminated_enum
.into_iter()
.chain(std::iter::once(main_type))
.chain(inline_types)
.collect(),
)
}
pub(crate) fn convert_all_of_schema(&self, name: &str) -> anyhow::Result<Vec<RustType>> {
let graph = self.context.graph();
let merged_info = graph
.merged(name)
.ok_or_else(|| anyhow::anyhow!("Schema '{name}' not found in registry"))?;
if let Some(parent_info) = self.discriminator_converter.detect_discriminated_parent(name) {
let parent_merged = graph
.merged(&parent_info.parent_name)
.ok_or_else(|| anyhow::anyhow!("Parent schema '{}' not found", parent_info.parent_name))?;
return self.convert_discriminated_child(name, &merged_info.schema, &parent_merged.schema);
}
let effective_schema = graph.resolved(name).unwrap_or(&merged_info.schema);
let result = self.build_struct(
Self::struct_name(name, effective_schema),
effective_schema,
Some(name),
StructKind::Schema,
)?;
self.finalize_struct_types(name, effective_schema, result.result, result.inline_types)
}
}