Last active
August 31, 2024 06:21
-
-
Save jmsdnns/dc7e489e671dca5bcf8d5323fc853701 to your computer and use it in GitHub Desktop.
Trying out Rust's proc macros by building a very basic ORM
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use proc_macro::TokenStream; | |
use quote::quote; | |
use syn::{ | |
parse_macro_input, Data::Struct, DataStruct, DeriveInput, Field, Fields::Named, FieldsNamed, | |
Path, Type, TypePath, | |
}; | |
#[derive(Debug)] | |
struct DBModel { | |
name: String, | |
fields: Vec<DBField>, | |
} | |
#[derive(Debug)] | |
struct DBField { | |
name: String, | |
ty: String, | |
} | |
fn get_db_field(field: &Field) -> Option<DBField> { | |
let ident = match &field.ident { | |
Some(id) => Some(format!("{}", id)), | |
None => { | |
return None; | |
} | |
}; | |
let ty_ident = match &field.ty { | |
Type::Path(TypePath { | |
path: Path { segments, .. }, | |
.. | |
}) => segments.first().map(|s| format!("{}", s.ident)), | |
_ => { | |
return None; | |
} | |
}; | |
let db_field = DBField { | |
name: ident.unwrap(), | |
ty: ty_ident.unwrap(), | |
}; | |
println!("Field: {} - Type: {}", db_field.name, db_field.ty); | |
Some(db_field) | |
} | |
#[proc_macro_derive(DBModel)] | |
pub fn derive(input: TokenStream) -> TokenStream { | |
let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput); | |
let fields = match data { | |
Struct(DataStruct { | |
fields: Named(FieldsNamed { ref named, .. }), | |
.. | |
}) => named, | |
_ => panic!("Uhhh wat"), | |
}; | |
let db_model = DBModel { | |
name: ident.to_string().to_lowercase(), | |
fields: fields.iter().filter_map(get_db_field).collect(), | |
}; | |
println!("DBModel: {} - Fields: {:?}", db_model.name, db_model.fields); | |
let fields: Vec<String> = db_model.fields.iter().map(|f| f.name.to_string()).collect(); | |
let columns = fields.join(","); | |
let input_fields = fields | |
.iter() | |
.map(|_| "?".to_string()) | |
.collect::<Vec<String>>() | |
.join(","); | |
let select_string = format!("select {} from {};", &columns, &db_model.name); | |
let insert_string = format!( | |
"insert into {} ({}) values({});", | |
&db_model.name, &columns, &input_fields | |
); | |
let result = quote! { | |
impl #ident { | |
pub fn select() -> ::std::string::String { | |
::std::string::String::from(#select_string) | |
} | |
pub fn insert(&self) -> ::std::string::String { | |
::std::string::String::from(#insert_string) | |
} | |
} | |
}; | |
result.into() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![allow(dead_code)] | |
use micromacros::DBModel; | |
#[derive(DBModel)] | |
pub struct Book { | |
id: u64, | |
title: String, | |
pages: u64, | |
author: String, | |
} | |
#[test] | |
fn gen_select() { | |
let select_sql = Book::select(); | |
assert_eq!("select id,title,pages,author from book;", select_sql); | |
} | |
#[test] | |
fn gen_insert() { | |
let book = Book { | |
id: 1728, | |
title: "My Story".to_string(), | |
pages: 1337, | |
author: "Jms Dnns".to_string(), | |
}; | |
let insert_sql = book.insert(); | |
assert_eq!( | |
"insert into book (id,title,pages,author) values(?,?,?,?);", | |
insert_sql | |
); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment