Closed Madoshakalaka closed 2 years ago
In the meantime, I crafted this super useful build.rs script. It's meant to be used in a frontend crate. It:
backend/src/entities/
serde::Deserialize
attributes and imports frontend/src/models/
(mod.rs/prelude.rs/// build.rs
use convert_case::{Case, Casing};
use quote::quote;
use std::fs;
use syn::__private::Span;
use syn::{
AttrStyle, Attribute, Field, Fields, FieldsNamed, Ident, Item, ItemStruct, Path, PathSegment,
};
use syn::{VisPublic, Visibility};
struct TableFile {
file_name: String,
content: Vec<Item>,
}
impl TableFile {
fn transform_and_write(self) {
std::fs::create_dir("src/models").ok();
let Self {
content,
file_name: name,
} = self;
content
.into_iter()
.filter_map(|i| match i {
Item::Struct(i) => Some(i),
_ => None,
})
.filter_map(|i| match i.ident.to_string().as_str() {
"Model" => Some(transform_struct(i)),
_ => None,
})
.map(|s| {
let f: ::syn::File = ::syn::parse_quote!(
//! generated by custom build script
use serde::Deserialize;
#s
);
prettyplease::unparse(&f)
})
.for_each(|file| std::fs::write(format!("src/models/{name}"), file).unwrap());
}
}
fn transform_struct(i: ItemStruct) -> ItemStruct {
ItemStruct {
attrs: vec![Attribute {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
path: Path {
leading_colon: None,
segments: std::iter::once(PathSegment::from(Ident::new(
"derive",
Span::call_site(),
)))
.collect(),
},
tokens: quote! {(Deserialize)},
}],
vis: Visibility::Public(VisPublic {
pub_token: Default::default(),
}),
struct_token: i.struct_token,
ident: i.ident,
generics: i.generics,
fields: Fields::Named(FieldsNamed {
brace_token: Default::default(),
named: i
.fields
.into_iter()
.map(|f| Field {
attrs: vec![],
vis: Visibility::Public(VisPublic {
pub_token: Default::default(),
}),
ident: f.ident,
colon_token: f.colon_token,
ty: f.ty,
})
.collect(),
}),
semi_token: i.semi_token,
}
}
fn main() {
let entities_dir = "../backend/src/entities/";
println!("cargo:rerun-if-changed={entities_dir}");
let files = std::fs::read_dir(entities_dir).unwrap();
let (mod_uses, prelude_uses): (Vec<_>, Vec<_>) = files
.filter_map(|file| file.ok())
.filter_map(|e| {
let name = e.file_name();
(name != "mod.rs" && name != "prelude.rs").then(|| name.into_string().unwrap())
})
.map(|file_name| {
let name = format!("{entities_dir}{file_name}");
let mod_name = file_name.strip_suffix(".rs").unwrap().to_string();
let struct_name = mod_name.to_case(Case::Pascal);
let table = std::fs::read_to_string(&name).unwrap();
let content = syn::parse_file(&table).unwrap().items;
let table_file = TableFile { file_name, content };
table_file.transform_and_write();
let mod_name = Ident::new(&mod_name, Span::call_site());
let mod_name_ = mod_name.clone();
let struct_name = Ident::new(&struct_name, Span::call_site());
(
quote! {pub mod #mod_name;},
quote! {pub use super::#mod_name_::Model as #struct_name;},
)
})
.unzip();
let mod_file = syn::parse_quote! {
//! generated by custom build script
pub mod prelude;
#(#mod_uses)*
};
let prelude_file = syn::parse_quote! {
//! generated by custom build script
#(#prelude_uses)*
};
fs::write("src/models/mod.rs", prettyplease::unparse(&mod_file)).unwrap();
fs::write(
"src/models/prelude.rs",
prettyplease::unparse(&prelude_file),
)
.unwrap();
}
# frontend/Cargo.toml
[build-dependencies]
syn = {version = "1.0", features = ["full"]}
quote = "1.0"
convert_case = "0.5"
prettyplease = "0.1"
The expected directory structure:
├───backend
│ └───src/
│ └───entities/
│
└───frontend/
└───src/
└───build.rs
Out of curiousity, what will SeaORM do at the frontend?
Out of curiousity, what will SeaORM at the frontend?
The model structs generated by sea-orm help deserialize fetched data in the frontend
When the entities are shared between a backend and web frontend (like one built with yew), it's undesirable (and impossible maybe) to pack sqlx+sea-orm into the frontend, where the data are meant to be fetched by HTTP requests. The frontend does benefit from the structs and the serde traits though.
For this reason it might be desirable to allow a feature gate flag in
sea-orm-cli
, and it generates something like this when the flag is supplied: