1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
#![warn(missing_debug_implementations, rust_2018_idioms)]
#![deny(broken_intra_doc_links)]
#![forbid(unsafe_code)]
//! This crate defines the macros for `#[derive(OpenapiType)]`.

use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, LitStr, TraitBound, TraitBoundModifier, TypeParamBound};

#[macro_use]
mod util;

mod attrs;
use attrs::*;
mod codegen;
use codegen::*;
mod parser;
use parser::*;

/// The derive macro for [OpenapiType](https://docs.rs/openapi_type/*/openapi_type/trait.OpenapiType.html).
#[proc_macro_derive(OpenapiType, attributes(openapi))]
pub fn derive_openapi_type(input: TokenStream) -> TokenStream {
	let input = parse_macro_input!(input);
	expand_openapi_type(input).unwrap_or_else(|err| err.to_compile_error()).into()
}

fn expand_openapi_type(mut input: DeriveInput) -> syn::Result<TokenStream2> {
	// parse #[serde] and #[openapi] attributes
	let mut attrs = ContainerAttributes::default();
	for attr in &input.attrs {
		if attr.path.is_ident("serde") {
			attrs.parse_from(attr, false)?;
		}
	}
	for attr in &input.attrs {
		if attr.path.is_ident("openapi") {
			attrs.parse_from(attr, true)?;
		}
	}

	// parse #[doc] attributes
	let mut doc: Vec<String> = Vec::new();
	for attr in &input.attrs {
		if attr.path.is_ident("doc") {
			if let Some(lit) = parse_doc_attr(attr)? {
				doc.push(lit.value());
			}
		}
	}
	let doc = gen_doc_option(&doc);

	// prepare impl block for codegen
	let ident = &input.ident;
	let name = ident.to_string();
	let mut name = LitStr::new(&name, ident.span());
	if let Some(rename) = &attrs.rename {
		name = rename.clone();
	}

	// prepare the generics - all impl generics will get `OpenapiType` requirement
	let (impl_generics, ty_generics, where_clause) = {
		let generics = &mut input.generics;
		generics.type_params_mut().for_each(|param| {
			param.colon_token.get_or_insert_with(Default::default);
			param.bounds.push(TypeParamBound::Trait(TraitBound {
				paren_token: None,
				modifier: TraitBoundModifier::None,
				lifetimes: None,
				path: path!(::openapi_type::OpenapiType)
			}));
		});
		generics.split_for_impl()
	};

	// parse the input data
	let parsed = match &input.data {
		Data::Struct(strukt) => parse_struct(ident, strukt, &attrs)?,
		Data::Enum(inum) => parse_enum(ident, inum, &attrs)?,
		Data::Union(union) => parse_union(union)?
	};

	// run the codegen
	let schema_code = parsed.gen_schema();

	// put the code together
	Ok(quote! {
		#[allow(unused_mut)]
		impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause {
			fn schema() -> ::openapi_type::OpenapiSchema {
				// this will be used by the schema code
				let mut dependencies = ::openapi_type::private::Dependencies::new();

				let mut schema: ::openapi_type::OpenapiSchema = #schema_code;
				schema.nullable = false;
				schema.dependencies = dependencies;

				const NAME: &::core::primitive::str = #name;
				schema.name = ::std::option::Option::Some(::std::string::String::from(NAME));

				const DESCRIPTION: ::core::option::Option<&'static ::core::primitive::str> = #doc;
				schema.description = DESCRIPTION.map(|desc| ::std::string::String::from(desc));

				schema
			}
		}
	})
}