use std::collections::BTreeSet;
use oas3::spec::{ObjectOrReference, ObjectSchema, Schema, SchemaType, SchemaTypeSet};
use regex::Regex;
use serde_json::Number;
use crate::reserved::{to_rust_field_name, to_rust_type_name};
use super::{
SchemaGraph,
ast::{EnumDef, FieldDef, RustType, StructDef, TypeRef, VariantContent, VariantDef},
utils::doc_comment_lines,
};
pub struct SchemaConverter<'a> {
graph: &'a SchemaGraph,
}
impl<'a> SchemaConverter<'a> {
pub fn new(graph: &'a SchemaGraph) -> Self {
Self { graph }
}
pub fn convert_schema(&self, name: &str, schema: &ObjectSchema) -> anyhow::Result<Vec<RustType>> {
if !schema.one_of.is_empty() {
return Ok(vec![self.convert_one_of_enum(name, schema)?]);
}
if !schema.any_of.is_empty() {
return Ok(vec![self.convert_any_of_enum(name, schema)?]);
}
if !schema.enum_values.is_empty() {
return Ok(vec![self.convert_simple_enum(name, schema, &schema.enum_values)?]);
}
if !schema.properties.is_empty() {
let (main_type, inline_types) = self.convert_struct(name, schema)?;
let mut all_types = vec![main_type];
all_types.extend(inline_types);
return Ok(all_types);
}
Ok(vec![])
}
fn convert_one_of_enum(&self, name: &str, schema: &ObjectSchema) -> anyhow::Result<RustType> {
let mut variants = Vec::new();
let mut seen_names = BTreeSet::new();
let discriminator_property = schema.discriminator.as_ref().map(|d| d.property_name.as_str());
for (i, variant_schema_ref) in schema.one_of.iter().enumerate() {
if let Ok(variant_schema) = variant_schema_ref.resolve(self.graph.spec()) {
if variant_schema.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null)) {
continue;
}
let mut variant_name = if let Some(ref title) = variant_schema.title {
to_rust_type_name(title)
} else {
self.infer_variant_name(&variant_schema, i)
};
if seen_names.contains(&variant_name) {
variant_name = format!("{}{}", variant_name, i);
}
seen_names.insert(variant_name.clone());
let docs = variant_schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default();
let deprecated = variant_schema.deprecated.unwrap_or(false);
let content = if discriminator_property.is_some() {
if !variant_schema.properties.is_empty() {
let fields = self.convert_fields_with_exclusions(&variant_schema, discriminator_property)?;
VariantContent::Struct(fields)
} else {
let type_ref = self.schema_to_type_ref(&variant_schema)?;
VariantContent::Tuple(vec![type_ref])
}
} else {
if let Some(ref title) = variant_schema.title {
if self.graph.get_schema(title).is_some() {
let type_ref = TypeRef::new(to_rust_type_name(title));
VariantContent::Tuple(vec![type_ref])
} else if !variant_schema.properties.is_empty() {
let fields = self.convert_fields(&variant_schema)?;
VariantContent::Struct(fields)
} else {
let type_ref = self.schema_to_type_ref(&variant_schema)?;
VariantContent::Tuple(vec![type_ref])
}
} else if !variant_schema.properties.is_empty() {
let fields = self.convert_fields(&variant_schema)?;
VariantContent::Struct(fields)
} else {
let type_ref = self.schema_to_type_ref(&variant_schema)?;
VariantContent::Tuple(vec![type_ref])
}
};
variants.push(VariantDef {
name: to_rust_type_name(&variant_name),
docs,
content,
serde_attrs: vec![],
deprecated,
});
}
}
let discriminator = schema.discriminator.as_ref().map(|d| d.property_name.clone());
Ok(RustType::Enum(EnumDef {
name: to_rust_type_name(name),
docs: schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default(),
variants,
discriminator,
derives: vec!["Debug".into(), "Clone".into(), "Serialize".into(), "Deserialize".into()],
serde_attrs: vec![],
}))
}
fn convert_any_of_enum(&self, name: &str, schema: &ObjectSchema) -> anyhow::Result<RustType> {
let has_freeform_string = schema.any_of.iter().any(|s| {
if let Ok(resolved) = s.resolve(self.graph.spec()) {
resolved.const_value.is_none() && resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::String))
} else {
false
}
});
let const_values: Vec<_> = schema
.any_of
.iter()
.filter_map(|s| {
if let Ok(resolved) = s.resolve(self.graph.spec()) {
resolved.const_value.as_ref().map(|v| {
(
v.clone(),
resolved.description.clone(),
resolved.deprecated.unwrap_or(false),
)
})
} else {
None
}
})
.collect();
if has_freeform_string && !const_values.is_empty() {
return self.convert_string_enum_with_catchall(name, schema, &const_values);
}
let mut variants = Vec::new();
let mut seen_names = BTreeSet::new();
for (i, variant_schema_ref) in schema.any_of.iter().enumerate() {
if let Ok(variant_schema) = variant_schema_ref.resolve(self.graph.spec()) {
if variant_schema.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null)) {
continue;
}
let mut variant_name = if let Some(ref title) = variant_schema.title {
to_rust_type_name(title)
} else {
self.infer_variant_name(&variant_schema, i)
};
if seen_names.contains(&variant_name) {
variant_name = format!("{}{}", variant_name, i);
}
seen_names.insert(variant_name.clone());
let docs = variant_schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default();
let deprecated = variant_schema.deprecated.unwrap_or(false);
let content = if let Some(ref title) = variant_schema.title {
if self.graph.get_schema(title).is_some() {
let type_ref = TypeRef::new(to_rust_type_name(title));
VariantContent::Tuple(vec![type_ref])
} else if !variant_schema.properties.is_empty() {
let fields = self.convert_fields(&variant_schema)?;
VariantContent::Struct(fields)
} else {
let type_ref = self.schema_to_type_ref(&variant_schema)?;
VariantContent::Tuple(vec![type_ref])
}
} else if !variant_schema.properties.is_empty() {
let fields = self.convert_fields(&variant_schema)?;
VariantContent::Struct(fields)
} else {
let type_ref = self.schema_to_type_ref(&variant_schema)?;
VariantContent::Tuple(vec![type_ref])
};
variants.push(VariantDef {
name: to_rust_type_name(&variant_name),
docs,
content,
serde_attrs: vec![],
deprecated,
});
}
}
Ok(RustType::Enum(EnumDef {
name: to_rust_type_name(name),
docs: schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default(),
variants,
discriminator: None,
derives: vec!["Debug".into(), "Clone".into(), "Serialize".into(), "Deserialize".into()],
serde_attrs: vec!["untagged".into()],
}))
}
fn convert_string_enum_with_catchall(
&self,
name: &str,
schema: &ObjectSchema,
const_values: &[(serde_json::Value, Option<String>, bool)],
) -> anyhow::Result<RustType> {
let mut variants = Vec::new();
for (value, description, deprecated) in const_values {
if let Some(str_val) = value.as_str() {
let variant_name = to_rust_type_name(str_val);
let docs = description.as_ref().map(|d| doc_comment_lines(d)).unwrap_or_default();
variants.push(VariantDef {
name: variant_name,
docs,
content: VariantContent::Unit,
serde_attrs: vec![format!("rename = \"{}\"", str_val)],
deprecated: *deprecated,
});
}
}
variants.push(VariantDef {
name: "Other".to_string(),
docs: vec!["/// Any other string value".to_string()],
content: VariantContent::Tuple(vec![TypeRef::new("String")]),
serde_attrs: vec!["untagged".to_string()],
deprecated: false,
});
Ok(RustType::Enum(EnumDef {
name: to_rust_type_name(name),
docs: schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default(),
variants,
discriminator: None,
derives: vec![
"Debug".into(),
"Clone".into(),
"PartialEq".into(),
"Eq".into(),
"Serialize".into(),
"Deserialize".into(),
],
serde_attrs: vec![],
}))
}
fn infer_variant_name(&self, schema: &ObjectSchema, index: usize) -> String {
if !schema.enum_values.is_empty() {
return "Enum".to_string();
}
if let Some(ref schema_type) = schema.schema_type {
match schema_type {
SchemaTypeSet::Single(typ) => match typ {
SchemaType::String => "String".to_string(),
SchemaType::Number => "Number".to_string(),
SchemaType::Integer => "Integer".to_string(),
SchemaType::Boolean => "Boolean".to_string(),
SchemaType::Array => "Array".to_string(),
SchemaType::Object => "Object".to_string(),
SchemaType::Null => "Null".to_string(),
},
SchemaTypeSet::Multiple(_) => "Mixed".to_string(),
}
} else {
format!("Variant{}", index)
}
}
fn convert_simple_enum(
&self,
name: &str,
schema: &ObjectSchema,
enum_values: &[serde_json::Value],
) -> anyhow::Result<RustType> {
let mut variants = Vec::new();
for value in enum_values {
if let Some(str_val) = value.as_str() {
let variant_name = to_rust_type_name(str_val);
variants.push(VariantDef {
name: variant_name,
docs: vec![],
content: VariantContent::Unit,
serde_attrs: vec![format!("rename = \"{}\"", str_val)],
deprecated: false,
});
}
}
Ok(RustType::Enum(EnumDef {
name: to_rust_type_name(name),
docs: schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default(),
variants,
discriminator: None,
derives: vec!["Debug".into(), "Clone".into(), "Serialize".into(), "Deserialize".into()],
serde_attrs: vec![],
}))
}
pub fn convert_struct(&self, name: &str, schema: &ObjectSchema) -> anyhow::Result<(RustType, Vec<RustType>)> {
let (mut fields, inline_types) = self.convert_fields_with_inline_types(name, schema)?;
let mut serde_attrs = vec![];
if let Some(ref additional) = schema.additional_properties {
match additional {
Schema::Boolean(bool_schema) => {
if !bool_schema.0 {
serde_attrs.push("deny_unknown_fields".to_string());
}
}
Schema::Object(schema_ref) => {
if let Ok(additional_schema) = schema_ref.resolve(self.graph.spec()) {
let value_type = self.schema_to_type_ref(&additional_schema)?;
let map_type = TypeRef::new(format!(
"std::collections::HashMap<String, {}>",
value_type.to_rust_type()
));
fields.push(FieldDef {
name: "additional_properties".to_string(),
docs: vec!["/// Additional properties not defined in the schema".to_string()],
rust_type: map_type,
optional: false,
serde_attrs: vec!["flatten".to_string()],
validation_attrs: vec![],
regex_validation: None,
default_value: None,
read_only: false,
write_only: false,
deprecated: false,
multiple_of: None,
unique_items: false,
});
}
}
}
}
let all_fields_defaultable = fields.iter().all(|f| {
f.default_value.is_some()
|| f.rust_type.nullable
|| f.rust_type.is_array
|| matches!(
f.rust_type.base_type.as_str(),
"String"
| "bool"
| "i8"
| "i16"
| "i32"
| "i64"
| "i128"
| "isize"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
| "f32"
| "f64"
| "serde_json::Value"
)
});
if all_fields_defaultable && fields.iter().any(|f| f.default_value.is_some()) {
serde_attrs.push("default".to_string());
}
let all_read_only = !fields.is_empty() && fields.iter().all(|f| f.read_only);
let all_write_only = !fields.is_empty() && fields.iter().all(|f| f.write_only);
let mut derives = vec!["Debug".into(), "Clone".into()];
if !all_read_only {
derives.push("Serialize".into());
}
if !all_write_only {
derives.push("Deserialize".into());
}
derives.push("Validate".into());
let struct_type = RustType::Struct(StructDef {
name: to_rust_type_name(name),
docs: schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default(),
fields,
derives,
serde_attrs,
});
Ok((struct_type, inline_types))
}
pub fn extract_validation_pattern<'s>(&self, prop_name: &str, schema: &'s ObjectSchema) -> Option<&'s String> {
match (schema.schema_type.as_ref(), schema.pattern.as_ref()) {
(Some(SchemaTypeSet::Single(SchemaType::String)), Some(pattern)) => {
if Regex::new(pattern).is_ok() {
Some(pattern)
} else {
eprintln!(
"Warning: Invalid regex pattern '{}' for property '{}'",
pattern, prop_name
);
None
}
}
_ => None,
}
}
fn render_number(is_float: bool, num: &Number) -> String {
if is_float {
if num.to_string().contains(".") {
num.to_string()
} else {
format!("{}.0", num)
}
} else {
format!("{}i64", num.as_i64().unwrap_or_default())
}
}
pub fn extract_validation_attrs(&self, _prop_name: &str, is_required: bool, schema: &ObjectSchema) -> Vec<String> {
let mut attrs = Vec::new();
if let Some(ref format) = schema.format {
match format.as_str() {
"email" => attrs.push("email".to_string()),
"uri" | "url" => attrs.push("url".to_string()),
_ => {}
}
}
if let Some(ref schema_type) = schema.schema_type {
if matches!(
schema_type,
SchemaTypeSet::Single(SchemaType::Number) | SchemaTypeSet::Single(SchemaType::Integer)
) {
let mut parts = Vec::<String>::new();
let is_float = matches!(schema_type, SchemaTypeSet::Single(SchemaType::Number));
if schema.multiple_of.is_some() {
}
if let Some(exclusive_min) = schema
.exclusive_minimum
.as_ref()
.map(|v| format!("exclusive_min = {}", Self::render_number(is_float, v)))
{
parts.push(exclusive_min);
}
if let Some(exclusive_max) = schema
.exclusive_maximum
.as_ref()
.map(|v| format!("exclusive_max = {}", Self::render_number(is_float, v)))
{
parts.push(exclusive_max);
}
if let Some(min) = schema
.minimum
.as_ref()
.map(|v| format!("min = {}", Self::render_number(is_float, v)))
{
parts.push(min);
}
if let Some(max) = schema
.maximum
.as_ref()
.map(|v| format!("max = {}", Self::render_number(is_float, v)))
{
parts.push(max);
}
if !parts.is_empty() {
attrs.push(format!("range({})", parts.join(", ")));
}
}
if matches!(schema_type, SchemaTypeSet::Single(SchemaType::String)) && schema.enum_values.is_empty() {
let is_non_string_format = schema
.format
.as_ref()
.map(|f| matches!(f.as_str(), "date" | "date-time" | "time" | "binary" | "byte" | "uuid"))
.unwrap_or(false);
if !is_non_string_format {
if let (Some(min), Some(max)) = (schema.min_length, schema.max_length) {
attrs.push(format!("length(min = {min}, max = {max})"));
} else if let Some(min) = schema.min_length {
attrs.push(format!("length(min = {min})"));
} else if let Some(max) = schema.max_length {
attrs.push(format!("length(max = {max})"));
} else if is_required {
attrs.push("length(min = 1)".to_string());
}
}
}
if matches!(schema_type, SchemaTypeSet::Single(SchemaType::Array)) {
if let (Some(min), Some(max)) = (schema.min_items, schema.max_items) {
attrs.push(format!("length(min = {min}, max = {max})"));
} else if let Some(min) = schema.min_items {
attrs.push(format!("length(min = {min})"));
} else if let Some(max) = schema.max_items {
attrs.push(format!("length(max = {max})"));
}
}
}
attrs
}
pub fn extract_default_value(&self, schema: &ObjectSchema) -> Option<serde_json::Value> {
schema.default.clone()
}
fn convert_fields_with_exclusions(
&self,
schema: &ObjectSchema,
exclude_field: Option<&str>,
) -> anyhow::Result<Vec<FieldDef>> {
let mut fields = Vec::new();
let mut properties: Vec<_> = schema.properties.iter().collect();
properties.sort_by(|(a, _), (b, _)| a.cmp(b));
for (prop_name, prop_schema_ref) in properties {
if let Some(exclude) = exclude_field
&& prop_name == exclude
{
continue;
}
let rust_type = if let ObjectOrReference::Ref { ref_path, .. } = prop_schema_ref {
if let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path) {
TypeRef::new(to_rust_type_name(&ref_name))
} else {
if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
self.schema_to_type_ref(&prop_schema)?
} else {
TypeRef::new("serde_json::Value")
}
}
} else {
if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
self.schema_to_type_ref(&prop_schema)?
} else {
TypeRef::new("serde_json::Value")
}
};
let is_required = schema.required.contains(prop_name);
let optional = !is_required;
let mut serde_attrs = vec![];
let rust_field_name = to_rust_field_name(prop_name);
if rust_field_name != *prop_name {
serde_attrs.push(format!("rename = \"{}\"", prop_name));
}
if optional || rust_type.nullable {
serde_attrs.push("skip_serializing_if = \"Option::is_none\"".to_string());
}
let (
docs,
validation_attrs,
regex_validation,
default_value,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
) = if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
let docs = prop_schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default();
let validation = self.extract_validation_attrs(prop_name, is_required, &prop_schema);
let regex_validation = self.extract_validation_pattern(prop_name, &prop_schema);
let default = self.extract_default_value(&prop_schema);
let read_only = prop_schema.read_only.unwrap_or(false);
let write_only = prop_schema.write_only.unwrap_or(false);
let deprecated = prop_schema.deprecated.unwrap_or(false);
let multiple_of = prop_schema.multiple_of.clone();
let unique_items = prop_schema.unique_items.unwrap_or(false);
(
docs,
validation,
regex_validation.cloned(),
default,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
)
} else {
(vec![], vec![], None, None, false, false, false, None, false)
};
let is_nullable = rust_type.nullable;
let final_type = if optional && !is_nullable {
rust_type.with_option()
} else {
rust_type
};
fields.push(FieldDef {
name: to_rust_field_name(prop_name),
docs,
rust_type: final_type,
optional: optional || is_nullable,
serde_attrs,
validation_attrs,
regex_validation,
default_value,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
});
}
Ok(fields)
}
fn convert_fields(&self, schema: &ObjectSchema) -> anyhow::Result<Vec<FieldDef>> {
self.convert_fields_with_exclusions(schema, None)
}
fn convert_fields_with_inline_types(
&self,
parent_name: &str,
schema: &ObjectSchema,
) -> anyhow::Result<(Vec<FieldDef>, Vec<RustType>)> {
let mut fields = Vec::new();
let mut inline_types = Vec::new();
let mut properties: Vec<_> = schema.properties.iter().collect();
properties.sort_by(|(a, _), (b, _)| a.cmp(b));
for (prop_name, prop_schema_ref) in properties {
let rust_type = if let ObjectOrReference::Ref { ref_path, .. } = prop_schema_ref {
if let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path) {
TypeRef::new(to_rust_type_name(&ref_name))
} else {
if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
self.schema_to_type_ref(&prop_schema)?
} else {
TypeRef::new("serde_json::Value")
}
}
} else {
if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
let has_null = prop_schema.any_of.iter().any(|v| {
if let Ok(resolved) = v.resolve(self.graph.spec()) {
resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null))
} else {
false
}
});
if !prop_schema.any_of.is_empty() && has_null && prop_schema.any_of.len() == 2 {
let mut found_type = None;
for variant_ref in &prop_schema.any_of {
if let ObjectOrReference::Ref { ref_path, .. } = variant_ref {
if let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path) {
found_type = Some(TypeRef::new(to_rust_type_name(&ref_name)).with_option());
break;
}
} else if let Ok(resolved) = variant_ref.resolve(self.graph.spec())
&& resolved.schema_type != Some(SchemaTypeSet::Single(SchemaType::Null))
{
found_type = Some(self.schema_to_type_ref(&resolved)?.with_option());
break;
}
}
found_type.unwrap_or_else(|| self.schema_to_type_ref(&prop_schema).unwrap().with_option())
} else if !prop_schema.any_of.is_empty()
&& (prop_schema.title.is_none()
|| prop_schema
.title
.as_ref()
.map(|t| self.graph.get_schema(t).is_none())
.unwrap_or(true))
{
let enum_name = format!("{}{}", parent_name, to_rust_type_name(prop_name));
let enum_type = self.convert_any_of_enum(&enum_name, &prop_schema)?;
inline_types.push(enum_type);
TypeRef::new(to_rust_type_name(&enum_name))
} else {
self.schema_to_type_ref(&prop_schema)?
}
} else {
TypeRef::new("serde_json::Value")
}
};
let is_required = schema.required.contains(prop_name);
let optional = !is_required;
let mut serde_attrs = vec![];
let rust_field_name = to_rust_field_name(prop_name);
if rust_field_name != *prop_name {
serde_attrs.push(format!("rename = \"{}\"", prop_name));
}
if optional || rust_type.nullable {
serde_attrs.push("skip_serializing_if = \"Option::is_none\"".to_string());
}
let (
docs,
validation_attrs,
regex_validation,
default_value,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
) = if let Ok(prop_schema) = prop_schema_ref.resolve(self.graph.spec()) {
let docs = prop_schema
.description
.as_ref()
.map(|d| doc_comment_lines(d))
.unwrap_or_default();
let required = schema.required.contains(prop_name);
let validation = self.extract_validation_attrs(prop_name, required, &prop_schema);
let regex_validation = self.extract_validation_pattern(prop_name, &prop_schema);
let default = self.extract_default_value(&prop_schema);
let read_only = prop_schema.read_only.unwrap_or(false);
let write_only = prop_schema.write_only.unwrap_or(false);
let deprecated = prop_schema.deprecated.unwrap_or(false);
let multiple_of = prop_schema.multiple_of.clone();
let unique_items = prop_schema.unique_items.unwrap_or(false);
(
docs,
validation,
regex_validation.cloned(),
default,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
)
} else {
(vec![], vec![], None, None, false, false, false, None, false)
};
let is_nullable = rust_type.nullable;
let final_type = if optional && !is_nullable {
rust_type.with_option()
} else {
rust_type
};
fields.push(FieldDef {
name: to_rust_field_name(prop_name),
docs,
rust_type: final_type,
optional: optional || is_nullable,
serde_attrs,
validation_attrs,
regex_validation,
default_value,
read_only,
write_only,
deprecated,
multiple_of,
unique_items,
});
}
Ok((fields, inline_types))
}
pub fn schema_to_type_ref(&self, schema: &ObjectSchema) -> anyhow::Result<TypeRef> {
if let Some(ref schema_type) = schema.schema_type {
if !matches!(schema_type, SchemaTypeSet::Single(SchemaType::Object)) {
} else if let Some(ref title) = schema.title
&& self.graph.get_schema(title).is_some()
&& !schema.properties.is_empty()
{
let is_cyclic = self.graph.is_cyclic(title);
let mut type_ref = TypeRef::new(to_rust_type_name(title));
if is_cyclic {
type_ref = type_ref.with_boxed();
}
return Ok(type_ref);
}
} else if let Some(ref title) = schema.title
&& self.graph.get_schema(title).is_some()
&& !schema.properties.is_empty()
{
let is_cyclic = self.graph.is_cyclic(title);
let mut type_ref = TypeRef::new(to_rust_type_name(title));
if is_cyclic {
type_ref = type_ref.with_boxed();
}
return Ok(type_ref);
}
if !schema.one_of.is_empty() || !schema.any_of.is_empty() {
let variants = if !schema.one_of.is_empty() {
&schema.one_of
} else {
&schema.any_of
};
let has_null = variants.iter().any(|v| {
if let Ok(resolved) = v.resolve(self.graph.spec()) {
resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null))
} else {
false
}
});
if has_null && variants.len() == 2 {
for variant_ref in variants {
if let ObjectOrReference::Ref { ref_path, .. } = variant_ref
&& let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path)
{
return Ok(TypeRef::new(to_rust_type_name(&ref_name)).with_option());
}
if let Ok(resolved) = variant_ref.resolve(self.graph.spec()) {
if resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null)) {
continue;
}
let inner_type = self.schema_to_type_ref(&resolved)?;
return Ok(inner_type.with_option());
}
}
}
let mut fallback_type: Option<TypeRef> = None;
for variant_ref in variants {
if let ObjectOrReference::Ref { ref_path, .. } = variant_ref
&& let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path)
{
return Ok(TypeRef::new(to_rust_type_name(&ref_name)));
}
if let Ok(resolved) = variant_ref.resolve(self.graph.spec()) {
if resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::Null)) {
continue;
}
if resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::Array)) {
let unique_items = resolved.unique_items.unwrap_or(false);
if let Some(ref items_box) = resolved.items
&& let Schema::Object(items_ref) = items_box.as_ref()
&& let Ok(items_schema) = items_ref.resolve(self.graph.spec())
{
if !items_schema.one_of.is_empty() {
for one_of_ref in &items_schema.one_of {
if let ObjectOrReference::Ref { ref_path, .. } = one_of_ref
&& let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path)
{
return Ok(
TypeRef::new(to_rust_type_name(&ref_name))
.with_vec()
.with_unique_items(unique_items),
);
}
}
}
}
}
if resolved.schema_type == Some(SchemaTypeSet::Single(SchemaType::String)) && fallback_type.is_none() {
fallback_type = Some(TypeRef::new("String"));
continue;
}
if !resolved.one_of.is_empty() {
for nested_ref in &resolved.one_of {
if let ObjectOrReference::Ref { ref_path, .. } = nested_ref
&& let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path)
{
return Ok(TypeRef::new(to_rust_type_name(&ref_name)));
}
}
}
if let Some(ref variant_title) = resolved.title
&& self.graph.get_schema(variant_title).is_some()
{
return Ok(TypeRef::new(to_rust_type_name(variant_title)));
}
}
}
if let Some(t) = fallback_type {
return Ok(t);
}
}
if let Some(ref schema_type) = schema.schema_type {
match schema_type {
SchemaTypeSet::Single(typ) => {
let base_type = match typ {
SchemaType::String => {
if let Some(ref format) = schema.format {
match format.as_str() {
"date" => "chrono::NaiveDate",
"date-time" => "chrono::DateTime<chrono::Utc>",
"time" => "chrono::NaiveTime",
"binary" => "Vec<u8>", "byte" => "String", "uuid" => "uuid::Uuid", _ => "String",
}
} else {
"String"
}
}
SchemaType::Number => "f64",
SchemaType::Integer => "i64",
SchemaType::Boolean => "bool",
SchemaType::Array => {
let unique_items = schema.unique_items.unwrap_or(false);
if let Some(ref items_box) = schema.items
&& let Schema::Object(items_ref) = items_box.as_ref()
{
if let ObjectOrReference::Ref { ref_path, .. } = items_ref.as_ref() {
if let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path) {
return Ok(
TypeRef::new(to_rust_type_name(&ref_name))
.with_vec()
.with_unique_items(unique_items),
);
}
}
if let Ok(items_schema) = items_ref.resolve(self.graph.spec()) {
if !items_schema.one_of.is_empty() {
for one_of_ref in &items_schema.one_of {
if let ObjectOrReference::Ref { ref_path, .. } = one_of_ref
&& let Some(ref_name) = SchemaGraph::extract_ref_name(ref_path)
{
return Ok(
TypeRef::new(to_rust_type_name(&ref_name))
.with_vec()
.with_unique_items(unique_items),
);
}
}
}
let item_type = self.schema_to_type_ref(&items_schema)?;
return Ok(
TypeRef::new(item_type.to_rust_type())
.with_vec()
.with_unique_items(unique_items),
);
}
}
return Ok(
TypeRef::new("serde_json::Value")
.with_vec()
.with_unique_items(unique_items),
);
}
SchemaType::Object => {
return Ok(TypeRef::new("serde_json::Value"));
}
SchemaType::Null => {
return Ok(TypeRef::new("()").with_option());
}
};
return Ok(TypeRef::new(base_type));
}
SchemaTypeSet::Multiple(_) => {
return Ok(TypeRef::new("serde_json::Value"));
}
}
}
Ok(TypeRef::new("serde_json::Value"))
}
}