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#[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 let ty_name = input.ident;
21 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
54struct 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}