openapi_type_derive 0.4.2

Implementation detail of the openapi_type crate
Documentation
use crate::parser::{ParseData, ParseDataField, ParseDataType, TypeOrInline};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{spanned::Spanned, LitStr};
use syn_path::path;

fn gen_doc_option(doc: &[String]) -> TokenStream {
	let doc = doc.join("\n");
	let doc = doc.trim();
	if doc.is_empty() {
		quote!(::core::option::Option::None)
	} else {
		quote!(::core::option::Option::Some(#doc))
	}
}

impl ParseData {
	pub(super) fn gen_visit_impl(&self) -> TokenStream {
		let name = self.name.as_ref();
		let doc = &self.doc;
		match &self.ty {
			ParseDataType::Struct {
				fields,
				deny_unknown_fields
			} => gen_struct(name, doc, fields, *deny_unknown_fields),
			ParseDataType::Enum { variants } => gen_enum(name, doc, variants),
			ParseDataType::Alternatives { alts } => gen_alt(name, doc, alts),
			ParseDataType::Unit => gen_unit(name, doc)
		}
	}
}

impl TypeOrInline {
	fn visit_type_fn(&self) -> TokenStream {
		match self {
			Self::Type(ty) => {
				quote_spanned!(ty.span() => <#ty as ::openapi_type::OpenapiType>::visit_type)
			},
			Self::Inline(data) => {
				let visit_impl = data.gen_visit_impl();
				quote! {
					{
						fn visit_type<__openapi_type_V>(visitor: &mut __openapi_type_V)
						where
							__openapi_type_V: ::openapi_type::Visitor
						{
							#visit_impl
						}
						visit_type
					}
				}
			}
		}
	}
}

fn gen_struct(name: Option<&LitStr>, doc: &[String], fields: &[ParseDataField], deny_unknown_fields: bool) -> TokenStream {
	let str = path!(::core::primitive::str);
	let string = path!(::std::string::String);
	let option = path!(::core::option::Option);

	let name = match name {
		Some(name) => quote!(#option::Some(#name)),
		None => quote!(#option::None)
	};
	let doc = gen_doc_option(doc);

	let fields = fields.iter().map(|f| {
		let name = &f.name;
		let doc = gen_doc_option(&f.doc);
		let visit = f.ty.visit_type_fn();

		if f.flatten {
			quote!({
				let visitor = ::openapi_type::ObjectVisitor::visit_flatten_field(object_visitor);
				let field_visit_type = #visit;
				field_visit_type(visitor)
			})
		} else {
			quote!({
				const FIELD_NAME: &#str = #name;
				const FIELD_DOC: #option<&'static #str> = #doc;

				let field_visit_type = #visit;
				let field_visitor = ::openapi_type::ObjectVisitor::visit_field(
					object_visitor,
					#string::from(FIELD_NAME),
					FIELD_DOC.map(#string::from)
				);
				field_visit_type(field_visitor);
			})
		}
	});

	quote! {
		const OBJECT_NAME: #option<&'static #str> = #name;
		const OBJECT_DOC: #option<&'static #str> = #doc;

		let object_visitor = ::openapi_type::Visitor::visit_object(visitor);

		if let #option::Some(object_name) = OBJECT_NAME {
			::openapi_type::ObjectVisitor::visit_name(
				object_visitor,
				#string::from(object_name)
			);
		}
		if let #option::Some(object_doc) = OBJECT_DOC {
			::openapi_type::ObjectVisitor::visit_description(
				object_visitor,
				#string::from(object_doc)
			);
		}
		if #deny_unknown_fields {
			::openapi_type::ObjectVisitor::visit_deny_additional(object_visitor);
		}

		#(#fields)*
	}
}

fn gen_enum(name: Option<&LitStr>, doc: &[String], variants: &[LitStr]) -> TokenStream {
	let str = path!(::core::primitive::str);
	let string = path!(::std::string::String);
	let option = path!(::core::option::Option);

	let name = match name {
		Some(name) => quote!(#option::Some(#name)),
		None => quote!(#option::None)
	};
	let doc = gen_doc_option(doc);

	quote! {
		const ENUM_NAME: #option<&'static #str> = #name;
		const ENUM_DOC: #option<&'static #str> = #doc;

		::openapi_type::Visitor::visit_enum(
			visitor,
			ENUM_NAME.map(#string::from),
			ENUM_DOC.map(#string::from),
			[#(#string::from(#variants)),*]
		);
	}
}

fn gen_alt(name: Option<&LitStr>, doc: &[String], alt: &[TypeOrInline]) -> TokenStream {
	let str = path!(::core::primitive::str);
	let string = path!(::std::string::String);
	let option = path!(::core::option::Option);

	let name = match name {
		Some(name) => quote!(#option::Some(#name)),
		None => quote!(#option::None)
	};
	let doc = gen_doc_option(doc);

	let fns = alt.into_iter().map(|alt| alt.visit_type_fn());
	quote! {
		const OBJECT_NAME: #option<&'static #str> = #name;
		const OBJECT_DOC: #option<&'static #str> = #doc;

		let alt_visitor = ::openapi_type::Visitor::visit_alternatives(visitor);

		if let #option::Some(object_name) = OBJECT_NAME {
			::openapi_type::AlternativesVisitor::visit_name(
				alt_visitor,
				#string::from(object_name)
			);
		}
		if let #option::Some(object_doc) = OBJECT_DOC {
			::openapi_type::AlternativesVisitor::visit_description(
				alt_visitor,
				#string::from(object_doc)
			);
		}

		#({
			let visitor = ::openapi_type::AlternativesVisitor::visit_alternative(alt_visitor);
			let visit_type_fn = #fns;
			visit_type_fn(visitor);
		})*
	}
}

fn gen_unit(name: Option<&LitStr>, doc: &[String]) -> TokenStream {
	let str = path!(::core::primitive::str);
	let string = path!(::std::string::String);
	let option = path!(::core::option::Option);

	let name = match name {
		Some(name) => quote!(#option::Some(#name)),
		None => quote!(#option::None)
	};
	let doc = gen_doc_option(doc);

	quote! {
		const OBJECT_NAME: #option<&'static #str> = #name;
		const OBJECT_DOC: #option<&'static #str> = #doc;

		let option_visitor = ::openapi_type::Visitor::visit_unit_struct(
			visitor,
			OBJECT_NAME.map(#string::from),
			OBJECT_DOC.map(#string::from)
		);
	}
}