use std::rc::Rc;
use oas3::spec::ObjectSchema;
use super::{ConversionOutput, discriminator::DiscriminatorConverter, fields::FieldConverter};
use crate::{
generator::{
ast::{Documentation, RustType, StructDef, StructKind, StructToken},
converter::ConverterContext,
naming::{constants::DISCRIMINATED_BASE_SUFFIX, identifiers::to_rust_type_name},
},
utils::SchemaExt,
};
#[derive(Clone, Debug)]
pub(crate) struct StructConverter {
context: Rc<ConverterContext>,
field_converter: FieldConverter,
discriminator_converter: DiscriminatorConverter,
}
impl StructConverter {
pub(crate) fn new(context: Rc<ConverterContext>) -> Self {
let field_converter = FieldConverter::new(&context);
let discriminator_converter = DiscriminatorConverter::new(context.clone());
Self {
context,
field_converter,
discriminator_converter,
}
}
pub(crate) fn struct_name(name: &str, schema: &ObjectSchema) -> StructToken {
if schema.is_discriminated_base_type() {
StructToken::from(format!("{}{}", to_rust_type_name(name), DISCRIMINATED_BASE_SUFFIX))
} 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 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_name) = self.discriminator_converter.detect_discriminated_parent(name) {
let parent_merged = graph
.merged(parent_name)
.ok_or_else(|| anyhow::anyhow!("Parent schema '{parent_name}' not found"))?;
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)
}
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.as_str().to_string(),
_ => format!("{}{DISCRIMINATED_BASE_SUFFIX}", to_rust_type_name(name)),
};
self
.discriminator_converter
.build_base_discriminated_enum(name, schema, &base_struct_name)
})
.transpose()?;
Ok(
discriminated_enum
.into_iter()
.chain(std::iter::once(main_type))
.chain(inline_types)
.collect(),
)
}
}