google_cloud_bigquery_derive/
lib.rs1extern crate proc_macro;
18
19use proc_macro::TokenStream;
20use quote::quote;
21use syn::{Data, DeriveInput, Fields, parse_macro_input};
22
23#[proc_macro_derive(FromRow, attributes(bigquery))]
29pub fn derive_from_row(input: TokenStream) -> TokenStream {
30 let input = parse_macro_input!(input as DeriveInput);
31 let name = input.ident;
32
33 let fields = match input.data {
34 Data::Struct(data) => match data.fields {
35 Fields::Named(fields) => fields.named,
36 _ => {
37 return syn::Error::new_spanned(
38 name,
39 "FromRow can only be derived for structs with named fields",
40 )
41 .to_compile_error()
42 .into();
43 }
44 },
45 _ => {
46 return syn::Error::new_spanned(name, "FromRow can only be derived for structs")
47 .to_compile_error()
48 .into();
49 }
50 };
51 let value_extractions = fields.iter().map(|f| {
52 let field_name = f.ident.as_ref().expect("named field must have identifier");
53 let db_column_name = get_field_name(f);
54 quote! {
55 let #field_name = row.take(#db_column_name)?;
56 }
57 });
58
59 let field_idents = fields
60 .iter()
61 .map(|f| f.ident.as_ref().expect("named field must have identifier"));
62
63 let expanded = quote! {
66 impl std::convert::TryFrom<google_cloud_bigquery::query::Row> for #name {
67 type Error = google_cloud_bigquery::error::RowError;
68
69 fn try_from(mut row: google_cloud_bigquery::query::Row) -> std::result::Result<Self, Self::Error> {
70 #( #value_extractions )*
71
72 std::result::Result::Ok(Self {
73 #( #field_idents, )*
74 })
75 }
76 }
77 };
78
79 expanded.into()
80}
81
82#[proc_macro_derive(FromSql, attributes(bigquery))]
86pub fn derive_from_sql(input: TokenStream) -> TokenStream {
87 let input = parse_macro_input!(input as DeriveInput);
88 let name = input.ident;
89
90 let fields = match input.data {
91 Data::Struct(data) => match data.fields {
92 Fields::Named(fields) => fields.named,
93 _ => {
94 return syn::Error::new_spanned(
95 name,
96 "FromSql can only be derived for structs with named fields",
97 )
98 .to_compile_error()
99 .into();
100 }
101 },
102 _ => {
103 return syn::Error::new_spanned(name, "FromSql can only be derived for structs")
104 .to_compile_error()
105 .into();
106 }
107 };
108
109 let field_idents_struct_array = fields
110 .iter()
111 .map(|f| f.ident.as_ref().expect("named field must have identifier"));
112 let field_idents_struct_obj = fields
113 .iter()
114 .map(|f| f.ident.as_ref().expect("named field must have identifier"));
115
116 let field_extractions_array = fields.iter().map(|f| {
117 let field_name = f.ident.as_ref().expect("named field must have identifier");
118 let db_column_name = get_field_name(f);
119 quote! {
120 let #field_name = iter.next()
121 .ok_or_else(|| google_cloud_bigquery::error::ConvertError::MissingField(#db_column_name.to_string()))?;
122 let #field_name = google_cloud_bigquery::query::FromSql::from_sql(#field_name)?;
123 }
124 });
125
126 let field_extractions_obj = fields.iter().map(|f| {
127 let field_name = f.ident.as_ref().expect("named field must have identifier");
128 let db_column_name = get_field_name(f);
129 quote! {
130 let #field_name = obj.remove(#db_column_name)
131 .ok_or_else(|| google_cloud_bigquery::error::ConvertError::MissingField(#db_column_name.to_string()))?;
132 let #field_name = google_cloud_bigquery::query::FromSql::from_sql(#field_name)?;
133 }
134 });
135
136 let expanded = quote! {
137 impl google_cloud_bigquery::query::FromSql for #name {
138 fn from_sql(value: wkt::Value) -> std::result::Result<Self, google_cloud_bigquery::error::ConvertError> {
139 match value {
140 wkt::Value::Array(arr) => {
141 let mut iter = arr.into_iter();
142 #( #field_extractions_array )*
143 std::result::Result::Ok(Self {
144 #( #field_idents_struct_array, )*
145 })
146 }
147 wkt::Value::Object(mut obj) => {
148 #( #field_extractions_obj )*
149 std::result::Result::Ok(Self {
150 #( #field_idents_struct_obj, )*
151 })
152 }
153 other => std::result::Result::Err(google_cloud_bigquery::error::ConvertError::TypeMismatch {
154 expected: "array or object",
155 got: other,
156 }),
157 }
158 }
159 }
160 };
161
162 expanded.into()
163}
164
165fn get_field_name(field: &syn::Field) -> String {
166 for attr in &field.attrs {
167 if attr.path().is_ident("bigquery") {
168 let mut renamed = None;
169 let _ = attr.parse_nested_meta(|meta| {
170 if meta.path.is_ident("rename") {
171 let value = meta.value()?;
172 let lit: syn::LitStr = value.parse()?;
173 renamed = Some(lit.value());
174 Ok(())
175 } else {
176 Err(meta.error("unsupported bigquery attribute"))
177 }
178 });
179 if let Some(name) = renamed {
180 return name;
181 }
182 }
183 }
184 field
185 .ident
186 .as_ref()
187 .expect("named field must have identifier")
188 .to_string()
189}