diff --git a/derive/src/attributes/mod.rs b/derive/src/attributes/mod.rs new file mode 100644 index 0000000..d8412dc --- /dev/null +++ b/derive/src/attributes/mod.rs @@ -0,0 +1,2 @@ +mod row; +pub use row::Row; diff --git a/derive/src/attributes/row.rs b/derive/src/attributes/row.rs new file mode 100644 index 0000000..e08fa71 --- /dev/null +++ b/derive/src/attributes/row.rs @@ -0,0 +1,43 @@ +use quote::ToTokens; + +pub const ATTRIBUTE_NAME: &str = "row"; +pub const ATTRIBUTE_SYNTAX: &str = "#[row(crate = ...)]"; + +pub const CRATE_PATH: &str = "crate"; +pub const DEFAULT_CRATE_PATH: &str = "::clickhouse"; + +pub struct Row { + pub crate_path: syn::Path, +} + +impl Default for Row { + fn default() -> Self { + let default_crate_path = syn::parse_str::(DEFAULT_CRATE_PATH).unwrap(); + Self { + crate_path: default_crate_path, + } + } +} + +impl<'a> TryFrom<&'a syn::Attribute> for Row { + type Error = &'a syn::Attribute; + + fn try_from(attr: &'a syn::Attribute) -> Result { + if attr.path().is_ident(ATTRIBUTE_NAME) { + let row = attr.parse_args::().unwrap(); + let syn::Expr::Assign(syn::ExprAssign { left, right, .. }) = row else { + panic!("expected `{}`", ATTRIBUTE_SYNTAX); + }; + if left.to_token_stream().to_string() != CRATE_PATH { + panic!("expected `{}`", ATTRIBUTE_SYNTAX); + } + let syn::Expr::Path(syn::ExprPath { path, .. }) = *right else { + panic!("expected `{}`", ATTRIBUTE_SYNTAX); + }; + Ok(Self { crate_path: path }) + } else { + return Err(attr); + } + } +} + diff --git a/derive/src/lib.rs b/derive/src/lib.rs index bd5675a..0aa8786 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,3 +1,5 @@ +mod attributes; + use proc_macro2::TokenStream; use quote::quote; use serde_derive_internals::{ @@ -6,6 +8,24 @@ use serde_derive_internals::{ }; use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields}; +struct Attributes { + row: attributes::Row, +} + +impl From<&[syn::Attribute]> for Attributes { + fn from(attrs: &[syn::Attribute]) -> Self { + let mut row = None; + for attr in attrs { + if let Ok(r) = attributes::Row::try_from(attr) { + row = Some(r); + } + } + Self { + row: row.unwrap_or_default(), + } + } +} + fn column_names(data: &DataStruct, cx: &Ctxt, container: &Container) -> TokenStream { match &data.fields { Fields::Named(fields) => { @@ -35,12 +55,14 @@ fn column_names(data: &DataStruct, cx: &Ctxt, container: &Container) -> TokenStr // TODO: support wrappers `Wrapper(Inner)` and `Wrapper(T)`. // TODO: support the `nested` attribute. -// TODO: support the `crate` attribute. -#[proc_macro_derive(Row)] +#[proc_macro_derive(Row, attributes(row))] pub fn row(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); let cx = Ctxt::new(); + let Attributes { + row: attributes::Row { crate_path }, + } = Attributes::from(input.attrs.as_slice()); let container = Container::from_ast(&cx, &input); let name = input.ident; @@ -54,10 +76,9 @@ pub fn row(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - // TODO: replace `clickhouse` with `::clickhouse` here. let expanded = quote! { #[automatically_derived] - impl #impl_generics clickhouse::Row for #name #ty_generics #where_clause { + impl #impl_generics #crate_path::Row for #name #ty_generics #where_clause { const COLUMN_NAMES: &'static [&'static str] = #column_names; } }; diff --git a/src/row.rs b/src/row.rs index c5ca680..8efa4db 100644 --- a/src/row.rs +++ b/src/row.rs @@ -75,21 +75,21 @@ pub(crate) fn join_column_names() -> Option { #[cfg(test)] mod tests { - // XXX: need for `derive(Row)`. Provide `row(crate = ..)` instead. - use crate as clickhouse; - use clickhouse::Row; + use crate::Row; use super::*; #[test] fn it_grabs_simple_struct() { #[derive(Row)] + #[row(crate = crate)] #[allow(dead_code)] struct Simple1 { one: u32, } #[derive(Row)] + #[row(crate = crate)] #[allow(dead_code)] struct Simple2 { one: u32, @@ -103,6 +103,7 @@ mod tests { #[test] fn it_grabs_mix() { #[derive(Row)] + #[row(crate = crate)] struct SomeRow { _a: u32, } @@ -115,6 +116,7 @@ mod tests { use serde::Serialize; #[derive(Row, Serialize)] + #[row(crate = crate)] #[allow(dead_code)] struct TopLevel { #[serde(rename = "two")] @@ -129,6 +131,7 @@ mod tests { use serde::Serialize; #[derive(Row, Serialize)] + #[row(crate = crate)] #[allow(dead_code)] struct TopLevel { one: u32, @@ -144,6 +147,7 @@ mod tests { use serde::Deserialize; #[derive(Row, Deserialize)] + #[row(crate = crate)] #[allow(dead_code)] struct TopLevel { one: u32, @@ -158,6 +162,7 @@ mod tests { fn it_rejects_other() { #[allow(dead_code)] #[derive(Row)] + #[row(crate = crate)] struct NamedTuple(u32, u32); assert_eq!(join_column_names::(), None); @@ -170,6 +175,7 @@ mod tests { use serde::Serialize; #[derive(Row, Serialize)] + #[row(crate = crate)] #[allow(dead_code)] struct MyRow { r#type: u32, diff --git a/src/sql/mod.rs b/src/sql/mod.rs index 66330f6..465fc77 100644 --- a/src/sql/mod.rs +++ b/src/sql/mod.rs @@ -149,12 +149,11 @@ impl SqlBuilder { mod tests { use super::*; - // XXX: need for `derive(Row)`. Provide `row(crate = ..)` instead. - use crate as clickhouse; use clickhouse_derive::Row; #[allow(unused)] #[derive(Row)] + #[row(crate = crate)] struct Row { a: u32, b: u32, @@ -162,6 +161,7 @@ mod tests { #[allow(unused)] #[derive(Row)] + #[row(crate = crate)] struct Unnamed(u32, u32); #[test]