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
109
#![allow(unused)]
// extern crate proc_macro; // might not be needed.

use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, DeriveInput, Field, Ident};
use utils::{extract_context_attr_value, get_struct_fields, get_type_name};

mod utils;

#[proc_macro_derive(FilterNodes, attributes(context))]
pub fn derives_filter_nodes(input: TokenStream) -> TokenStream {
	let ast = parse_macro_input!(input as DeriveInput);

	//// get struct name and fields
	let struct_name = &ast.ident;
	let fields = get_struct_fields(&ast);

	//// Properties to be collected
	let mut props: Vec<&Option<Ident>> = Vec::new(); // not needed for now.
	let mut prop_opval_idents: Vec<&Ident> = Vec::new();
	let mut props_opval_contexts: Vec<proc_macro2::TokenStream> = Vec::new();

	for field in fields.named.iter() {
		// NOTE: By macro limitation, we can do only type name match and it would not support type alias
		//       For now, assume Option is use as is, not even in a fully qualified way.
		//       We can add other variants of Option if proven needed
		let type_name = get_type_name(field);

		// NOTE: For now only convert the properties of types with option and OpVal
		if type_name.starts_with("Option ") && type_name.contains("OpVal") {
			if let Some(ident) = field.ident.as_ref() {
				prop_opval_idents.push(ident);
				let block = if let Some(ctx) = extract_context_attr_value(field) {
					quote! {
						Some(#ctx.to_string())
					}
				} else {
					quote! {
						None
					}
				};

				props_opval_contexts.push(block);
			}
		} else {
			props.push(&field.ident);
		}
	}

	//// Generate each type of .pushes code block
	let ff_pushes = quote! {
		#(
			ff.push((stringify!(#props), self.#props.clone()).into());
		)*
	};

	let ff_opt_pushes = quote! {
		#(
			if let Some(val) = self.#prop_opval_idents {
				let node = modql::FilterNode {
					context_path: #props_opval_contexts,
					name: stringify!(#prop_opval_idents).to_string(),
					opvals: val.0.into_iter().map(|n| n.into()).collect(),
				};
				nodes.push(node);
			}
		)*
	};

	//// Out code for the impl IntoFilterNodes
	let out_impl_into_filter_nodes = quote! {
		impl modql::IntoFilterNodes for #struct_name {
			fn filter_nodes(self, context: Option<String>) -> Vec<modql::FilterNode> {
				let mut nodes = Vec::new();
				#ff_opt_pushes
				nodes
			}
		}
	};

	//// Out code for the from struct for Vec<FilterNode>
	let out_into_filter_node = quote! {
		impl From<#struct_name> for Vec<modql::FilterNode> {
			fn from(val: #struct_name) -> Self {
				modql::IntoFilterNodes::filter_nodes(val, None)
			}
		}
	};

	//// Out code for from struct for OrGroups
	let out_into_op_groups = quote! {
		impl From<#struct_name> for modql::OrGroups {
			fn from(val: #struct_name) -> Self {
				let nodes: Vec<modql::FilterNode> = val.into();
				nodes.into()
			}
		}
	};

	//// Final out code
	let output = quote! {
		#out_impl_into_filter_nodes
		#out_into_filter_node
		#out_into_op_groups
	};

	output.into()
}