claudius_derive/
lib.rs

1#![recursion_limit = "128"]
2
3extern crate proc_macro;
4#[macro_use]
5extern crate quote;
6extern crate syn;
7
8use proc_macro2::TokenStream;
9use syn::{parse_macro_input, DeriveInput};
10
11use derive_util::StructVisitor;
12
13////////////////////////////////////// #[derive(CommandLine)] ///////////////////////////////////
14
15/// Derive the CommandLine trait for a given struct.
16#[proc_macro_derive(JsonSchema, attributes())]
17pub fn derive_json_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
18    let input = parse_macro_input!(input as DeriveInput);
19    // `ty_name` holds the type's identifier.
20    let ty_name = input.ident;
21    // Break out for templating purposes.
22    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
23
24    let data = match input.data {
25        syn::Data::Struct(ref ds) => ds,
26        syn::Data::Enum(_) => {
27            panic!("enums are not supported");
28        }
29        syn::Data::Union(_) => {
30            panic!("unions are not supported");
31        }
32    };
33
34    let mut jsv = JsonSchemaVisitor;
35    let (value, required) = jsv.visit_struct(&ty_name, data);
36
37    let gen = quote! {
38        impl #impl_generics ::claudius::JsonSchema for #ty_name #ty_generics #where_clause {
39            fn json_schema() -> serde_json::Value {
40                let mut result = serde_json::json!{{}};
41                let mut properties = serde_json::json!{{}};
42                #value
43                result["required"] = serde_json::Value::Array(vec![].into());
44                #required
45                result["type"] = "object".into();
46                result["properties"] = properties;
47                result
48            }
49        }
50    };
51    gen.into()
52}
53
54///////////////////////////////////////// JsonSchemaVisitor ////////////////////////////////////////
55
56struct JsonSchemaVisitor;
57
58impl StructVisitor for JsonSchemaVisitor {
59    type Output = (TokenStream, TokenStream);
60
61    fn visit_struct_named_fields(
62        &mut self,
63        _ty_name: &syn::Ident,
64        _ds: &syn::DataStruct,
65        fields: &syn::FieldsNamed,
66    ) -> Self::Output {
67        let mut result = quote! {};
68        let mut required = quote! {};
69        for field in fields.named.iter() {
70            if let Some(field_ident) = &field.ident {
71                let field_ident = field_ident.to_string();
72                let field_ident = if let Some(field_ident) = field_ident.strip_prefix("r#") {
73                    field_ident.to_string()
74                } else {
75                    field_ident.clone()
76                };
77                let field_type = field.ty.clone();
78                result = quote! {
79                    #result
80                    properties[#field_ident] = <#field_type as ::claudius::JsonSchema>::json_schema();
81                };
82                required = quote! {
83                    #required
84                    if let Some(serde_json::Value::Array(arr)) = result.get_mut("required") {
85                        arr.push(#field_ident.into())
86                    }
87                };
88            }
89        }
90        (result, required)
91    }
92}