use super::{
Container, Field, TokenStream2, Variant, close, field_name, is_required, quote,
shape::member_schema, skip_value, string_value, variant_name,
};
pub(super) fn read_names(field: &Field, container: &Container) -> Vec<String> {
names(field_name(field, container), &field.attrs)
}
pub(super) fn variant_names(variant: &Variant, container: &Container) -> Vec<String> {
names(variant_name(variant, container), &variant.attrs)
}
pub(super) fn variants_read_names(
variants: &[&Variant],
container: &Container,
) -> Vec<Vec<String>> {
let mut claimed: Vec<String> = Vec::new();
variants
.iter()
.map(|variant| {
let read: Vec<String> = variant_names(variant, container)
.into_iter()
.filter(|name| !claimed.contains(name))
.collect();
claimed.extend(read.iter().cloned());
read
})
.collect()
}
pub(super) fn shadowed_variant<'a>(
variants: &[&'a Variant],
container: &Container,
) -> Option<(&'a Variant, &'a Variant)> {
variants.iter().enumerate().find_map(|(index, later)| {
let wire = variant_name(later, container);
variants[..index]
.iter()
.find(|earlier| variant_names(earlier, container).contains(&wire))
.map(|earlier| (*later, *earlier))
})
}
fn names(wire: String, attrs: &[syn::Attribute]) -> Vec<String> {
let mut names = vec![wire];
for attr in attrs {
if !attr.path().is_ident("serde") {
continue;
}
let _ = attr.parse_nested_meta(|meta| {
if !meta.path.is_ident("alias") {
return skip_value(&meta);
}
if let Some(alias) = string_value(&meta)?.filter(|alias| !names.contains(alias)) {
names.push(alias);
}
Ok(())
});
}
names
}
pub(super) fn property(field: &Field, container: &Container) -> TokenStream2 {
let names = read_names(field, container);
let schema = member_schema(field);
let required = is_required(field, container);
if let [wire] = names.as_slice() {
let require = required.then(|| quote!(required.push(::std::string::String::from(#wire));));
return quote! {
keywords.properties.insert(::std::string::String::from(#wire), #schema);
#require
};
}
let bound = if required {
exactly_one(&names)
} else {
at_most_one(&names)
};
quote! {
{
let schema = #schema;
#(
keywords
.properties
.insert(::std::string::String::from(#names), ::core::clone::Clone::clone(&schema));
)*
let bound = #bound;
keywords
.all_of
.get_or_insert_with(::std::vec::Vec::new)
.push(bound);
}
}
}
pub(super) fn named_string(names: &[String]) -> TokenStream2 {
let value = if let [name] = names {
quote!(constant.const_value = ::core::option::Option::Some(::core::convert::Into::into(#name));)
} else {
quote! {
constant.enumeration = ::core::option::Option::Some(::std::vec![
#(::core::convert::Into::into(#names)),*
]);
}
};
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,
),
);
#value
::kynos::openapi::Schema::Object(::std::boxed::Box::new(constant))
}
}
}
pub(super) fn keyed(names: &[String], payload: &TokenStream2) -> TokenStream2 {
let bound = if let [name] = names {
quote! {
keywords.required =
::core::option::Option::Some(::std::vec![::std::string::String::from(#name)]);
}
} else {
let exactly = exactly_one(names);
quote!(keywords.all_of = ::core::option::Option::Some(::std::vec![#exactly]);)
};
close("e! {
{
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 payload = #payload;
#(
keywords
.properties
.insert(::std::string::String::from(#names), ::core::clone::Clone::clone(&payload));
)*
#bound
::kynos::openapi::Schema::Object(::std::boxed::Box::new(keywords))
}
})
}
fn exactly_one(names: &[String]) -> TokenStream2 {
let branches = names.iter().map(|name| requiring(&[name]));
quote! {
{
let mut exactly = ::kynos::openapi::SchemaObject::default();
exactly.one_of = ::core::option::Option::Some(::std::vec![#(#branches),*]);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(exactly))
}
}
}
fn at_most_one(names: &[String]) -> TokenStream2 {
let pairs: Vec<TokenStream2> = names
.iter()
.enumerate()
.flat_map(|(index, first)| {
names[index + 1..]
.iter()
.map(move |second| requiring(&[first, second]))
})
.collect();
let refused = if let [pair] = pairs.as_slice() {
pair.clone()
} else {
quote! {
{
let mut either = ::kynos::openapi::SchemaObject::default();
either.any_of = ::core::option::Option::Some(::std::vec![#(#pairs),*]);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(either))
}
}
};
quote! {
{
let mut apart = ::kynos::openapi::SchemaObject::default();
apart.not = ::core::option::Option::Some(::std::boxed::Box::new(#refused));
::kynos::openapi::Schema::Object(::std::boxed::Box::new(apart))
}
}
}
fn requiring(names: &[&String]) -> TokenStream2 {
quote! {
{
let mut present = ::kynos::openapi::SchemaObject::default();
present.required = ::core::option::Option::Some(
::std::vec![#(::std::string::String::from(#names)),*],
);
::kynos::openapi::Schema::Object(::std::boxed::Box::new(present))
}
}
}