aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tosin-macros/src/lib.rs284
1 files changed, 160 insertions, 124 deletions
diff --git a/tosin-macros/src/lib.rs b/tosin-macros/src/lib.rs
index 9c1f650..92945ed 100644
--- a/tosin-macros/src/lib.rs
+++ b/tosin-macros/src/lib.rs
@@ -148,90 +148,70 @@ fn impl_model(ast: &syn::DeriveInput) -> TokenStream {
} else {
panic!("not on a struct");
};
- let app_name = {
- // ugly hack because span::module_path() doesn't exist
- let call_site = proc_macro::Span::call_site();
- let call_site_file = call_site.source_file();
- let call_site_path = call_site_file.path();
- if !call_site_file.is_real() {
- panic!("call site does not have a real path");
- }
-
- let app_dir = if call_site_path.ends_with("models.rs") {
- call_site_path.parent().unwrap()
- } else {
- todo!("not in models.rs what is this")
- };
-
- // oh this is so fucking disgusting
- if app_dir.starts_with("examples") {
- app_dir
- .components()
- .skip(1) // drop the "examples/", keep the example name
- .map(|c| c.as_os_str().to_string_lossy())
- .collect::<Vec<_>>()
- .join("::")
- } else {
- todo!("what's a {}", call_site_path.display())
- }
- };
- let real_table_name = format!("{}-{}", app_name, lowercase_name);
- let real_db_types: Vec<_> = ast_data
- .fields
- .iter()
- .map(|field| {
- if is_id(field) {
- syn::parse_str("Id").unwrap()
- } else {
- field.ty.clone()
- }
- })
- .collect();
- let new_params: Vec<_> = ast_data
- .fields
- .iter()
- .filter(|field| !is_id(field))
- .map(|field| {
- let ty = &field.ty;
- let name = &field.ident;
- quote! { #name: #ty }
- })
- .collect();
- let field_names: Vec<_> = ast_data
- .fields
- .iter()
- .filter(|field| !is_id(field))
- .map(|field| &field.ident)
- .collect();
let FieldsInfo {
tosin_fields,
diesel_columns,
diesel_types,
} = ast_data.fields.iter().map(to_field_spec).collect();
-
- let insertable_types: Vec<_> = ast_data
+ let non_id_field_names: Vec<_> = ast_data
.fields
.iter()
.filter(|field| !is_id(field))
- .map(|field| {
- let ident = &field.ident;
- let ty = &field.ty;
- quote! {
- Option<tosin::db::dsl::Eq<#lowercase_name::#ident, &'insert #ty>>
- }
- })
+ .map(|field| &field.ident)
.collect();
- let insertable_values: Vec<_> = ast_data
- .fields
- .iter()
- .filter(|field| !is_id(field))
- .map(|field| {
- let ident = &field.ident;
- quote! {
- Some(#lowercase_name::#ident.eq(&self.#ident))
+
+ let model_meta = quote! {
+ pub const META: ::tosin::db::models::ModelMeta = ::tosin::db::models::ModelMeta {
+ name: stringify!(#name),
+ fields: &[ #(#tosin_fields),* ],
+ };
+ };
+
+ let new = {
+ let new_params: Vec<_> = ast_data
+ .fields
+ .iter()
+ .filter(|field| !is_id(field))
+ .map(|field| {
+ let ty = &field.ty;
+ let name = &field.ident;
+ quote! { #name: #ty }
+ })
+ .collect();
+ quote! {
+ pub fn new(#(#new_params),*) -> Self {
+ Self {
+ id: None,
+ #(#non_id_field_names),*
+ }
}
- })
- .collect();
+ }
+ };
+
+ let save_mut = quote! {
+ pub fn save_mut(&mut self, connection: &tosin::db::backend::Connection) {
+ use diesel::prelude::*;
+ if self.id.is_none() {
+ // no id yet, so not from db, so insert
+ // unfortunately InsertStatement::get_result is only supported on pg for now,
+ // so we have to pull this shit
+ let new_self = tosin::db::backend::insert_and_retrieve::<
+ Self, // row
+ #lowercase_name::table, // table
+ #lowercase_name::id, // id
+ >(
+ self,
+ #lowercase_name::table,
+ connection,
+ #lowercase_name::id
+ );
+ *self = new_self;
+ } else {
+ todo!("update existing db item");
+ }
+ }
+ };
+
let (getters, setters): (Vec<_>, Vec<_>) = ast_data
.fields
.iter()
@@ -253,72 +233,128 @@ fn impl_model(ast: &syn::DeriveInput) -> TokenStream {
(getter, setter)
})
.unzip();
- let gen = quote! {
- impl #name {
- pub const META: ::tosin::db::models::ModelMeta = ::tosin::db::models::ModelMeta {
- name: stringify!(#name),
- fields: &[ #(#tosin_fields),* ],
- };
- pub fn new(#(#new_params),*) -> Self {
- Self {
- id: None,
- #(#field_names),*
+ let impl_queryable = {
+ let real_db_types: Vec<_> = ast_data
+ .fields
+ .iter()
+ .map(|field| {
+ if is_id(field) {
+ syn::parse_str("Id").unwrap()
+ } else {
+ field.ty.clone()
}
- }
+ })
+ .collect();
+ quote! {
+ impl<__DB: tosin::db::diesel_backend::Backend, __ST> tosin::db::Queryable<__ST, __DB> for #name
+ where (#(#real_db_types),*): tosin::db::Queryable<__ST, __DB> {
+ type Row = <(#(#real_db_types),*) as tosin::db::Queryable<__ST, __DB>>::Row;
- pub fn save_mut(&mut self, connection: &tosin::db::backend::Connection) {
- use diesel::prelude::*;
- if self.id.is_none() {
- // no id yet, so not from db, so insert
- // unfortunately InsertStatement::get_result is only supported on pg for now,
- // so we have to pull this shit
- let new_self = tosin::db::backend::insert_and_retrieve::<
- Self, // row
- #lowercase_name::table, // table
- #lowercase_name::id, // id
- >(
- self,
- #lowercase_name::table,
- connection,
- #lowercase_name::id
- );
- *self = new_self;
- } else {
- todo!("update existing db item");
+ fn build(row: Self::Row) -> Self {
+ let (id, #(#non_id_field_names),*): (#(#real_db_types),*) = tosin::db::Queryable::build(row);
+ Self {
+ id: Some(id),
+ #(#non_id_field_names),*
+ }
}
}
-
- #(#getters)*
- #(#setters)*
}
+ };
- impl<__DB: tosin::db::diesel_backend::Backend, __ST> tosin::db::Queryable<__ST, __DB> for #name
- where (#(#real_db_types),*): tosin::db::Queryable<__ST, __DB> {
- type Row = <(#(#real_db_types),*) as tosin::db::Queryable<__ST, __DB>>::Row;
+ let diesel_table = {
+ let app_name = {
+ // ugly hack because span::module_path() doesn't exist
+ let call_site = proc_macro::Span::call_site();
+ let call_site_file = call_site.source_file();
+ let call_site_path = call_site_file.path();
+ if !call_site_file.is_real() {
+ panic!("call site does not have a real path");
+ }
+
+ let app_dir = if call_site_path.ends_with("models.rs") {
+ call_site_path.parent().unwrap()
+ } else {
+ todo!("not in models.rs what is this")
+ };
- fn build(row: Self::Row) -> Self {
- let row: (#(#real_db_types),*) = tosin::db::Queryable::build(row);
- todo!()
+ // oh this is so fucking disgusting
+ if app_dir.starts_with("examples") {
+ app_dir
+ .components()
+ .skip(1) // drop the "examples/", keep the example name
+ .map(|c| c.as_os_str().to_string_lossy())
+ .collect::<Vec<_>>()
+ .join("::")
+ } else {
+ todo!("what's a {}", call_site_path.display())
+ }
+ };
+ let real_table_name = format!("{}-{}", app_name, lowercase_name);
+ quote! {
+ // this means users need #[macro_use] extern crate diesel; but fuck doing it ourselves
+ table! {
+ #[sql_name = #real_table_name]
+ #lowercase_name {
+ #(#diesel_columns,)*
+ }
}
}
+ };
+
+ let impl_insertable = {
+ let insertable_types: Vec<_> = ast_data
+ .fields
+ .iter()
+ .filter(|field| !is_id(field))
+ .map(|field| {
+ let ident = &field.ident;
+ let ty = &field.ty;
+ quote! {
+ Option<tosin::db::dsl::Eq<#lowercase_name::#ident, &'insert #ty>>
+ }
+ })
+ .collect();
+ let insertable_values: Vec<_> = ast_data
+ .fields
+ .iter()
+ .filter(|field| !is_id(field))
+ .map(|field| {
+ let ident = &field.ident;
+ quote! {
+ Some(#lowercase_name::#ident.eq(&self.#ident))
+ }
+ })
+ .collect();
+ quote! {
+ impl<'insert> tosin::db::Insertable<#lowercase_name::table> for &'insert #name {
+ type Values = <(#(#insertable_types,)*) as tosin::db::Insertable<#lowercase_name::table>>::Values;
- // this means users need #[macro_use] extern crate diesel; but fuck doing it ourselves
- table! {
- #[sql_name = #real_table_name]
- #lowercase_name {
- #(#diesel_columns,)*
+ fn values(self) -> Self::Values {
+ use diesel::ExpressionMethods;
+ (#(#insertable_values,)*).values()
+ }
}
}
+ };
+
+ let gen = quote! {
+ impl #name {
+ #model_meta
- impl<'insert> tosin::db::Insertable<#lowercase_name::table> for &'insert #name {
- type Values = <(#(#insertable_types,)*) as tosin::db::Insertable<#lowercase_name::table>>::Values;
+ #new
- fn values(self) -> Self::Values {
- use diesel::ExpressionMethods;
- (#(#insertable_values,)*).values()
- }
+ #save_mut
+
+ #(#getters)*
+ #(#setters)*
}
+
+ #impl_queryable
+
+ #diesel_table
+
+ #impl_insertable
};
gen.into()
}