schemadoc_diff_derive/
lib.rs1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span};
5
6use quote::quote;
7use syn::{Data, Field, Fields, GenericArgument, Path, PathArguments, Type, TypePath};
8
9#[derive(Debug)]
10enum FieldType {
11 DiffResult,
12 BoxedDiffResult,
13 Other,
14}
15
16impl FieldType {
17 pub fn is_other(&self) -> bool {
18 matches!(self, FieldType::Other)
19 }
20}
21
22fn inner_type(path: &Path) -> Option<&Type> {
23 if path.leading_colon.is_some() {
24 return None;
25 }
26
27 if path.segments.len() != 1 {
28 return None;
29 }
30
31 let ab = match &path.segments[0].arguments {
32 PathArguments::AngleBracketed(ab) => ab,
33 _ => return None,
34 };
35
36 if ab.args.len() != 1 {
37 return None;
38 }
39
40 match &ab.args[0] {
41 GenericArgument::Type(t) => Some(t),
42 _ => None,
43 }
44}
45
46enum CoFieldType {
47 Other,
48 ContainerDiffResult,
49 PrimitiveDiffResult,
50}
51
52
53fn is_primitive_type(path: &Path) -> bool {
54 match path.get_ident() {
55 None => false,
56 Some(ident) => {
57 matches!(ident.to_string().as_ref(), "String" | "usize" | "bool" | "f32" | "Value")
58 }
59 }
60}
61
62
63fn co_get_field_type(field: &Field) -> CoFieldType {
64 match &field.ty {
65 Type::Path(ty) => {
66 let ident = &ty.path.segments[0].ident;
67
68 if ident == "Box" {
69 match inner_type(&ty.path) {
70 Some(Type::Path(ty @ TypePath { path, .. })) => {
71 let ident = &path.segments[0].ident;
72 if ident == "DiffResult" {
73 match inner_type(&ty.path) {
74 Some(Type::Path(TypePath { path, .. })) => {
75 if is_primitive_type(path) {
76 CoFieldType::PrimitiveDiffResult
77 } else {
78 CoFieldType::Other
79 }
80 }
81 _ => unreachable!("DiffResult must have generic type."),
82 }
83 } else {
84 CoFieldType::Other
85 }
86 }
87 None => unreachable!("Box must have generic type."),
88 _ => CoFieldType::Other
89 }
90 } else if ident == "DiffResult" {
91 match inner_type(&ty.path) {
92 Some(Type::Path(TypePath { path, .. })) => {
93 if is_primitive_type(path) {
94 CoFieldType::PrimitiveDiffResult
95 } else if matches!(path.segments[0].ident.to_string().as_ref(), "VecDiff" | "MapDiff") {
96 CoFieldType::ContainerDiffResult
97 } else {
98 CoFieldType::Other
99 }
100 }
101 _ => unreachable!("DiffResult must have generic type."),
102 }
103 } else {
104 CoFieldType::Other
105 }
106 }
107 _ => CoFieldType::Other
108 }
109}
110
111#[proc_macro_derive(DiffOwnChanges)]
112pub fn diff_own_changes_proc_macro(input: TokenStream) -> TokenStream {
113 let syn::DeriveInput {
114 ident,
115 data,
116 ..
117 } = syn::parse_macro_input!(input as syn::DeriveInput);
118
119 let data = match data {
120 Data::Struct(data) => data,
121 _ => panic!("Only structs are supported"),
122 };
123
124 let fields = match data.fields {
125 Fields::Named(fields) => fields.named,
126 _ => panic!("Only structs with names fields are supported")
127 };
128
129 let diff_result_fields: Vec<_> = fields
130 .iter()
131 .map(|field| (field, co_get_field_type(field)))
132 .filter(|(_, field_type)| !matches!(field_type, CoFieldType::Other))
133 .collect();
134
135 let field_idents: Vec<_> = diff_result_fields
136 .iter()
137 .map(|(field, field_type)| {
138 let field_ident = field.ident.as_ref().unwrap();
139 let field_name = field_ident.to_string();
140
141 if matches!(field_type, CoFieldType::PrimitiveDiffResult) {
142 quote! {
143 if !self.#field_ident.is_same_or_none() {
144 changes.push((#field_name.into(), (&self.#field_ident).into()))
145 }
146 }
147 } else {
148 quote! {
149 if !self.#field_ident.is_same_or_none() {
150 changes.extend(self.#field_ident.get_own_changes())
151 }
152 }
153 }
154 }).collect();
155
156 let expanded = quote! {
157 impl crate::diff_own_changes::DiffOwnChanges for #ident{
158 fn get_own_changes(&self) -> Vec<(::std::borrow::Cow<str>, crate::diff_result_type::DiffResultType)> {
159 let mut changes = Vec::new();
160
161 #(#field_idents)*
162
163 changes
164 }
165 }
166 };
167
168 TokenStream::from(expanded)
169}
170
171fn get_field_type(field: &Field) -> FieldType {
172 match &field.ty {
173 Type::Path(ty) => {
174 let ident = &ty.path.segments[0].ident;
175
176 if ident == "Box" {
177 match inner_type(&ty.path) {
178 Some(Type::Path(ty)) => {
179 let ident = &ty.path.segments[0].ident;
180 if ident == "DiffResult" {
181 FieldType::BoxedDiffResult
182 } else {
183 FieldType::Other
184 }
185 }
186 None => unreachable!("Box must have generic type."),
187 _ => FieldType::Other
188 }
189 } else if ident == "DiffResult" {
190 FieldType::DiffResult
191 } else {
192 FieldType::Other
193 }
194 }
195 _ => FieldType::Other
196 }
197}
198
199
200#[proc_macro_derive(Empty)]
201pub fn is_empty_proc_macro(input: TokenStream) -> TokenStream {
202 let syn::DeriveInput {
203 ident,
204 data,
205 ..
206 } = syn::parse_macro_input!(input as syn::DeriveInput);
207
208 let data = match data {
209 Data::Struct(data) => data,
210 _ => panic!("Only structs are supported"),
211 };
212
213 let fields = match data.fields {
214 Fields::Named(fields) => fields.named,
215 _ => panic!("Only structs with names fields are supported")
216 };
217 let diff_result_fields: Vec<_> = fields
218 .iter()
219 .filter(|field| !get_field_type(field).is_other())
220 .collect();
221
222 let field_idents = diff_result_fields
223 .iter()
224 .enumerate()
225 .map(|(i, field)| {
226 let field_ident = field.ident.as_ref().unwrap();
227
228 let delim = if i < diff_result_fields.len() - 1 {
229 quote! { && }
230 } else {
231 quote! {}
232 };
233
234 quote! { self.#field_ident.is_same_or_none() #delim}
235 });
236
237 let expanded = quote! {
238 impl crate::core::Empty for #ident{
239 fn is_empty(&self) -> bool {
240 #(#field_idents)*
241 }
242 }
243 };
244
245 TokenStream::from(expanded)
246}
247
248enum FieldTypeDiff {
249 Box,
250 Other,
251 DiffResult,
252}
253
254fn get_field_diff_type(field: &Field) -> FieldTypeDiff {
255 match &field.ty {
256 Type::Path(p) => {
257 let ident = &p.path.segments[0].ident;
258
259 if ident == "Box" {
260 FieldTypeDiff::Box
261 } else if ident == "DiffResult" {
262 FieldTypeDiff::DiffResult
263 } else {
264 FieldTypeDiff::Other
265 }
266 }
267 _ => FieldTypeDiff::Other
268 }
269}
270
271
272#[proc_macro_derive(Diff)]
273pub fn diff_proc_macro(input: TokenStream) -> TokenStream {
274 let syn::DeriveInput {
275 ident: diff_ident,
276 data,
277 ..
278 } = syn::parse_macro_input!(input as syn::DeriveInput);
279
280 let data = match data {
281 Data::Struct(data) => data,
282 _ => panic!("Only structs are supported"),
283 };
284
285 let fields = match data.fields {
286 Fields::Named(fields) => fields.named,
287 _ => panic!("Only structs with names fields are supported")
288 };
289
290 let fields: Vec<_> = fields
291 .iter()
292 .map(|field| (field, get_field_diff_type(field)))
293 .collect();
294
295 let removed_idents = fields
296 .iter()
297 .map(|(field, ty)| {
298 let field_ident = field.ident.as_ref().unwrap();
299 if matches!(ty, FieldTypeDiff::Box) {
300 quote! { #field_ident: Box::new(self.#field_ident.diff(None, &context.removing())), }
301 } else if matches!(ty, FieldTypeDiff::DiffResult) {
302 quote! { #field_ident: self.#field_ident.diff(None, &context.removing()), }
303 } else {
304 quote! { #field_ident: self.#field_ident.clone(), }
305 }
306 });
307
308 let updated_idents = fields
309 .iter()
310 .map(|(field, ty)| {
311 let field_ident = field.ident.as_ref().unwrap();
312
313 if matches!(ty, FieldTypeDiff::Box) {
314 quote! { #field_ident: Box::new(self.#field_ident.diff(Option::from(&*value.#field_ident), context)), }
315 } else if matches!(ty, FieldTypeDiff::DiffResult) {
316 quote! { #field_ident: self.#field_ident.diff(Option::from(&value.#field_ident), context), }
317 } else {
318 quote! { #field_ident: self.#field_ident.clone(), }
319 }
320 });
321
322 let diff_ident_name = diff_ident.to_string();
323 let ident = Ident::new(
324 &diff_ident_name.replace("Diff", ""),
325 Span::call_site(),
326 );
327
328 let expanded = quote! {
329 impl Diff<crate::schema::#ident, #diff_ident, crate::context::HttpSchemaDiffContext> for crate::schema::#ident {
330 fn diff(
331 &self,
332 new: Option<&crate::schema::#ident>,
333 context: &crate::context::HttpSchemaDiffContext,
334 ) -> DiffResult<#diff_ident> {
335 let diff = match new {
336 None => DiffResult::Removed(#diff_ident {
337 #(#removed_idents)*
338 }),
339 Some(value) => {
340 let diff = #diff_ident {
341 #(#updated_idents)*
342 };
343
344 if diff.is_empty() {
345 DiffResult::Same(diff)
346 } else {
347 DiffResult::Updated(diff, None)
348 }
349 }
350 };
351 DiffResult::new(diff, context)
352 }
353 }
354 };
355
356 TokenStream::from(expanded)
357}
358
359
360#[cfg(test)]
361mod tests {}