skopje_macros/lib.rs
1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{
4 Data,
5 DeriveInput,
6 Fields,
7 FieldsNamed,
8 // parse::{Parse, ParseStream},
9 parse_macro_input,
10};
11
12// /// ```rust
13// /// #[derive(Debug, serde::Deserialize)]
14// /// #[skopje::extract(
15// /// method = HTTP_GET,
16// /// url = "https://api.binance.com/api/v1/ticker/allBookTickers",
17// /// )]
18// /// #[skopje::load(
19// /// method = PG_INSERT,
20// /// client = deadpool_postgres::Pool,
21// /// obj = self.0
22// /// )]
23// /// pub struct Symbols(pub Vec<Symbol>);
24// /// ```
25// ///
26// /// Above is equivalent to:
27// ///
28// /// ```rust
29// /// #[derive(Debug, serde::Deserialize)]
30// /// pub struct Symbols(pub Vec<Symbol>);
31// ///
32// /// #[skopje::async_trait]
33// /// impl skopje::etl::Extract for Symbols {
34// /// type Client = skopje::HttpClient;
35// /// async fn extract(client: Self::Client) -> Result<Self> {
36// /// let url = "https://api.binance.com/api/v1/ticker/allBookTickers";
37// /// let data: Self = client.fetch(url).await?;
38// /// Ok(data)
39// /// }
40// /// }
41// ///
42// /// #[skopje::async_trait]
43// /// impl skopje::etl::Load for Symbols {
44// /// type Client = skopje::PgPool;
45// /// async fn load(&self, client: Self::Client) -> Result<()> {
46// /// client
47// /// .insert(super::common_sql::INSERT_SYMBOL, self.0.iter())
48// /// .await?;
49// /// Ok(())
50// /// }
51// /// }
52// /// ```
53// #[proc_macro_attribute]
54// pub fn extract(item: TokenStream, attr: TokenStream) -> TokenStream {
55// let args = parse_macro_input!(attr as ExtractArgs);
56// quote! {}.into()
57// }
58
59// struct ExtractArgs<'a> {
60// method: Option<&'a str>,
61// url: Option<&'a str>,
62// path: Option<&'a str>,
63// }
64
65// impl Parse for ExtractArgs<'_> {
66// fn parse(input: ParseStream) -> syn::Result<Self> {
67// let mut output = Self {
68// method: None,
69// url: None,
70// path: None,
71// };
72
73// Ok(output)
74// }
75// }
76
77/// Provide a like-for-like implementation of ['crate::load::pg::SqlMap`].
78/// Take the following:
79///
80/// ```rust
81/// #[derive(SqlMap)]
82/// struct MyStruct {
83/// field0: String,
84/// field1: i64,
85/// }
86/// ```
87///
88/// Above is equivalent to below:
89///
90/// ```rust
91/// struct MyStruct {
92/// field0: String,
93/// field1: i64,
94/// }
95///
96/// impl skopje::load::pg::SqlMap for &MyStruct {
97/// fn sql_map(&self) -> std::vec::Vec<&(dyn skopje::ToSql + std::marker::Sync)> {
98/// std::vec::Vec::from([
99/// &self.field0,
100/// &self.field1,
101/// ])
102/// }
103/// }
104/// ```
105#[proc_macro_derive(SqlMap)]
106pub fn derive_sql_map(item: TokenStream) -> TokenStream {
107 let body = parse_macro_input!(item as DeriveInput);
108
109 // Extract the struct name.
110 let struct_name = &body.ident;
111
112 // Extract field names.
113 let fields = match &body.data {
114 Data::Struct(data_struct) => match &data_struct.fields {
115 Fields::Named(FieldsNamed { named, .. }) => named,
116 _ => panic!("SqlMap can only be derived for structs with named fields"),
117 },
118 _ => panic!("SqlMap can only be derived for structs"),
119 };
120
121 // Create an array of references to each field.
122 let field_refs = fields.iter().map(|field| {
123 let field_name = &field.ident;
124 quote! { &self.#field_name }
125 });
126
127 // Return the implementation.
128 quote! {
129 impl skopje::load::pg::SqlMap for &#struct_name {
130 fn sql_map(&self) -> std::vec::Vec<&(dyn skopje::ToSql + std::marker::Sync)> {
131 vec![#(#field_refs),*]
132 }
133 }
134 }
135 .into()
136}