Skip to content

Commit

Permalink
Add support for converting Map keys to case
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Jan 23, 2025
1 parent fd78cae commit c9d4d9b
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 18 deletions.
1 change: 1 addition & 0 deletions crates/zino-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ ahash = "0.8.11"
apache-avro = "0.17.0"
base64 = "0.22.1"
cfg-if = "1.0"
convert_case = "0.7.1"
cron = "0.15.0"
csv = "1.3.1"
dirs = "6.0.0"
Expand Down
12 changes: 12 additions & 0 deletions crates/zino-core/src/extension/json_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use crate::{
JsonValue, Map, Record, Uuid,
};
use chrono::NaiveDateTime;
use convert_case::{Case, Casing};
use rust_decimal::Decimal;
use std::{
borrow::Cow,
mem,
net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr},
num::{ParseFloatError, ParseIntError},
str::{FromStr, ParseBoolError},
Expand Down Expand Up @@ -246,6 +248,9 @@ pub trait JsonObjectExt {
/// Extracts values from the populated data corresponding to the key and moves them to `self`.
fn extract_from_populated(&mut self, key: &str, fields: &[&str]);

/// Renames all the keys to the specific case.
fn rename_keys(&mut self, case: Case);

/// Attempts to read the map as an instance of the model `M`.
fn read_as_model<M: Model>(&self) -> Result<M, Validation>;

Expand Down Expand Up @@ -770,6 +775,13 @@ impl JsonObjectExt for Map {
self.append(&mut object);
}

#[inline]
fn rename_keys(&mut self, case: Case) {
for (key, value) in mem::take(self) {
self.insert(key.to_case(case), value);
}
}

fn read_as_model<M: Model>(&self) -> Result<M, Validation> {
let mut model = M::new();
let validation = model.read_map(self);
Expand Down
7 changes: 7 additions & 0 deletions crates/zino-derive/docs/model_hooks.md
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
Derives the [`ModelHooks`](zino_core::model::ModelHooks) trait.

# Attributes on struct fields

- **`#[schema(rename_all = "...")]`**: The `rename_all` attribute is used to
rename all the fields according to the specific case when decoding the model as a `Map`.
Supported values: `lowercase` | `UPPERCASE` | `PascalCase` | `camelCase` | `snake_case`
| `SCREAMING_SNAKE_CASE` | `kebab-case` | `SCREAMING-KEBAB-CASE`.
4 changes: 2 additions & 2 deletions crates/zino-derive/src/model_accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -726,16 +726,16 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
fetched_queries.push(quote! {
let mut models = Self::find::<Map>(query).await?;
for model in models.iter_mut() {
Self::after_decode(model).await?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await?;
}
});
fetched_one_queries.push(quote! {
let mut model = Self::find_by_id::<Map>(id)
.await?
.ok_or_else(|| zino_core::warn!("404 Not Found: cannot find the model `{}`", id))?;
Self::after_decode(&mut model).await?;
Self::translate_model(&mut model);
Self::after_decode(&mut model).await?;
});
if !model_references.is_empty() {
for (model, ref_fields) in model_references.into_iter() {
Expand Down
44 changes: 43 additions & 1 deletion crates/zino-derive/src/model_hooks.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,58 @@
use super::parser;
use proc_macro2::TokenStream;
use quote::quote;
use quote::{format_ident, quote};
use syn::DeriveInput;

/// Parses the token stream for the `ModelHooks` trait derivation.
pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
// Model name
let name = input.ident;

// Parsing struct attributes
let mut model_hooks = Vec::new();
let mut field_case = None;
for attr in input.attrs.iter() {
for (key, value) in parser::parse_schema_attr(attr).into_iter() {
if let Some(value) = value {
match key.as_str() {
"rename_all" => {
field_case = match value.as_str() {
"lowercase" => Some("Lower"),
"UPPERCASE" => Some("Upper"),
"PascalCase" => Some("Pascal"),
"camelCase" => Some("Camel"),
"snake_case" => Some("Snake"),
"SCREAMING_SNAKE_CASE" => Some("Constant"),
"kebab-case" => Some("Kebab"),
"SCREAMING-KEBAB-CASE" => Some("Cobol"),
_ => None,
}
}
_ => (),
}
}
}
}
if let Some(case) = field_case {
let case_variant = format_ident!("{}", case);
model_hooks.push(quote! {
#[inline]
async fn after_decode(model: &mut zino_core::Map) -> Result<(), zino_core::error::Error> {
use convert_case::Case;
use zino_core::extension::JsonObjectExt;

model.rename_keys(Case::#case_variant);
Ok(())
}
});
}

quote! {
impl zino_core::model::ModelHooks for #name {
type Data = ();
type Extension = ();

#(#model_hooks)*
}
}
}
2 changes: 1 addition & 1 deletion crates/zino-dioxus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ version = "0.4.39"
features = ["serde"]

[dependencies.comrak]
version = "0.33.0"
version = "0.34.0"
default-features = false
features = ["shortcodes", "syntect"]

Expand Down
2 changes: 1 addition & 1 deletion crates/zino-orm/src/accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@ where
let mut model = Self::find_by_id::<Map>(id)
.await?
.ok_or_else(|| warn!("404 Not Found: cannot find the model `{}`", id))?;
Self::after_decode(&mut model).await?;
Self::translate_model(&mut model);
Self::after_decode(&mut model).await?;
Ok(model)
}

Expand Down
10 changes: 5 additions & 5 deletions crates/zino-orm/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1090,8 +1090,8 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
let mut data = Self::find::<Map>(query).await?;
let translate_enabled = query.translate_enabled();
for model in data.iter_mut() {
Self::after_decode(model).await?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await?;
}
serde_json::from_value(data.into()).map_err(Error::from)
}
Expand Down Expand Up @@ -1129,10 +1129,10 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
async fn find_one_as<T: DeserializeOwned>(query: &Query) -> Result<Option<T>, Error> {
match Self::find_one::<Map>(query).await? {
Some(mut data) => {
Self::after_decode(&mut data).await?;
query
.translate_enabled()
.then(|| Self::translate_model(&mut data));
Self::after_decode(&mut data).await?;
serde_json::from_value(data.into()).map_err(Error::from)
}
None => Ok(None),
Expand Down Expand Up @@ -1188,8 +1188,8 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
for row in rows {
let mut map = Map::decode_row(&row)?;
let primary_key = map.get(primary_key_name).cloned();
Self::after_decode(&mut map).await?;
translate_enabled.then(|| Self::translate_model(&mut map));
Self::after_decode(&mut map).await?;
if let Some(key) = primary_key {
associations.push((key, map));
}
Expand Down Expand Up @@ -1280,8 +1280,8 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
for row in rows {
let mut map = Map::decode_row(&row)?;
let primary_key = map.get(primary_key_name).cloned();
Self::after_decode(&mut map).await?;
translate_enabled.then(|| Self::translate_model(&mut map));
Self::after_decode(&mut map).await?;
if let Some(key) = primary_key {
associations.push((key, map));
}
Expand Down Expand Up @@ -1368,8 +1368,8 @@ pub trait Schema: 'static + Send + Sync + ModelHooks {
let mut data = Self::lookup::<M, Map>(query, join_on).await?;
let translate_enabled = query.translate_enabled();
for model in data.iter_mut() {
Self::after_decode(model).await?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await?;
}
serde_json::from_value(data.into()).map_err(Error::from)
}
Expand Down
15 changes: 7 additions & 8 deletions crates/zino/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,6 @@ where
}

let mut model_snapshot = model.snapshot();
Self::after_decode(&mut model_snapshot)
.await
.extract(&req)?;

let ctx = model.insert().await.extract(&req)?;
if let Some(last_insert_id) = ctx.last_insert_id() {
if model_snapshot.get_i64("id") == Some(0) {
Expand All @@ -119,6 +115,9 @@ where
}

Self::translate_model(&mut model_snapshot);
Self::after_decode(&mut model_snapshot)
.await
.extract(&req)?;
Self::before_respond(&mut model_snapshot, extension.as_ref())
.await
.extract(&req)?;
Expand Down Expand Up @@ -169,7 +168,7 @@ where
}

async fn list(req: Self::Request) -> Self::Result {
let mut query = match req.get_query("mode") {
let mut query = match req.get_query("query_mode") {
Some("full") => Self::default_query(),
Some("snapshot") => Self::default_snapshot_query(),
_ => Self::default_list_query(),
Expand All @@ -192,8 +191,8 @@ where
let mut models = Self::find(&query).await.extract(&req)?;
let translate_enabled = query.translate_enabled();
for model in models.iter_mut() {
Self::after_decode(model).await.extract(&req)?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await.extract(&req)?;
Self::before_respond(model, extension.as_ref())
.await
.extract(&req)?;
Expand Down Expand Up @@ -475,8 +474,8 @@ where
let mut models = Self::find(&query).await.extract(&req)?;
let translate_enabled = query.translate_enabled();
for model in models.iter_mut() {
Self::after_decode(model).await.extract(&req)?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await.extract(&req)?;
Self::before_respond(model, extension.as_ref())
.await
.extract(&req)?;
Expand Down Expand Up @@ -508,8 +507,8 @@ where
let mut models = Self::find(&query).await.extract(&req)?;
let translate_enabled = query.translate_enabled();
for model in models.iter_mut() {
Self::after_decode(model).await.extract(&req)?;
translate_enabled.then(|| Self::translate_model(model));
Self::after_decode(model).await.extract(&req)?;
}
models
};
Expand Down

0 comments on commit c9d4d9b

Please sign in to comment.