Mark Nefedov
Posted on May 24, 2023
I've recently embarked on an endeavor to add migration generation to my favorite Rust ORM Welds.
Rust's powerful macro system allows us to extend the language in ways that can greatly enhance productivity and maintainability. In this article, I'll focus on a specific use case: finding all the structs in a codebase that derive a particular macro, in this case the WeldsModel
macro.
The solution provided in the question uses the syn
crate, a parsing library for Rust syntax, and the walkdir
crate, which provides an iterator over the files in a directory and its subdirectories. Here, we'll walk through this solution and explain how it works.
The Main Function
First, let's look at the main function:
fn main() {
for entry in walkdir::WalkDir::new("welds/examples")
.into_iter()
.filter_map(Result::ok)
{
let path = entry.path();
if path.is_file() && path.extension().unwrap() == "rs" {
let mut file = File::open(path).expect("Unable to open file");
let mut contents = String::new();
file.read_to_string(&mut contents)
.expect("Unable to read file");
let syntax_tree = syn::parse_file(&contents).unwrap();
let weldsmodel_structs = find_weldsmodel_structs(syntax_tree);
for weldsmodel_struct in weldsmodel_structs {
println!("Found struct: {}", weldsmodel_struct.ident);
}
}
}
}
The main
function here uses the WalkDir
iterator from the walkdir
crate to iterate over all the files in the welds/examples
directory and its subdirectories. It filters out directories and non-Rust files (path.extension().unwrap() == "rs"
), then reads the contents of each Rust file into a String
.
The syn::parse_file
function is then used to parse this string into a syntax tree, which represents the structure of the Rust code in the file. This syntax tree is passed to the find_weldsmodel_structs
function, which returns a Vec<ItemStruct>
containing all structs in the file that derive the WeldsModel
macro. ItemStruct is a syn
data model that describes struct
in a Rust AST, they will be used later in the project as a data source to build schema from.
Finally, the main
function prints out the name of each struct found.
The find_weldsmodel_structs Function
Next, let's look at the find_weldsmodel_structs
function:
fn find_weldsmodel_structs(syntax_tree: syn::File) -> Vec<ItemStruct> {
let mut weldsmodel_structs = Vec::new();
for item in syntax_tree.items {
if let syn::Item::Struct(item_struct) = item {
if has_weldsmodel_derive(&item_struct) {
weldsmodel_structs.push(item_struct);
}
}
}
weldsmodel_structs
}
This function iterates over all items in the syntax tree, checks if each item is a struct, and if so, checks if the struct derives the WeldsModel
macro using the has_weldsmodel_derive
function. If both conditions are met, the struct is added to the weldsmodel_structs
vector.
The has_weldsmodel_derive Function
Finally, the has_weldsmodel_derive
function:
fn has_weldsmodel_derive(item_struct: &ItemStruct) -> bool {
let attrs = &item_struct.attrs;
for attr in attrs {
let meta = &attr.meta;
if meta.path().is_ident("derive") {
let nested = attr
.parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
.unwrap();
for nested_meta in nested {
if nested_meta.path().is_ident("WeldsModel") {
return true;
}
}
}
}
false
}
This function checks if a given struct item has the WeldsModel
derive attribute. It does this by looking at each attribute of the struct, and if the attribute's path is "derive", it parses the arguments of the attribute with parse_args_with
. This gives a Punctuated
list of Meta
items, each representing an argument of the derive attribute. If any of these arguments' path is "WeldsModel", the function returns true
, otherwise false
.
TLDR
That's the overall solution: use WalkDir
to iterate over all Rust files in a directory, use syn::parse_file
to parse the contents of each file into a syntax tree, and then check each ATS node attributes and meta to find structures you need.
Posted on May 24, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.