#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(clippy::panic)]
#![allow(clippy::too_many_lines)]
#![allow(clippy::uninlined_format_args)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::disallowed_methods)]
#![allow(clippy::option_if_let_else)]
#![allow(clippy::use_self)]
#![allow(clippy::ref_option)]
#![allow(clippy::needless_pass_by_value)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::module_name_repetitions)]
#![allow(clippy::redundant_clone)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unnecessary_wraps)]
#![allow(clippy::struct_excessive_bools)]
#![allow(clippy::explicit_iter_loop)]
#![allow(clippy::collection_is_never_read)]
#![allow(clippy::suspicious_doc_comments)]
#![allow(clippy::while_let_on_iterator)]
#![allow(clippy::manual_while_let_some)]
#![allow(clippy::unused_peekable)]
#![allow(dead_code)]
#![allow(unused_variables)]
#![allow(unused_must_use)]
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
mod atomic;
mod model;
mod projection;
mod relations;
mod schema;
fn has_sea_orm_attribute(field: &syn::Field, attr_name: &str) -> bool {
for attr in &field.attrs {
if attr.path().is_ident("sea_orm") {
let meta_str = quote::quote!(#attr).to_string();
if meta_str.contains(attr_name) {
return true;
}
}
}
false
}
fn has_ergorm_attribute(field: &syn::Field, attr_name: &str) -> bool {
for attr in &field.attrs {
if attr.path().is_ident("ormada") {
let meta_str = quote::quote!(#attr).to_string();
if meta_str.contains(attr_name) {
return true;
}
}
}
false
}
#[proc_macro_derive(OrmadaModel, attributes(ormada))]
pub fn derive_ormada_model(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = &input.ident;
let fields = match &input.data {
Data::Struct(data) => match &data.fields {
Fields::Named(fields) => &fields.named,
_ => {
return syn::Error::new_spanned(
struct_name,
"OrmadaModel can only be derived for structs with named fields",
)
.to_compile_error()
.into();
}
},
_ => {
return syn::Error::new_spanned(
struct_name,
"OrmadaModel can only be derived for structs",
)
.to_compile_error()
.into();
}
};
let mut primary_key = None;
let mut auto_now_add_fields = Vec::new(); let mut auto_now_fields = Vec::new(); let mut all_fields = Vec::new();
for field in fields {
let field_name = field.ident.as_ref().unwrap();
let field_ty = &field.ty;
all_fields.push((field_name, field_ty));
if has_sea_orm_attribute(field, "primary_key") {
primary_key = Some(field_name);
continue;
}
if has_ergorm_attribute(field, "auto_now_add") {
auto_now_add_fields.push(field_name);
continue;
}
if has_ergorm_attribute(field, "auto_now") {
auto_now_fields.push(field_name);
}
}
if primary_key.is_none() {
return syn::Error::new_spanned(
struct_name,
"Model must have a field marked with #[sea_orm(primary_key)]",
)
.to_compile_error()
.into();
}
let primary_key = primary_key.unwrap();
let create_field_assignments: Vec<_> = all_fields
.iter()
.map(|(field_name, _)| {
if Some(*field_name) == Some(primary_key) {
let field_ty = all_fields.iter().find(|(n, _)| n == field_name).unwrap().1;
quote! {
#field_name: if model.#field_name == <#field_ty as Default>::default() {
sea_orm::ActiveValue::NotSet
} else {
sea_orm::ActiveValue::Set(model.#field_name)
}
}
} else if auto_now_add_fields.contains(field_name)
|| auto_now_fields.contains(field_name)
{
quote! { #field_name: sea_orm::ActiveValue::Set(now) }
} else {
quote! { #field_name: sea_orm::ActiveValue::Set(model.#field_name) }
}
})
.collect();
let save_field_assignments: Vec<_> = all_fields
.iter()
.map(|(field_name, _)| {
if Some(*field_name) == Some(primary_key) {
quote! { #field_name: sea_orm::ActiveValue::Set(self.#field_name) }
} else if auto_now_fields.contains(field_name) {
quote! { #field_name: sea_orm::ActiveValue::Set(now) }
} else {
quote! { #field_name: sea_orm::ActiveValue::Set(self.#field_name) }
}
})
.collect();
let update_auto_fields = auto_now_fields.iter().map(|field_name| {
quote! {
active_model.#field_name = sea_orm::ActiveValue::Set({
let now: sea_orm::prelude::DateTimeWithTimeZone = chrono::Utc::now().into();
now
});
}
});
let relation_infos = relations::parse_relations(&input);
let model_with_relations = relations::generate_model_with_relations(fields, &relation_infos);
let from_impl = relations::generate_from_impl(fields, &relation_infos);
let field_refs: Vec<&syn::Field> = fields.iter().collect();
let trait_impl = relations::generate_trait_impl(&relation_infos, &field_refs);
let has_relation_impls = relations::generate_has_relation_impls(&relation_infos);
let expanded = quote! {
#model_with_relations
#from_impl
#trait_impl
impl ::ormada::traits::OrmadaEntity for Entity {
fn to_active_model_for_create(model: Model) -> ::core::result::Result<ActiveModel, ::ormada::error::OrmadaError> {
let now = ::chrono::Utc::now().fixed_offset();
::core::result::Result::Ok(ActiveModel {
#(#create_field_assignments,)*
})
}
async fn save_model<'a, C: ::sea_orm::ConnectionTrait>(
db: &'a C,
model: Model,
) -> Result<Model, ormada::error::OrmadaError> {
model.save(db).await
}
}
impl Model {
pub async fn save<'a, C: ::sea_orm::ConnectionTrait>(
self,
db: &'a C,
) -> Result<Self, ormada::error::OrmadaOrmError> {
use sea_orm::Set;
let now = ::chrono::Utc::now().fixed_offset();
let mut active_model = ActiveModel {
#(#save_field_assignments,)*
};
#(#update_auto_fields)*
use sea_orm::ActiveModelTrait;
Ok(active_model.update(db).await?)
}
}
#has_relation_impls
};
TokenStream::from(expanded)
}
#[proc_macro_attribute]
pub fn atomic(args: TokenStream, input: TokenStream) -> TokenStream {
atomic::impl_atomic(args, input)
}
#[proc_macro_attribute]
pub fn ormada_model(attr: TokenStream, item: TokenStream) -> TokenStream {
match model::impl_ormada_model(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn ergorm_projection(attr: TokenStream, item: TokenStream) -> TokenStream {
match projection::generate_projection(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn ormada_schema(attr: TokenStream, item: TokenStream) -> TokenStream {
match schema::impl_ormada_schema(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}
#[proc_macro_attribute]
pub fn ormada_data_migration(attr: TokenStream, item: TokenStream) -> TokenStream {
match schema::impl_ormada_data_migration(attr.into(), item.into()) {
Ok(tokens) => tokens.into(),
Err(err) => err.to_compile_error().into(),
}
}