use std::borrow::Cow;
use std::marker::PhantomData;
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::parse_quote;
use crate::feature::Feature;
use crate::schema_type::SchemaType;
use crate::{Array, DiagResult, TryToTokens};
pub(crate) trait Variant {
fn to_tokens(&self) -> TokenStream;
fn get_type(&self) -> (TokenStream, TokenStream) {
let schema_type = SchemaType {
path: &parse_quote!(str),
nullable: false,
};
(
match schema_type.try_to_token_stream() {
Ok(tokens) => tokens,
Err(diag) => diag.emit_as_item_tokens(),
},
quote! {&str},
)
}
}
pub(crate) struct SimpleEnumVariant<T: ToTokens> {
pub(crate) value: T,
}
impl<T> Variant for SimpleEnumVariant<T>
where
T: ToTokens,
{
fn to_tokens(&self) -> TokenStream {
self.value.to_token_stream()
}
}
cfg_feature! {
#![feature ="repr"]
pub(crate) struct ReprVariant<'r, T: ToTokens> {
pub(crate) value: T,
pub(crate) type_path: &'r syn::TypePath,
}
impl<T> Variant for ReprVariant<'_, T>
where
T: ToTokens,
{
fn to_tokens(&self) -> TokenStream {
self.value.to_token_stream()
}
fn get_type(&self) -> (TokenStream, TokenStream) {
let schema_type = SchemaType {
path: &self.type_path.path,
nullable: false,
};
(
match schema_type.try_to_token_stream() {
Ok(stream) => stream,
Err(diag) => diag.emit_as_item_tokens(),
},
self.type_path.to_token_stream(),
)
}
}
}
pub(crate) struct ObjectVariant<'o, T: ToTokens> {
pub(crate) item: T,
pub(crate) name: Cow<'o, str>,
pub(crate) title: Option<TokenStream>,
pub(crate) example: Option<TokenStream>,
}
impl<T> Variant for ObjectVariant<'_, T>
where
T: ToTokens,
{
fn to_tokens(&self) -> TokenStream {
let oapi = crate::oapi_crate();
let name = &self.name;
let title = &self.title;
let example = &self.example;
let variant = &self.item;
quote! {
#oapi::oapi::schema::Object::new()
#title
#example
.property(#name, #variant)
.required(#name)
}
}
}
pub(crate) struct Enum<'e, V: Variant> {
pub(crate) title: Option<TokenStream>,
pub(crate) example: Option<TokenStream>,
len: usize,
items: Array<'e, TokenStream>,
schema_type: TokenStream,
enum_type: TokenStream,
description: Option<TokenStream>,
_p: PhantomData<V>,
}
impl<V: Variant> Enum<'_, V> {
pub(crate) fn new<I: IntoIterator<Item = V>>(items: I) -> Self {
items.into_iter().collect()
}
pub(crate) fn title<I: Into<TokenStream>>(mut self, title: I) -> Self {
self.title = Some(title.into());
self
}
pub(crate) fn example<I: Into<TokenStream>>(mut self, example: I) -> Self {
self.example = Some(example.into());
self
}
pub(crate) fn description<I: Into<TokenStream>>(mut self, description: I) -> Self {
self.description = Some(description.into());
self
}
}
impl<T> ToTokens for Enum<'_, T>
where
T: Variant,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
let len = &self.len;
let title = &self.title;
let example = &self.example;
let items = &self.items;
let schema_type = &self.schema_type;
let enum_type = &self.enum_type;
let description = &self.description;
tokens.extend(quote! {
#oapi::oapi::Object::new()
#title
#description
#example
.schema_type(#schema_type)
.enum_values::<[#enum_type; #len], #enum_type>(#items)
})
}
}
impl<V: Variant> FromIterator<V> for Enum<'_, V> {
fn from_iter<T: IntoIterator<Item = V>>(iter: T) -> Self {
let mut len = 0;
let mut schema_type: TokenStream = quote! {};
let mut enum_type: TokenStream = quote! {};
let items = iter
.into_iter()
.enumerate()
.map(|(index, variant)| {
if index == 0 {
(schema_type, enum_type) = variant.get_type();
}
len = index + 1;
variant.to_tokens()
})
.collect::<Array<TokenStream>>();
Self {
title: None,
example: None,
description: None,
len,
items,
schema_type,
enum_type,
_p: PhantomData,
}
}
}
pub(crate) struct TaggedEnum<T: Variant> {
items: TokenStream,
len: usize,
_p: PhantomData<T>,
}
impl<V: Variant> TaggedEnum<V> {
pub(crate) fn new<'t, I: IntoIterator<Item = (Cow<'t, str>, V)>>(items: I) -> Self {
items.into_iter().collect()
}
}
impl<T> ToTokens for TaggedEnum<T>
where
T: Variant,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
let len = &self.len;
let items = &self.items;
tokens.extend(quote! {
Into::<#oapi::oapi::schema::OneOf>::into(#oapi::oapi::schema::OneOf::with_capacity(#len))
#items
})
}
}
impl<'t, V: Variant> FromIterator<(Cow<'t, str>, V)> for TaggedEnum<V> {
fn from_iter<T: IntoIterator<Item = (Cow<'t, str>, V)>>(iter: T) -> Self {
let mut len = 0;
let items = iter
.into_iter()
.enumerate()
.map(|(index, (tag, variant))| {
let oapi = crate::oapi_crate();
len = index + 1;
let (schema_type, enum_type) = variant.get_type();
let item = variant.to_tokens();
quote! {
.item(
#oapi::oapi::schema::Object::new()
.property(
#tag,
#oapi::oapi::schema::Object::new()
.schema_type(#schema_type)
.enum_values::<[#enum_type; 1], #enum_type>([#item])
)
.required(#tag)
)
}
})
.collect::<TokenStream>();
Self {
items,
len,
_p: PhantomData,
}
}
}
pub(crate) struct UntaggedEnum {
title: Option<Feature>,
}
impl UntaggedEnum {
pub(crate) fn new() -> Self {
Self { title: None }
}
pub(crate) fn with_title(title: Option<Feature>) -> Self {
Self { title }
}
}
impl TryToTokens for UntaggedEnum {
fn try_to_tokens(&self, tokens: &mut TokenStream) -> DiagResult<()> {
let oapi = crate::oapi_crate();
let title = self
.title
.as_ref()
.map(|f| f.try_to_token_stream())
.transpose()?;
tokens.extend(quote! {
#oapi::oapi::schema::Object::new()
.schema_type(#oapi::oapi::schema::BasicType::Null)
.default_value(#oapi::oapi::__private::serde_json::Value::Null)
#title
});
Ok(())
}
}
pub(crate) struct AdjacentlyTaggedEnum<T: Variant> {
items: TokenStream,
len: usize,
_p: PhantomData<T>,
}
impl<V: Variant> AdjacentlyTaggedEnum<V> {
pub(crate) fn new<'t, I: IntoIterator<Item = (Cow<'t, str>, Cow<'t, str>, V)>>(
items: I,
) -> Self {
items.into_iter().collect()
}
}
impl<T> ToTokens for AdjacentlyTaggedEnum<T>
where
T: Variant,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
let len = &self.len;
let items = &self.items;
tokens.extend(quote! {
Into::<#oapi::oapi::schema::OneOf>::into(#oapi::oapi::schema::OneOf::with_capacity(#len))
#items
})
}
}
impl<'t, V: Variant> FromIterator<(Cow<'t, str>, Cow<'t, str>, V)> for AdjacentlyTaggedEnum<V> {
fn from_iter<T: IntoIterator<Item = (Cow<'t, str>, Cow<'t, str>, V)>>(iter: T) -> Self {
let oapi = crate::oapi_crate();
let mut len = 0;
let items = iter
.into_iter()
.enumerate()
.map(|(index, (tag, content, variant))| {
len = index + 1;
let (schema_type, enum_type) = variant.get_type();
let item = variant.to_tokens();
quote! {
.item(
#oapi::oapi::schema::Object::new()
.property(
#tag,
#oapi::oapi::schema::Object::new()
.schema_type(#oapi::oapi::schema::BasicType::String)
.enum_values::<[#enum_type; 1], #enum_type>([#content])
)
.required(#tag)
.property(
#content,
#oapi::oapi::schema::Object::new()
.schema_type(#schema_type)
.enum_values::<[#enum_type; 1], #enum_type>([#item])
)
.required(#content)
)
}
})
.collect::<TokenStream>();
Self {
items,
len,
_p: PhantomData,
}
}
}
pub(crate) struct CustomEnum<'c, T: ToTokens> {
items: T,
pub(crate) tag: Option<Cow<'c, str>>,
}
impl<'c, T: ToTokens> CustomEnum<'c, T> {
pub(crate) fn discriminator(mut self, discriminator: Cow<'c, str>) -> Self {
self.tag = Some(discriminator);
self
}
}
impl<T> ToTokens for CustomEnum<'_, T>
where
T: ToTokens,
{
fn to_tokens(&self, tokens: &mut TokenStream) {
let oapi = crate::oapi_crate();
self.items.to_tokens(tokens);
let discriminator = self.tag.as_ref().map(|tag| {
quote! {
.discriminator(#oapi::oapi::schema::Discriminator::new(#tag))
}
});
tokens.extend(quote! {
#discriminator
});
}
}
impl FromIterator<TokenStream> for CustomEnum<'_, TokenStream> {
fn from_iter<T: IntoIterator<Item = TokenStream>>(iter: T) -> Self {
let oapi = crate::oapi_crate();
let mut len = 0;
let items = iter
.into_iter()
.enumerate()
.map(|(index, variant)| {
len = index + 1;
quote! {
.item(
#variant
)
}
})
.collect::<TokenStream>();
let mut tokens = TokenStream::new();
tokens.extend(quote! {
Into::<#oapi::oapi::schema::OneOf>::into(#oapi::oapi::schema::OneOf::with_capacity(#len))
#items
});
CustomEnum {
items: tokens,
tag: None,
}
}
}