i_love_jesus_macros/
lib.rs1use proc_macro::TokenStream;
6use quote::quote;
7use quote::quote_spanned;
8use syn::parse::Parse;
9use syn::parse::ParseStream;
10use syn::parse_macro_input;
11use syn::spanned::Spanned;
12use syn::Data;
13use syn::DeriveInput;
14use syn::Fields;
15use syn::Meta;
16use syn::Path;
17use syn::Token;
18
19#[proc_macro_derive(CursorKeysModule, attributes(diesel, cursor_keys_module))]
30pub fn cursor_keys_module_derive(input: TokenStream) -> TokenStream {
31 let input = parse_macro_input!(input as DeriveInput);
32 let struct_name = input.ident;
33 let mut table_name = None;
34 let mut module_name = None;
35 for attr in input.attrs {
36 if let Meta::List(meta) = attr.meta {
37 if table_name.is_none() && meta.path.is_ident("diesel") {
38 if let Ok(a) = meta.parse_args::<MetaNameValue>() {
39 if a.path.is_ident("table_name") {
40 table_name = Some(a.value);
41 }
42 }
43 } else if module_name.is_none() && meta.path.is_ident("cursor_keys_module") {
44 if let Ok(a) = meta.parse_args::<MetaNameValue>() {
45 if a.path.is_ident("name") {
46 module_name = Some(a.value);
47 }
48 }
49 }
50 }
51 }
52 let Some(table_name) = table_name else {
53 return quote! {
54 compile_error!("missing `#[diesel(table_name = /* name */)]`");
55 }
56 .into();
57 };
58 let Some(module_name) = module_name else {
59 return quote! {
60 compile_error!("missing `#[cursor_keys_module(name = /* name */)]`");
61 }
62 .into();
63 };
64 let Data::Struct(data) = input.data else {
65 return quote! {
66 compile_error!("input must be struct");
67 }
68 .into();
69 };
70 let Fields::Named(fields) = data.fields else {
71 return quote! {
72 compile_error!("tuple struct not supported")
73 }
74 .into();
75 };
76 let struct_defs = fields.named.iter().map(|field| {
77 let span = field.span();
78 let ident = field
79 .ident
80 .as_ref()
81 .expect("should have already returned early in the tuple struct case");
82 quote_spanned! {span =>
83 #[allow(non_camel_case_types)]
84 #[derive(Clone, Copy, Debug, Default)]
85 pub struct #ident;
86 }
87 });
88 let impls = fields.named.iter().map(|field| {
89 let span = field.span();
90 let ident = field.ident.as_ref().expect("should have already returned early in the tuple struct case");
91 let ty = &field.ty;
92 let column = quote! {
93 #table_name::#ident
94 };
95 let mut select_expression = None;
96 let mut select_expression_type = None;
97 for attr in &field.attrs {
98 if let Meta::List(meta) = &attr.meta {
99 if meta.path.is_ident("diesel") {
100 if let Ok(a) = meta.parse_args::<MetaNameValue>() {
101 if a.path.is_ident("select_expression") {
102 select_expression = Some(a.value);
103 } else if a.path.is_ident("select_expression_type") {
104 select_expression_type = Some(a.value);
105 }
106 }
107 }
108 }
109 }
110 let (select_expression, select_expression_type) = match (select_expression, select_expression_type) {
111 (Some(_), None) => return quote_spanned! {span =>
112 compile_error!("missing `#[diesel(select_expression_type = /* type */)]`");
113 },
114 (None, Some(_)) => return quote_spanned! {span =>
115 compile_error!("missing `#[diesel(select_expression = /* expression */)]`");
116 },
117 (None, None) => (column.clone(), column.clone()),
118 (Some(select_expression), Some(select_expression_type)) => (select_expression, select_expression_type),
119 };
120 quote_spanned! {span =>
121 impl ::i_love_jesus::CursorKey<#struct_name> for #module_name::#ident {
122 type SqlType = <#select_expression_type as ::diesel::Expression>::SqlType;
123 type CursorValue = ::diesel::dsl::AsExprOf<#ty, Self::SqlType>;
124 type SqlValue = #select_expression_type;
125 fn get_cursor_value(cursor: &#struct_name) -> Self::CursorValue {
126 ::diesel::expression::AsExpression::<Self::SqlType>::as_expression(::std::clone::Clone::clone(&cursor.#ident))
127 }
128 fn get_sql_value() -> Self::SqlValue {
129 #select_expression
130 }
131 }
132 }
133 });
134
135 quote! {
137 pub mod #module_name {
138 use super::*;
139 #(#struct_defs)*
140 }
141 #(#impls)*
142 }
143 .into()
144}
145
146struct MetaNameValue {
148 pub path: Path,
149 pub value: proc_macro2::TokenStream,
150}
151
152impl Parse for MetaNameValue {
153 fn parse(input: ParseStream) -> syn::Result<Self> {
154 let path = input.parse()?;
155 let _: Token![=] = input.parse()?;
156 let value = input.parse()?;
157 Ok(MetaNameValue { path, value })
158 }
159}