From c25475735a1efec9dfd9ae3dde412ed80b7de2fd Mon Sep 17 00:00:00 2001 From: cosmod Date: Wed, 27 Nov 2024 17:53:09 +0800 Subject: [PATCH] Add the derivation of Entity --- examples/axum-app/src/model/tag.rs | 3 +- examples/axum-app/src/model/user.rs | 3 +- zino-core/src/orm/entity.rs | 8 +-- zino-core/src/orm/mutation.rs | 9 +-- zino-core/src/orm/query.rs | 77 +++++++++++++++++++++----- zino-derive/docs/entity.md | 6 ++ zino-derive/src/entity.rs | 63 +++++++++++++++++++++ zino-derive/src/lib.rs | 9 +++ zino-dioxus/Cargo.toml | 2 +- zino-dioxus/src/typography/markdown.rs | 1 + zino/src/prelude/mod.rs | 5 +- 11 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 zino-derive/docs/entity.md create mode 100644 zino-derive/src/entity.rs diff --git a/examples/axum-app/src/model/tag.rs b/examples/axum-app/src/model/tag.rs index 3996fbdf..c0500828 100644 --- a/examples/axum-app/src/model/tag.rs +++ b/examples/axum-app/src/model/tag.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; use zino::prelude::*; -use zino_derive::{DecodeRow, Model, ModelAccessor, ModelHooks, Schema}; +use zino_derive::{DecodeRow, Entity, Model, ModelAccessor, ModelHooks, Schema}; /// The `tag` model. #[derive( @@ -14,6 +14,7 @@ use zino_derive::{DecodeRow, Model, ModelAccessor, ModelHooks, Schema}; ModelAccessor, ModelHooks, Model, + Entity, )] #[serde(default)] #[schema(auto_rename)] diff --git a/examples/axum-app/src/model/user.rs b/examples/axum-app/src/model/user.rs index 20a1e30d..64e22f9c 100644 --- a/examples/axum-app/src/model/user.rs +++ b/examples/axum-app/src/model/user.rs @@ -1,7 +1,7 @@ use super::Tag; use serde::{Deserialize, Serialize}; use zino::prelude::*; -use zino_derive::{DecodeRow, Model, ModelAccessor, ModelHooks, Schema}; +use zino_derive::{DecodeRow, Entity, Model, ModelAccessor, ModelHooks, Schema}; use zino_model::user::JwtAuthService; /// The `user` model. @@ -16,6 +16,7 @@ use zino_model::user::JwtAuthService; ModelAccessor, ModelHooks, Model, + Entity, )] #[serde(default)] pub struct User { diff --git a/zino-core/src/orm/entity.rs b/zino-core/src/orm/entity.rs index 50f6ab8f..8f3f531c 100644 --- a/zino-core/src/orm/entity.rs +++ b/zino-core/src/orm/entity.rs @@ -1,13 +1,9 @@ -use crate::{ - error::Error, - model::{Column, Model}, -}; -use std::str::FromStr; +use crate::model::Model; /// An interface for the model entity. pub trait Entity: Model { /// The column type. - type Column: AsRef + FromStr> + Into>; + type Column: AsRef; /// The primary key column. const PRIMARY_KEY: Self::Column; diff --git a/zino-core/src/orm/mutation.rs b/zino-core/src/orm/mutation.rs index 0002b16e..ab17c9c3 100644 --- a/zino-core/src/orm/mutation.rs +++ b/zino-core/src/orm/mutation.rs @@ -1,9 +1,6 @@ /// Generates SQL `SET` expressions. use super::{query::QueryExt, DatabaseDriver, Entity, Schema}; -use crate::{ - error::Error, - model::{EncodeColumn, Mutation, Query}, -}; +use crate::model::{EncodeColumn, Mutation, Query}; use std::marker::PhantomData; /// A mutation builder for the model entity. @@ -24,8 +21,8 @@ impl MutationBuilder { /// Builds the model mutation. #[inline] - pub fn build(self) -> Result { - Ok(Mutation::default()) + pub fn build(self) -> Mutation { + Mutation::default() } } diff --git a/zino-core/src/orm/query.rs b/zino-core/src/orm/query.rs index dbc86287..97c26ad8 100644 --- a/zino-core/src/orm/query.rs +++ b/zino-core/src/orm/query.rs @@ -1,6 +1,5 @@ use super::{Entity, Schema}; use crate::{ - error::Error, extension::{JsonObjectExt, JsonValueExt}, model::{EncodeColumn, Query}, JsonValue, Map, SharedString, @@ -18,6 +17,8 @@ pub struct QueryBuilder { logical_and: Vec, /// The logical `OR` conditions. logical_or: Vec, + /// Sort order. + sort_order: Vec<(String, bool)>, // Offset. offset: usize, // Limit. @@ -33,6 +34,7 @@ impl QueryBuilder { fields: Vec::new(), logical_and: Vec::new(), logical_or: Vec::new(), + sort_order: Vec::new(), offset: 0, limit: 0, } @@ -204,14 +206,22 @@ impl QueryBuilder { /// Adds a logical `AND` condition for the field `IN` a list of values. #[inline] - pub fn and_in>(self, col: E::Column, values: Vec) -> Self { - self.push_logical_and(col, "$in", values.into()) + pub fn and_in(self, col: E::Column, values: V) -> Self + where + T: Into, + V: Into>, + { + self.push_logical_and(col, "$in", values.into().into()) } /// Adds a logical `AND` condition for the field `NOT IN` a list of values. #[inline] - pub fn and_not_in>(self, col: E::Column, values: Vec) -> Self { - self.push_logical_and(col, "$nin", values.into()) + pub fn and_not_in(self, col: E::Column, values: V) -> Self + where + T: Into, + V: Into>, + { + self.push_logical_and(col, "$nin", values.into().into()) } /// Adds a logical `AND` condition for the field `BETWEEN` two values. @@ -344,14 +354,22 @@ impl QueryBuilder { /// Adds a logical `OR` condition for the field `IN` a list of values. #[inline] - pub fn or_in>(self, col: E::Column, values: Vec) -> Self { - self.push_logical_or(col, "$in", values.into()) + pub fn or_in(self, col: E::Column, values: V) -> Self + where + T: Into, + V: Into>, + { + self.push_logical_or(col, "$in", values.into().into()) } /// Adds a logical `OR` condition for the field `NOT IN` a list of values. #[inline] - pub fn or_not_in>(self, col: E::Column, values: Vec) -> Self { - self.push_logical_or(col, "$nin", values.into()) + pub fn or_not_in(self, col: E::Column, values: V) -> Self + where + T: Into, + V: Into>, + { + self.push_logical_or(col, "$nin", values.into().into()) } /// Adds a logical `OR` condition for the field `BETWEEN` two values. @@ -422,6 +440,30 @@ impl QueryBuilder { self } + /// Sets the sort order. + #[inline] + pub fn order_by(mut self, col: E::Column, descending: bool) -> Self { + let field = col.as_ref().into(); + self.sort_order.push((field, descending)); + self + } + + /// Sets the sort with an ascending order. + #[inline] + pub fn order_asc(mut self, col: E::Column) -> Self { + let field = col.as_ref().into(); + self.sort_order.push((field, false)); + self + } + + /// Sets the sort with an descending order. + #[inline] + pub fn order_desc(mut self, col: E::Column) -> Self { + let field = col.as_ref().into(); + self.sort_order.push((field, true)); + self + } + /// Sets the offset. #[inline] pub fn offset(mut self, offset: usize) -> Self { @@ -437,16 +479,25 @@ impl QueryBuilder { } /// Builds the model query. - pub fn build(self) -> Result { + pub fn build(self) -> Query { let mut filters = Map::new(); - filters.upsert("$and", self.logical_and); - filters.upsert("$or", self.logical_or); + let logical_and = self.logical_and; + let logical_or = self.logical_or; + if !logical_and.is_empty() { + filters.upsert("$and", logical_and); + } + if !logical_or.is_empty() { + filters.upsert("$or", logical_or); + } let mut query = Query::new(filters); query.set_fields(self.fields); query.set_offset(self.offset); query.set_limit(self.limit); - Ok(query) + for (field, descending) in self.sort_order.into_iter() { + query.order_by(field, descending); + } + query } /// Pushes a logical `AND` condition for the column and expressions. diff --git a/zino-derive/docs/entity.md b/zino-derive/docs/entity.md new file mode 100644 index 00000000..f8654a37 --- /dev/null +++ b/zino-derive/docs/entity.md @@ -0,0 +1,6 @@ +Derives the [`Entity`](zino_core::orm::Entity) trait. + +# Attributes on struct fields + +- **`#[schema(primary_key)]`**: The `primary_key` annotation is used to + mark a column as the primary key. diff --git a/zino-derive/src/entity.rs b/zino-derive/src/entity.rs new file mode 100644 index 00000000..01b71c66 --- /dev/null +++ b/zino-derive/src/entity.rs @@ -0,0 +1,63 @@ +use super::parser; +use convert_case::{Case, Casing}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::DeriveInput; + +/// Parses the token stream for the `Entity` trait derivation. +pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream { + // Model name + let name = input.ident; + + let mut primary_key_name = String::from("id"); + let mut model_column_variants = Vec::new(); + let mut model_column_mappings = Vec::new(); + for field in parser::parse_struct_fields(input.data) { + if let Some(ident) = field.ident { + let name = ident.to_string(); + let variant = format_ident!("{}", name.to_case(Case::Pascal)); + 'inner: for attr in field.attrs.iter() { + let arguments = parser::parse_schema_attr(attr); + for (key, _value) in arguments.into_iter() { + match key.as_str() { + "ignore" => break 'inner, + "primary_key" => { + primary_key_name.clone_from(&name); + } + _ => (), + } + } + } + model_column_variants.push(quote! { + #variant, + }); + model_column_mappings.push(quote! { + #variant => #name, + }); + } + } + + let model_column_type = format_ident!("{}Column", name); + let primary_key_variant = format_ident!("{}", primary_key_name.to_case(Case::Pascal)); + quote! { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] + pub enum #model_column_type { + #(#model_column_variants)* + } + + impl AsRef for #model_column_type { + #[inline] + fn as_ref(&self) -> &str { + use #model_column_type::*; + match self { + #(#model_column_mappings)* + } + } + } + + impl zino_core::orm::Entity for #name { + type Column = #model_column_type; + const PRIMARY_KEY: Self::Column = <#model_column_type>::#primary_key_variant; + } + } +} diff --git a/zino-derive/src/lib.rs b/zino-derive/src/lib.rs index 52d54558..df0b624f 100644 --- a/zino-derive/src/lib.rs +++ b/zino-derive/src/lib.rs @@ -7,12 +7,21 @@ use proc_macro::TokenStream; use syn::{parse_macro_input, DeriveInput}; mod decode_row; +mod entity; mod model; mod model_accessor; mod model_hooks; mod parser; mod schema; +#[doc = include_str!("../docs/entity.md")] +#[proc_macro_derive(Entity, attributes(schema))] +pub fn derive_entity(item: TokenStream) -> TokenStream { + let input = parse_macro_input!(item as DeriveInput); + let output = entity::parse_token_stream(input); + TokenStream::from(output) +} + #[doc = include_str!("../docs/schema.md")] #[proc_macro_derive(Schema, attributes(schema))] pub fn derive_schema(item: TokenStream) -> TokenStream { diff --git a/zino-dioxus/Cargo.toml b/zino-dioxus/Cargo.toml index e8978f58..d6933de3 100644 --- a/zino-dioxus/Cargo.toml +++ b/zino-dioxus/Cargo.toml @@ -37,7 +37,7 @@ version = "0.4.38" features = ["serde"] [dependencies.comrak] -version = "0.29.0" +version = "0.30.0" default-features = false features = ["shortcodes", "syntect"] diff --git a/zino-dioxus/src/typography/markdown.rs b/zino-dioxus/src/typography/markdown.rs index e7d9f756..ad938b3e 100644 --- a/zino-dioxus/src/typography/markdown.rs +++ b/zino-dioxus/src/typography/markdown.rs @@ -20,6 +20,7 @@ pub fn Markdown(props: MarkdownProps) -> Element { options.extension.math_dollars = true; options.extension.shortcodes = true; options.extension.underline = true; + options.extension.subscript = false; options.parse.smart = true; options.parse.relaxed_autolinks = true; options.render.full_info_string = true; diff --git a/zino/src/prelude/mod.rs b/zino/src/prelude/mod.rs index e6e351f9..aa986305 100644 --- a/zino/src/prelude/mod.rs +++ b/zino/src/prelude/mod.rs @@ -36,4 +36,7 @@ pub use zino_core::auth::RegoEngine; #[cfg(feature = "orm")] #[doc(no_inline)] -pub use zino_core::orm::{ModelAccessor, ModelHelper, ScalarQuery, Schema, Transaction}; +pub use zino_core::orm::{ + Entity, ModelAccessor, ModelHelper, MutationBuilder, QueryBuilder, ScalarQuery, Schema, + Transaction, +};