diff options
Diffstat (limited to 'tosin-macros/src')
-rw-r--r-- | tosin-macros/src/lib.rs | 284 |
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() } |