Skip to content

Instantly share code, notes, and snippets.

@jmsdnns
Last active August 31, 2024 06:21
Show Gist options
  • Save jmsdnns/dc7e489e671dca5bcf8d5323fc853701 to your computer and use it in GitHub Desktop.
Save jmsdnns/dc7e489e671dca5bcf8d5323fc853701 to your computer and use it in GitHub Desktop.
Trying out Rust's proc macros by building a very basic ORM
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()
}
#![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