use super::{
Comma, Container, DataEnum, Field, Fields, Punctuated, TokenStream2, Variant, constraints,
deprecate, described, doc_string, field_name, is_deprecated, is_described, is_flattened,
is_required, is_skipped, quote, variant_name,
};
pub(super) fn struct_body(fields: &Fields, container: &Container) -> TokenStream2 {
match fields {
Fields::Named(named) => object_body(&named.named, container, None),
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let ty = &unnamed.unnamed[0].ty;
quote!(registry.resolve::<#ty>())
}
Fields::Unnamed(unnamed) => tuple_body(&unnamed.unnamed),
Fields::Unit => quote! {
::kynos::openapi::Schema::of_type(
::kynos::openapi::model::schema::types::SchemaType::Null,
)
},
}
}
pub(super) fn tuple_body(fields: &Punctuated<Field, Comma>) -> TokenStream2 {
let members = fields.iter().map(|field| {
let ty = &field.ty;
quote!(registry.resolve::<#ty>())
});
quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::Array,
),
);
keywords.prefix_items =
::core::option::Option::Some(::std::vec![#(#members),*]);
keywords.items = ::core::option::Option::Some(
::std::boxed::Box::new(::kynos::openapi::Schema::never()),
);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
}
}
pub(super) fn object_body(
fields: &Punctuated<Field, Comma>,
container: &Container,
tag: Option<(&str, &str)>,
) -> TokenStream2 {
let tagged = tag.map(|(property, value)| {
let constant = constant_string(value);
quote! {
keywords.properties.insert(::std::string::String::from(#property), #constant);
required.push(::std::string::String::from(#property));
}
});
let entries = fields
.iter()
.filter(|field| is_described(field))
.map(|field| {
let ty = &field.ty;
let wire = field_name(field, container);
if is_flattened(field) {
return quote! {
keywords
.all_of
.get_or_insert_with(::std::vec::Vec::new)
.push(registry.resolve::<#ty>());
};
}
let constrained = constraints(field)
.map(|constraints| quote!(let schema = #constraints.apply(schema);));
let described = doc_string(&field.attrs).map(|doc| {
quote! {
let mut schema = schema;
if let ::kynos::openapi::Schema::Object(property) = &mut schema {
property.description =
::core::option::Option::Some(::std::string::String::from(#doc));
}
}
});
let deprecated = is_deprecated(&field.attrs).then(|| {
quote! {
let mut schema = schema;
if let ::kynos::openapi::Schema::Object(property) = &mut schema {
property.deprecated = ::core::option::Option::Some(true);
}
}
});
let require = is_required(field)
.then(|| quote!(required.push(::std::string::String::from(#wire));));
quote! {
{
let schema = registry.resolve::<#ty>();
#constrained
#described
#deprecated
keywords.properties.insert(::std::string::String::from(#wire), schema);
}
#require
}
});
quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::Object,
),
);
let mut required: ::std::vec::Vec<::std::string::String> =
::std::vec::Vec::new();
#tagged
#(#entries)*
if !required.is_empty() {
keywords.required = ::core::option::Option::Some(required);
}
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
}
}
pub(super) fn constant_string(value: &str) -> TokenStream2 {
quote! {
{
let mut constant = ::kynos::openapi::SchemaObject::default();
constant.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::String,
),
);
constant.const_value =
::core::option::Option::Some(::core::convert::Into::into(#value));
::kynos::openapi::Schema::Object(::std::boxed::Box::new(constant))
}
}
}
pub(super) fn enum_body(data: &DataEnum, container: &Container) -> TokenStream2 {
let variants: Vec<&Variant> = data
.variants
.iter()
.filter(|variant| !is_skipped(&variant.attrs))
.collect();
let any_deprecated = variants.iter().any(|variant| is_deprecated(&variant.attrs));
if container.tag.is_none()
&& !any_deprecated
&& variants
.iter()
.all(|variant| matches!(variant.fields, Fields::Unit))
{
let names = variants
.iter()
.map(|variant| variant_name(variant, container));
return quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::String,
),
);
keywords.enumeration = ::core::option::Option::Some(::std::vec![
#(::core::convert::Into::into(#names)),*
]);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
};
}
let branches = variants
.iter()
.map(|variant| branch(variant, container))
.collect::<Vec<_>>();
let discriminator = container.tag.as_ref().map(|tag| {
quote! {
keywords.discriminator = ::core::option::Option::Some(
::kynos::openapi::Discriminator::new(#tag),
);
}
});
quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.one_of = ::core::option::Option::Some(::std::vec![#(#branches),*]);
#discriminator
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
}
}
pub(super) fn branch(variant: &Variant, container: &Container) -> TokenStream2 {
let name = variant_name(variant, container);
let deprecated = is_deprecated(&variant.attrs);
let described = |schema: TokenStream2| {
deprecate(
described(schema, doc_string(&variant.attrs).as_deref()),
deprecated,
)
};
match (&container.tag, &container.content) {
(Some(tag), Some(content)) => {
let tagged = constant_string(&name);
let payload = payload(&variant.fields, container).map(|payload| {
quote! {
keywords.properties.insert(::std::string::String::from(#content), #payload);
required.push(::std::string::String::from(#content));
}
});
described(quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::Object,
),
);
let mut required: ::std::vec::Vec<::std::string::String> =
::std::vec::Vec::new();
keywords.properties.insert(::std::string::String::from(#tag), #tagged);
required.push(::std::string::String::from(#tag));
#payload
keywords.required = ::core::option::Option::Some(required);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
})
}
(Some(tag), None) => match &variant.fields {
Fields::Named(named) => {
described(object_body(&named.named, container, Some((tag, &name))))
}
Fields::Unit => described(object_body(
&Punctuated::new(),
container,
Some((tag, &name)),
)),
Fields::Unnamed(_) => {
let marker = object_body(&Punctuated::new(), container, Some((tag, &name)));
let payload = payload(&variant.fields, container);
described(quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.all_of = ::core::option::Option::Some(
::std::vec![#marker, #payload],
);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
})
}
},
(None, _) => match payload(&variant.fields, container) {
None => described(constant_string(&name)),
Some(payload) => described(quote! {
{
let mut keywords = ::kynos::openapi::SchemaObject::default();
keywords.ty = ::core::option::Option::Some(
::kynos::openapi::model::schema::types::TypeSet::One(
::kynos::openapi::model::schema::types::SchemaType::Object,
),
);
keywords.properties.insert(::std::string::String::from(#name), #payload);
keywords.required = ::core::option::Option::Some(
::std::vec![::std::string::String::from(#name)],
);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
}),
},
}
}
pub(super) fn payload(fields: &Fields, container: &Container) -> Option<TokenStream2> {
match fields {
Fields::Unit => None,
Fields::Named(named) => Some(object_body(&named.named, container, None)),
Fields::Unnamed(unnamed) if unnamed.unnamed.len() == 1 => {
let ty = &unnamed.unnamed[0].ty;
Some(quote!(registry.resolve::<#ty>()))
}
Fields::Unnamed(unnamed) => Some(tuple_body(&unnamed.unnamed)),
}
}