Skip to content

Commit

Permalink
Add the derivation of Entity
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Nov 27, 2024
1 parent 8c4560d commit c254757
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 29 deletions.
3 changes: 2 additions & 1 deletion examples/axum-app/src/model/tag.rs
Original file line number Diff line number Diff line change
@@ -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(
Expand All @@ -14,6 +14,7 @@ use zino_derive::{DecodeRow, Model, ModelAccessor, ModelHooks, Schema};
ModelAccessor,
ModelHooks,
Model,
Entity,
)]
#[serde(default)]
#[schema(auto_rename)]
Expand Down
3 changes: 2 additions & 1 deletion examples/axum-app/src/model/user.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -16,6 +16,7 @@ use zino_model::user::JwtAuthService;
ModelAccessor,
ModelHooks,
Model,
Entity,
)]
#[serde(default)]
pub struct User {
Expand Down
8 changes: 2 additions & 6 deletions zino-core/src/orm/entity.rs
Original file line number Diff line number Diff line change
@@ -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<str> + FromStr<Err: Into<Error>> + Into<Column<'static>>;
type Column: AsRef<str>;

/// The primary key column.
const PRIMARY_KEY: Self::Column;
Expand Down
9 changes: 3 additions & 6 deletions zino-core/src/orm/mutation.rs
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -24,8 +21,8 @@ impl<E: Entity> MutationBuilder<E> {

/// Builds the model mutation.
#[inline]
pub fn build(self) -> Result<Mutation, Error> {
Ok(Mutation::default())
pub fn build(self) -> Mutation {
Mutation::default()
}
}

Expand Down
77 changes: 64 additions & 13 deletions zino-core/src/orm/query.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use super::{Entity, Schema};
use crate::{
error::Error,
extension::{JsonObjectExt, JsonValueExt},
model::{EncodeColumn, Query},
JsonValue, Map, SharedString,
Expand All @@ -18,6 +17,8 @@ pub struct QueryBuilder<E: Entity> {
logical_and: Vec<Map>,
/// The logical `OR` conditions.
logical_or: Vec<Map>,
/// Sort order.
sort_order: Vec<(String, bool)>,
// Offset.
offset: usize,
// Limit.
Expand All @@ -33,6 +34,7 @@ impl<E: Entity> QueryBuilder<E> {
fields: Vec::new(),
logical_and: Vec::new(),
logical_or: Vec::new(),
sort_order: Vec::new(),
offset: 0,
limit: 0,
}
Expand Down Expand Up @@ -204,14 +206,22 @@ impl<E: Entity> QueryBuilder<E> {

/// Adds a logical `AND` condition for the field `IN` a list of values.
#[inline]
pub fn and_in<T: Into<JsonValue>>(self, col: E::Column, values: Vec<T>) -> Self {
self.push_logical_and(col, "$in", values.into())
pub fn and_in<T, V>(self, col: E::Column, values: V) -> Self
where
T: Into<JsonValue>,
V: Into<Vec<T>>,
{
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<T: Into<JsonValue>>(self, col: E::Column, values: Vec<T>) -> Self {
self.push_logical_and(col, "$nin", values.into())
pub fn and_not_in<T, V>(self, col: E::Column, values: V) -> Self
where
T: Into<JsonValue>,
V: Into<Vec<T>>,
{
self.push_logical_and(col, "$nin", values.into().into())
}

/// Adds a logical `AND` condition for the field `BETWEEN` two values.
Expand Down Expand Up @@ -344,14 +354,22 @@ impl<E: Entity> QueryBuilder<E> {

/// Adds a logical `OR` condition for the field `IN` a list of values.
#[inline]
pub fn or_in<T: Into<JsonValue>>(self, col: E::Column, values: Vec<T>) -> Self {
self.push_logical_or(col, "$in", values.into())
pub fn or_in<T, V>(self, col: E::Column, values: V) -> Self
where
T: Into<JsonValue>,
V: Into<Vec<T>>,
{
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<T: Into<JsonValue>>(self, col: E::Column, values: Vec<T>) -> Self {
self.push_logical_or(col, "$nin", values.into())
pub fn or_not_in<T, V>(self, col: E::Column, values: V) -> Self
where
T: Into<JsonValue>,
V: Into<Vec<T>>,
{
self.push_logical_or(col, "$nin", values.into().into())
}

/// Adds a logical `OR` condition for the field `BETWEEN` two values.
Expand Down Expand Up @@ -422,6 +440,30 @@ impl<E: Entity> QueryBuilder<E> {
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 {
Expand All @@ -437,16 +479,25 @@ impl<E: Entity> QueryBuilder<E> {
}

/// Builds the model query.
pub fn build(self) -> Result<Query, Error> {
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.
Expand Down
6 changes: 6 additions & 0 deletions zino-derive/docs/entity.md
Original file line number Diff line number Diff line change
@@ -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.
63 changes: 63 additions & 0 deletions zino-derive/src/entity.rs
Original file line number Diff line number Diff line change
@@ -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<str> 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;
}
}
}
9 changes: 9 additions & 0 deletions zino-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion zino-dioxus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
1 change: 1 addition & 0 deletions zino-dioxus/src/typography/markdown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 4 additions & 1 deletion zino/src/prelude/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

0 comments on commit c254757

Please sign in to comment.