From 16b5db8ee98799f1f8a33983d9a368b29ddb0573 Mon Sep 17 00:00:00 2001 From: Phosphorus Moscu Date: Thu, 5 Dec 2024 20:26:44 -0300 Subject: [PATCH] feat: add support for additional derive macros (#57) --- attr/src/metadata/model.rs | 11 +++++++++++ attr/src/metadata/table.rs | 9 +++++++++ core/src/join.rs | 21 +++++++++++++++++++++ macro/src/codegen/insert_model.rs | 18 ++++++++++++++---- ormlite/Cargo.toml | 1 + ormlite/tests/sqlite/06-insert.rs | 24 ++++++++++++++++++++++-- 6 files changed, 78 insertions(+), 6 deletions(-) diff --git a/attr/src/metadata/model.rs b/attr/src/metadata/model.rs index 56e011c..ed7867a 100644 --- a/attr/src/metadata/model.rs +++ b/attr/src/metadata/model.rs @@ -9,6 +9,7 @@ use syn::DeriveInput; pub struct ModelMeta { pub table: TableMeta, pub insert_struct: Option, + pub extra_derives: Option>, pub pkey: ColumnMeta, } @@ -32,6 +33,7 @@ impl ModelMeta { table.name, )); let mut insert_struct = None; + let mut extra_derives: Option> = None; for attr in attrs { if let Some(v) = attr.insert { insert_struct = Some(v.value()); @@ -39,12 +41,20 @@ impl ModelMeta { if let Some(v) = attr.insertable { insert_struct = Some(v.to_string()); } + if let Some(v) = attr.extra_derives { + if !v.is_empty() { + extra_derives = Some(v); + } + } } let pkey = table.columns.iter().find(|&c| c.name == pkey).unwrap().clone(); let insert_struct = insert_struct.map(|v| Ident::from(v)); + let extra_derives = extra_derives.take().map(|vec| vec.into_iter().map(|v| v.to_string()).map(Ident::from).collect()); + Self { table, insert_struct, + extra_derives, pkey, } } @@ -55,6 +65,7 @@ impl ModelMeta { Self { pkey: inner.columns.iter().find(|c| c.name == "id").unwrap().clone(), table: inner, + extra_derives: None, insert_struct: None, } } diff --git a/attr/src/metadata/table.rs b/attr/src/metadata/table.rs index f7f7279..c538239 100644 --- a/attr/src/metadata/table.rs +++ b/attr/src/metadata/table.rs @@ -103,6 +103,15 @@ pub struct TableAttr { /// pub insert: Option, + /// Add extra derives to the insertion structs. + /// Example: + /// #[ormlite(insert = "InsertUser", extra_derives(Serialize, Deserialize))] + /// pub struct User { + /// pub id: i32, + /// } + /// + pub extra_derives: Option>, + /// Only used for derive(Insert) /// Example: /// #[ormlite(returns = "User")] diff --git a/core/src/join.rs b/core/src/join.rs index 2731d9c..1ea4097 100644 --- a/core/src/join.rs +++ b/core/src/join.rs @@ -1,10 +1,12 @@ use crate::model::Model; use async_trait::async_trait; +use serde::Deserialize; use serde::{Serialize, Serializer}; use sqlmo::query::Join as JoinQueryFragment; use sqlmo::query::SelectColumn; use sqlx::{Database, Decode, Encode, Type}; use std::ops::{Deref, DerefMut}; +use serde::de::Error; pub trait JoinMeta { type IdType: Clone + Send + Eq + PartialEq + std::hash::Hash; @@ -224,3 +226,22 @@ impl Serialize for Join { } } } + +impl<'de, T> Deserialize<'de> for Join + where + T: JoinMeta + Deserialize<'de>, + { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = Option::::deserialize(deserializer)?; + + let (id_type, join_data) = match data { + Some(value) => (T::_id(&value), JoinData::QueryResult(value)), + None => return Err(D::Error::custom("Invalid value")) + }; + + Ok(Join { id: id_type, data: join_data }) + } + } diff --git a/macro/src/codegen/insert_model.rs b/macro/src/codegen/insert_model.rs index 27d427a..1acafdf 100644 --- a/macro/src/codegen/insert_model.rs +++ b/macro/src/codegen/insert_model.rs @@ -1,3 +1,4 @@ +use itertools::Itertools; use ormlite_attr::Ident; use ormlite_attr::ModelMeta; use proc_macro2::TokenStream; @@ -16,10 +17,19 @@ pub fn struct_InsertModel(ast: &DeriveInput, attr: &ModelMeta) -> TokenStream { pub #id: #ty } }); - quote! { - #[derive(Debug)] - #vis struct #insert_model { - #(#struct_fields,)* + if let Some(extra_derives) = &attr.extra_derives { + quote! { + #[derive(Debug, #(#extra_derives,)*)] + #vis struct #insert_model { + #(#struct_fields,)* + } + } + } else { + quote! { + #[derive(Debug)] + #vis struct #insert_model { + #(#struct_fields,)* + } } } } diff --git a/ormlite/Cargo.toml b/ormlite/Cargo.toml index a7f8d2c..d3a55ac 100644 --- a/ormlite/Cargo.toml +++ b/ormlite/Cargo.toml @@ -67,4 +67,5 @@ trybuild = { version = "1.0.99", features = ["diff"] } env_logger = "0.11.5" uuid = { version = "1.10.0", features = ["serde", "v4"] } serde = { version = "1.0.210", features = ["derive"] } +serde_json = { version = "1.0.128" } chrono = { version = "0.4.38", features = ["serde"] } diff --git a/ormlite/tests/sqlite/06-insert.rs b/ormlite/tests/sqlite/06-insert.rs index 159a520..42efac0 100644 --- a/ormlite/tests/sqlite/06-insert.rs +++ b/ormlite/tests/sqlite/06-insert.rs @@ -1,18 +1,20 @@ use ormlite::model::{Insert, Join, JoinMeta, Model}; use sqlmo::ToSql; +use serde::{Serialize, Deserialize}; +use serde_json::json; use ormlite::Connection; #[path = "../setup.rs"] mod setup; -#[derive(Debug, Model, Clone)] +#[derive(Debug, Model, Clone, Serialize, Deserialize)] pub struct Organization { id: i32, name: String, } #[derive(Model)] -#[ormlite(insert = "InsertUser")] +#[ormlite(insert = "InsertUser", extra_derives(Serialize, Deserialize))] // Note the previous syntax, #[ormlite(insertable = InsertUser)] still works, but the new syntax is preferred. pub struct User { id: i32, @@ -66,6 +68,24 @@ async fn main() { assert_eq!(champ.organization.id, 12321); assert_eq!(champ.organization.name, "my org"); + let champ_copy = InsertUser { + name: "Champ".to_string(), + organization: Join::new(org.clone()), + ty: 12, + }; + let champ_json = json!(champ_copy).to_string(); + + assert_eq!(champ_json, r#"{"name":"Champ","organization":{"id":12321,"name":"my org"},"ty":12}"#); + + let champ_deserializing = serde_json::from_str::(r#"{"name":"Champ","organization":{"id":12321,"name":"my org"},"ty":12}"#); + + let Ok(champ_deserialized) = champ_deserializing else { + panic!("Deserialize failing"); + }; + + assert_eq!(champ_deserialized.name, champ_copy.name); + assert_eq!(champ_deserialized.organization.name, champ_copy.organization.name); + let millie = InsertUser { name: "Millie".to_string(), organization: Join::new(org),