use heck::ToPascalCase;
use openapiv3::{
AdditionalProperties, IntegerFormat, NumberFormat, ObjectType, ReferenceOr, Schema, SchemaKind,
StringFormat, Type,
};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
#[must_use]
pub fn schema_to_rust_type(ref_or: &ReferenceOr<Schema>, required: bool) -> TokenStream {
let mut sink: Vec<TokenStream> = Vec::new();
schema_to_rust_type_ctx(ref_or, required, None, &mut sink)
}
#[must_use]
pub fn schema_to_rust_type_ctx(
ref_or: &ReferenceOr<Schema>,
required: bool,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> TokenStream {
let inner = ref_or_to_inner_type_ctx(ref_or, parent_name, inline_types);
if required {
inner
} else {
quote! { ::core::option::Option<#inner> }
}
}
fn ref_or_to_inner_type_ctx(
ref_or: &ReferenceOr<Schema>,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> TokenStream {
match ref_or {
ReferenceOr::Reference { reference } => ref_to_ident(reference),
ReferenceOr::Item(schema) => schema_kind_to_type(schema, parent_name, inline_types),
}
}
#[must_use]
pub fn ref_to_ident(reference: &str) -> TokenStream {
let name = reference.rsplit('/').next().unwrap_or(reference);
let ident = format_ident!("{}", name.to_pascal_case());
quote! { #ident }
}
fn schema_kind_to_type(
schema: &Schema,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> TokenStream {
match &schema.schema_kind {
SchemaKind::Type(Type::Object(obj)) => {
object_schema_to_type(schema, obj, parent_name, inline_types)
}
SchemaKind::Type(t) => primitive_type_to_rust(t, parent_name, inline_types),
SchemaKind::OneOf { one_of } => {
synthesize_inline_composition(parent_name, inline_types, |name, sink| {
super::compositions::generate_one_of(
name,
one_of,
schema.schema_data.discriminator.as_ref(),
schema.schema_data.description.as_ref(),
sink,
)
})
}
SchemaKind::AnyOf { any_of } => {
synthesize_inline_composition(parent_name, inline_types, |name, sink| {
super::compositions::generate_any_of(
name,
any_of,
schema.schema_data.description.as_ref(),
sink,
)
})
}
SchemaKind::AllOf { all_of } => {
synthesize_inline_composition(parent_name, inline_types, |name, sink| {
super::compositions::generate_all_of(
name,
all_of,
schema.schema_data.description.as_ref(),
sink,
)
})
}
SchemaKind::Not { .. } | SchemaKind::Any(_) => {
quote! { ::serde_json::Value }
}
}
}
fn synthesize_inline_composition(
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
generate: impl FnOnce(&str, &mut Vec<TokenStream>) -> TokenStream,
) -> TokenStream {
parent_name.map_or_else(
|| quote! { ::serde_json::Value },
|name| {
let tokens = generate(name, inline_types);
inline_types.push(tokens);
let ident = format_ident!("{}", name.to_pascal_case());
quote! { #ident }
},
)
}
fn primitive_type_to_rust(
t: &Type,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> TokenStream {
match t {
Type::Integer(i) => {
if i.format == openapiv3::VariantOrUnknownOrEmpty::Item(IntegerFormat::Int32) {
quote! { i32 }
} else {
quote! { i64 }
}
}
Type::Number(n) => {
if n.format == openapiv3::VariantOrUnknownOrEmpty::Item(NumberFormat::Float) {
quote! { f32 }
} else {
quote! { f64 }
}
}
Type::String(s) => {
if s.enumeration.is_empty() {
if matches!(
&s.format,
openapiv3::VariantOrUnknownOrEmpty::Item(StringFormat::Binary)
) {
quote! { ::std::vec::Vec<u8> }
} else {
quote! { ::std::string::String }
}
} else {
quote! { ::std::string::String }
}
}
Type::Boolean(_) => quote! { bool },
Type::Array(a) => {
let item_ty = a.items.as_ref().map_or_else(
|| quote! { ::serde_json::Value },
|items| ref_or_to_inner_type_ctx(&items.clone().unbox(), parent_name, inline_types),
);
quote! { ::std::vec::Vec<#item_ty> }
}
Type::Object(_) => quote! { ::serde_json::Value },
}
}
fn object_schema_to_type(
schema: &Schema,
obj: &ObjectType,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> TokenStream {
if !obj.properties.is_empty() {
return synthesize_inline_composition(parent_name, inline_types, |name, sink| {
super::schemas::generate_object_struct(name, schema, obj, sink)
});
}
if let Some(ap) = &obj.additional_properties {
if let Some(value_ty) = additional_properties_value_type(ap, parent_name, inline_types) {
return quote! {
::std::collections::HashMap<::std::string::String, #value_ty>
};
}
}
quote! { ::serde_json::Value }
}
#[must_use]
pub fn additional_properties_value_type(
ap: &AdditionalProperties,
parent_name: Option<&str>,
inline_types: &mut Vec<TokenStream>,
) -> Option<TokenStream> {
match ap {
AdditionalProperties::Any(false) => None,
AdditionalProperties::Any(true) => Some(quote! { ::serde_json::Value }),
AdditionalProperties::Schema(schema) => {
Some(ref_or_to_inner_type_ctx(schema, parent_name, inline_types))
}
}
}
#[must_use]
pub fn is_string_enum(schema: &Schema) -> bool {
if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
!s.enumeration.is_empty()
} else {
false
}
}
#[must_use]
pub fn string_enum_values(schema: &Schema) -> Vec<String> {
if let SchemaKind::Type(Type::String(s)) = &schema.schema_kind {
s.enumeration.iter().filter_map(Clone::clone).collect()
} else {
vec![]
}
}