-
Notifications
You must be signed in to change notification settings - Fork 77
Schema derive macro (reborn) #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c5f5e81
f2f6e34
9abe8ea
addb7a3
7f00ac1
9dcc096
6d00e43
48219ee
5e62148
2cd6027
746b6f3
150b024
c4e037e
c7785c2
b51b7b1
9bca3f5
0bf4c3a
b273e3b
505983e
0a7c30d
eeee0f3
2467469
2b9bccb
effdd09
5d87b4e
eb9ecce
78342ad
ffa7d63
1152b8c
1e624b4
2f419f1
c69f7ce
84f3d33
376439d
58ef6e0
12a5241
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
[package] | ||
name = "derive-macros" | ||
authors.workspace = true | ||
edition.workspace = true | ||
homepage.workspace = true | ||
keywords.workspace = true | ||
license.workspace = true | ||
repository.workspace = true | ||
readme.workspace = true | ||
version.workspace = true | ||
|
||
[lib] | ||
proc-macro = true | ||
|
||
[dependencies] | ||
proc-macro2 = "1" | ||
syn = { version = "2.0", features = ["extra-traits"] } | ||
quote = "1.0" | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
use proc_macro2::{Ident, TokenStream}; | ||
use quote::{quote, quote_spanned}; | ||
use syn::spanned::Spanned; | ||
use syn::{parse_macro_input, Data, DataStruct, DeriveInput, Fields, PathArguments, Type}; | ||
|
||
/// Derive a `deltakernel::schemas::ToDataType` implementation for the annotated struct. The actual | ||
/// field names in the schema (and therefore of the struct members) are all mandated by the Delta | ||
/// spec, and so the user of this macro is responsible for ensuring that | ||
/// e.g. `Metadata::schema_string` is the snake_case-ified version of `schemaString` from [Delta's | ||
/// Change Metadata](https://github.com/delta-io/delta/blob/master/PROTOCOL.md#change-metadata) | ||
/// action (this macro allows the use of standard rust snake_case, and will convert to the correct | ||
/// delta schema camelCase version). | ||
#[proc_macro_derive(Schema)] | ||
pub fn derive_schema(input: proc_macro::TokenStream) -> proc_macro::TokenStream { | ||
let input = parse_macro_input!(input as DeriveInput); | ||
let struct_ident = input.ident; | ||
|
||
let schema_fields = gen_schema_fields(&input.data); | ||
let output = quote! { | ||
#[automatically_derived] | ||
zachschuermann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
impl crate::actions::schemas::ToDataType for #struct_ident { | ||
fn to_data_type() -> crate::schema::DataType { | ||
use crate::actions::schemas::{ToDataType, GetStructField}; | ||
crate::schema::StructType::new(vec![ | ||
#schema_fields | ||
]).into() | ||
} | ||
} | ||
}; | ||
proc_macro::TokenStream::from(output) | ||
} | ||
|
||
// turn our struct name into the schema name, goes from snake_case to camelCase | ||
fn get_schema_name(name: &Ident) -> Ident { | ||
let snake_name = name.to_string(); | ||
let mut next_caps = false; | ||
let ret: String = snake_name | ||
.chars() | ||
.filter_map(|c| { | ||
if c == '_' { | ||
next_caps = true; | ||
None | ||
} else if next_caps { | ||
next_caps = false; | ||
// This assumes we're using ascii, should be okay | ||
Some(c.to_ascii_uppercase()) | ||
} else { | ||
Some(c) | ||
} | ||
}) | ||
.collect(); | ||
Ident::new(&ret, name.span()) | ||
} | ||
|
||
fn gen_schema_fields(data: &Data) -> TokenStream { | ||
let fields = match data { | ||
Data::Struct(DataStruct { | ||
fields: Fields::Named(fields), | ||
.. | ||
}) => &fields.named, | ||
_ => panic!("this derive macro only works on structs with named fields"), | ||
}; | ||
|
||
let schema_fields = fields.iter().map(|field| { | ||
let name = field.ident.as_ref().unwrap(); // we know these are named fields | ||
let name = get_schema_name(name); | ||
match field.ty { | ||
Type::Path(ref type_path) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rescuing https://github.com/delta-incubator/delta-kernel-rs/pull/129/files#r1527170864:
I've changed it here to join the full type path that was specified at the derive site. This means that if they have |
||
let type_path_quoted = type_path.path.segments.iter().map(|segment| { | ||
let segment_ident = &segment.ident; | ||
match &segment.arguments { | ||
PathArguments::None => quote! { #segment_ident :: }, | ||
PathArguments::AngleBracketed(angle_args) => quote! { #segment_ident::#angle_args :: }, | ||
_ => panic!("Can only handle <> type path args"), | ||
} | ||
}); | ||
quote_spanned! { field.span() => #(#type_path_quoted),* get_struct_field(stringify!(#name))} | ||
} | ||
_ => { | ||
panic!("Can't handle type: {:?}", field.ty); | ||
} | ||
} | ||
}); | ||
quote! { #(#schema_fields),* } | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
comment above added to address https://github.com/delta-incubator/delta-kernel-rs/pull/129/files#r1527171512
This will show up in the docs for the derive-macros crate as:

There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you wanted you could also omit the crate from docs with
#[doc(hidden)]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I sort of consider the crate docs like "implementer" docs. Like someone writing a connector would read them. I think understanding what this macro does is useful from that perspective, so likely it should be left in.