openapi_type_derive/
lib.rs

1#![allow(clippy::into_iter_on_ref)]
2#![warn(missing_debug_implementations, rust_2018_idioms)]
3#![deny(rustdoc::broken_intra_doc_links)]
4#![forbid(unsafe_code)]
5
6//! This crate defines the macros for `#[derive(OpenapiType)]`.
7
8use proc_macro::TokenStream;
9use proc_macro2::TokenStream as TokenStream2;
10use quote::quote;
11use syn::{parse_macro_input, Data, DeriveInput, Meta, TraitBound, TraitBoundModifier, TypeParamBound};
12use syn_path::path;
13
14mod attrs;
15mod codegen;
16mod parser;
17mod util;
18
19use attrs::*;
20use parser::*;
21
22/// The derive macro for [`OpenapiType`].
23///
24///  [`OpenapiType`]: https://docs.rs/openapi_type/*/openapi_type/trait.OpenapiType.html
25#[proc_macro_derive(OpenapiType, attributes(openapi))]
26pub fn derive_openapi_type(input: TokenStream) -> TokenStream {
27	let input = parse_macro_input!(input);
28	expand_openapi_type(input).unwrap_or_else(|err| err.to_compile_error()).into()
29}
30
31fn filter_parse_attrs(
32	attrs: &mut ContainerAttributes,
33	input: &DeriveInput,
34	filter: &str,
35	error_on_unknown: bool
36) -> syn::Result<()> {
37	for attr in &input.attrs {
38		match &attr.meta {
39			Meta::List(meta) if meta.path.is_ident(filter) => {
40				attrs.parse_from(meta.tokens.clone(), error_on_unknown)?;
41			},
42			_ => {}
43		}
44	}
45	Ok(())
46}
47
48fn expand_openapi_type(mut input: DeriveInput) -> syn::Result<TokenStream2> {
49	let ident = &input.ident;
50
51	// parse #[serde] and #[openapi] attributes
52	let mut attrs = ContainerAttributes::default();
53	filter_parse_attrs(&mut attrs, &input, "serde", false)?;
54	filter_parse_attrs(&mut attrs, &input, "openapi", true)?;
55
56	// parse #[doc] attributes
57	for attr in &input.attrs {
58		if attr.path().is_ident("doc") {
59			if let Some(lit) = parse_doc_attr(attr)? {
60				attrs.doc.push(lit.value());
61			}
62		}
63	}
64
65	// prepare the generics - all impl generics will get `OpenapiType` requirement
66	let (impl_generics, ty_generics, where_clause) = {
67		let generics = &mut input.generics;
68		generics.type_params_mut().for_each(|param| {
69			param.colon_token.get_or_insert_with(Default::default);
70			param.bounds.push(TypeParamBound::Trait(TraitBound {
71				paren_token: None,
72				modifier: TraitBoundModifier::None,
73				lifetimes: None,
74				path: path!(::openapi_type::OpenapiType)
75			}));
76		});
77		generics.split_for_impl()
78	};
79
80	// parse the input data
81	let parsed = match &input.data {
82		Data::Struct(strukt) => parse_struct(ident, strukt, &attrs)?,
83		Data::Enum(inum) => parse_enum(ident, inum, &attrs)?,
84		Data::Union(union) => parse_union(union)?
85	};
86
87	// run the codegen
88	let visit_impl = parsed.gen_visit_impl();
89
90	// put the code together
91	Ok(quote! {
92		#[allow(unused_mut)]
93		impl #impl_generics ::openapi_type::OpenapiType for #ident #ty_generics #where_clause {
94			fn visit_type<__openapi_type_V>(visitor: &mut __openapi_type_V)
95			where
96				__openapi_type_V: ::openapi_type::Visitor
97			{
98				#visit_impl
99			}
100		}
101	})
102}