supabase_wrappers_macros/
lib.rs1extern crate proc_macro;
2use proc_macro::TokenStream;
3use proc_macro2::TokenStream as TokenStream2;
4use quote::{format_ident, quote, ToTokens, TokenStreamExt};
5use syn::{parse_macro_input, punctuated::Punctuated, ItemStruct, Lit, MetaNameValue, Token};
6
7#[proc_macro_attribute]
40pub fn wrappers_fdw(attr: TokenStream, item: TokenStream) -> TokenStream {
41 let mut metas = TokenStream2::new();
42 let meta_attrs: Punctuated<MetaNameValue, Token![,]> =
43 parse_macro_input!(attr with Punctuated::parse_terminated);
44 let mut error_type: Option<String> = None;
45 for attr in meta_attrs {
46 let name = format!("{}", attr.path.segments.first().unwrap().ident);
47 if let Lit::Str(val) = attr.lit {
48 let value = val.value();
49 if name == "version" || name == "author" || name == "website" {
50 metas.append_all(quote! {
51 meta.insert(#name.to_owned(), #value.to_owned());
52 });
53 } else if name == "error_type" {
54 error_type = Some(value);
55 }
56 }
57 }
58
59 let error_type_ident = if let Some(error_type) = error_type {
60 format_ident!("{}", error_type)
61 } else {
62 let quoted = quote! {
63 compile_error!("Missing `error_type` in the `wrappers_fdw` attribute");
64 };
65 return quoted.into();
66 };
67
68 let item: ItemStruct = parse_macro_input!(item as ItemStruct);
69 let item_tokens = item.to_token_stream();
70 let ident = item.ident;
71 let ident_str = ident.to_string();
72 let ident_snake = to_snake_case(ident_str.as_str());
73
74 let module_ident = format_ident!("__{}_pgrx", ident_snake);
75 let fn_ident = format_ident!("{}_handler", ident_snake);
76 let fn_validator_ident = format_ident!("{}_validator", ident_snake);
77 let fn_meta_ident = format_ident!("{}_meta", ident_snake);
78 let fn_get_meta_ident = format_ident!("{}_get_meta", ident_snake);
79
80 let quoted = quote! {
81 #item_tokens
82
83 mod #module_ident {
84 use super::#ident;
85 use std::collections::HashMap;
86 use pgrx::pg_sys::panic::{ErrorReport, ErrorReportable};
87 use pgrx::prelude::*;
88 use supabase_wrappers::prelude::*;
89
90 #[pg_extern(create_or_replace)]
91 fn #fn_ident() -> supabase_wrappers::FdwRoutine {
92 #ident::fdw_routine()
93 }
94
95 #[pg_extern(create_or_replace)]
96 fn #fn_validator_ident(options: Vec<Option<String>>, catalog: Option<pg_sys::Oid>) {
97 #ident::validator(options, catalog)
98 .map_err(|e| <super::#error_type_ident as Into<ErrorReport>>::into(e))
99 .unwrap_or_report();
100 }
101
102 pub(super) fn #fn_get_meta_ident() -> HashMap<String, String> {
103 let mut meta: HashMap<String, String> = HashMap::new();
104 #metas
105 meta
106 }
107
108 #[pg_extern(create_or_replace)]
109 fn #fn_meta_ident() -> TableIterator<'static, (
110 name!(name, Option<String>),
111 name!(version, Option<String>),
112 name!(author, Option<String>),
113 name!(website, Option<String>)
114 )> {
115 let meta = #fn_get_meta_ident();
116
117 TableIterator::new(vec![(
118 Some(#ident_str.to_owned()),
119 meta.get("version").map(|s| s.to_owned()),
120 meta.get("author").map(|s| s.to_owned()),
121 meta.get("website").map(|s| s.to_owned()),
122 )].into_iter())
123 }
124 }
125
126 };
127
128 quoted.into()
129}
130
131fn to_snake_case(s: &str) -> String {
132 let mut acc = String::new();
133 let mut prev = '_';
134 for ch in s.chars() {
135 if ch.is_uppercase() && prev != '_' {
136 acc.push('_');
137 }
138 acc.push(ch);
139 prev = ch;
140 }
141 acc.to_lowercase()
142}