Skip to content

Commit

Permalink
Add support for percent-decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Dec 12, 2023
1 parent 85804bc commit 65605f8
Show file tree
Hide file tree
Showing 6 changed files with 49 additions and 33 deletions.
2 changes: 2 additions & 0 deletions zino-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ mime = "0.3.17"
mime_guess = "2.0.4"
multer = "2.1.0"
parking_lot = "0.12.1"
percent-encoding = "2.3.1"
rand = "0.8.5"
regex = "1.10.2"
reqwest-middleware = "0.2.4"
Expand All @@ -208,6 +209,7 @@ task-local-extensions = "0.1.4"
toml = "0.8.8"
tracing = "0.1.40"
tracing-appender = "0.2.3"
tracing-log = "0.2.0"
url = "2.5.0"

[dependencies.argon2]
Expand Down
4 changes: 4 additions & 0 deletions zino-core/src/application/tracing_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use tracing_appender::{
non_blocking::WorkerGuard,
rolling::{RollingFileAppender, Rotation},
};
use tracing_log::LogTracer;
use tracing_subscriber::{
filter::{EnvFilter, LevelFilter},
fmt::{time::OffsetTime, writer::MakeWriterExt},
Expand All @@ -19,6 +20,9 @@ pub(super) fn init<APP: Application + ?Sized>() {
return;
}

// Convert log records to tracing events.
LogTracer::init().expect("fail to initialize the log tracer");

// Initialize `OffsetTime` before forking threads.
let local_offset_time = OffsetTime::local_rfc_3339().expect("could not get local offset");

Expand Down
13 changes: 12 additions & 1 deletion zino-core/src/request/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,11 @@ pub trait RequestContext {

/// Gets the route parameter by name.
/// The name should not include `:`, `*`, `{` or `}`.
///
/// # Note
///
/// Please note that it does not handle the percent-decoding.
/// You can use [`parse_param()`](Self::parse_param) if you need percent-decoding.
fn get_param(&self, name: &str) -> Option<&str> {
const CAPTURES: [char; 4] = [':', '*', '{', '}'];
if let Some(index) = self
Expand All @@ -267,7 +272,8 @@ pub trait RequestContext {
<T as FromStr>::Err: std::error::Error,
{
if let Some(param) = self.get_param(name) {
param
percent_encoding::percent_decode_str(param)
.decode_utf8_lossy()
.parse::<T>()
.map_err(|err| Rejection::from_validation_entry(name.to_owned(), err).context(self))
} else {
Expand All @@ -280,6 +286,11 @@ pub trait RequestContext {
}

/// Gets the query value of the URI by name.
///
/// # Note
///
/// Please note that it does not handle the percent-decoding.
/// You can use [`parse_query()`](Self::parse_query) if you need percent-decoding.
fn get_query(&self, name: &str) -> Option<&str> {
self.original_uri().query()?.split('&').find_map(|param| {
if let Some((key, value)) = param.split_once('=')
Expand Down
32 changes: 16 additions & 16 deletions zino-derive/src/model.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::parser;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Data, DeriveInput, Fields};
Expand Down Expand Up @@ -68,24 +69,23 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
if enable_setter && !RESERVED_FIELDS.contains(&name.as_str()) {
let setter = if type_name == "String" {
if name == "password" {
if is_inherent {
quote! {
if let Some(password) = data.parse_string("password") {
match Self::encrypt_password(&password) {
Ok(password) => self.password = password,
Err(err) => validation.record_fail("password", err),
}
if is_inherent {
let parser_ident = format_ident!("parse_{}", name.to_case(Case::Snake));
quote! {
if let Some(value) = data.parse_string(#name) {
match Self::#parser_ident(&value) {
Ok(value) => self.#ident = value,
Err(err) => validation.record_fail(#name, err),
}
}
} else {
quote! {
if let Some(password) = data.parse_string("password") {
use zino_core::orm::ModelHelper;
match Self::encrypt_password(&password) {
Ok(password) => self.password = password,
Err(err) => validation.record_fail("password", err),
}
}
} else if name == "password" {
quote! {
if let Some(password) = data.parse_string(#name) {
use zino_core::orm::ModelHelper;
match Self::encrypt_password(&password) {
Ok(password) => self.password = password,
Err(err) => validation.record_fail(#name, err),
}
}
}
Expand Down
25 changes: 12 additions & 13 deletions zino-derive/src/model_accessor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
let name = ident.to_string();
let mut field_alias = None;
for attr in field.attrs.iter() {
let type_name = type_name.as_str();
let arguments = parser::parse_schema_attr(attr);
let is_readable = arguments.iter().all(|arg| arg.0 != "write_only");
for (key, value) in arguments.into_iter() {
Expand Down Expand Up @@ -115,8 +116,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
validation.record(#name, "it is a nonexistent value");
}
});
} else if type_name == "Option<Uuid>"
|| type_name == "Option<String>"
} else if matches!(type_name, "Option<Uuid>" | "Option<String>")
{
field_constraints.push(quote! {
if let Some(value) = self.#ident {
Expand All @@ -127,8 +127,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
});
} else if type_name == "Vec<Uuid>" || type_name == "Vec<String>"
{
} else if matches!(type_name, "Vec<Uuid>" | "Vec<String>") {
field_constraints.push(quote! {
let values = self.#ident
.iter()
Expand All @@ -142,7 +141,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
});
} else if parser::check_vec_type(&type_name) {
} else if parser::check_vec_type(type_name) {
field_constraints.push(quote! {
let values = self.#ident.clone();
let length = values.len();
Expand All @@ -153,7 +152,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
});
} else if parser::check_option_type(&type_name) {
} else if parser::check_option_type(type_name) {
field_constraints.push(quote! {
if let Some(value) = self.#ident {
let values = vec![value.clone()];
Expand All @@ -177,7 +176,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
} else {
model_references.insert(value, vec![name.clone()]);
}
if parser::check_vec_type(&type_name) {
if parser::check_vec_type(type_name) {
sample_queries.push(quote! {
if let Some(col) = Self::get_column(#name) {
let size = col.random_size();
Expand Down Expand Up @@ -239,7 +238,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
});
} else if parser::check_option_type(&type_name) {
} else if parser::check_option_type(type_name) {
field_constraints.push(quote! {
if let Some(value) = self.#ident {
let columns = [(#name, value.into())];
Expand Down Expand Up @@ -276,8 +275,8 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
"nonempty" if is_readable => {
if parser::check_vec_type(&type_name)
|| matches!(type_name.as_str(), "String" | "Map")
if parser::check_vec_type(type_name)
|| matches!(type_name, "String" | "Map")
{
field_constraints.push(quote! {
if self.#ident.is_empty() {
Expand Down Expand Up @@ -418,7 +417,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
"max_items" => {
if let Some(length) = value.and_then(|s| s.parse::<usize>().ok())
&& parser::check_vec_type(&type_name)
&& parser::check_vec_type(type_name)
{
field_constraints.push(quote! {
let length = #length;
Expand All @@ -431,7 +430,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
"min_items" => {
if let Some(length) = value.and_then(|s| s.parse::<usize>().ok())
&& parser::check_vec_type(&type_name)
&& parser::check_vec_type(type_name)
{
field_constraints.push(quote! {
let length = #length;
Expand All @@ -443,7 +442,7 @@ pub(super) fn parse_token_stream(input: DeriveInput) -> TokenStream {
}
}
"unique_items" => {
if parser::check_vec_type(&type_name) {
if parser::check_vec_type(type_name) {
field_constraints.push(quote! {
let slice = self.#ident.as_slice();
for index in 1..slice.len() {
Expand Down
6 changes: 3 additions & 3 deletions zino/src/controller/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,12 @@ where

async fn list(req: Self::Request) -> Self::Result {
let mut query = Self::default_list_query();
let mut res = req.query_validation(&mut query)?;
let extension = req.get_data::<<Self as ModelHooks>::Extension>();
Self::before_list(&mut query, extension.as_ref())
.await
.extract(&req)?;

let mut res = req.query_validation(&mut query)?;
let models = if query.populate_enabled() {
let mut models = Self::fetch(&query).await.extract(&req)?;
for model in models.iter_mut() {
Expand Down Expand Up @@ -331,12 +331,12 @@ where

async fn export(req: Self::Request) -> Self::Result {
let mut query = Self::default_query();
let mut res = req.query_validation(&mut query)?;
let extension = req.get_data::<<Self as ModelHooks>::Extension>();
Self::before_list(&mut query, extension.as_ref())
.await
.extract(&req)?;

let mut res = req.query_validation(&mut query)?;
let mut models = Self::find(&query).await.extract(&req)?;
let translate_enabled = query.translate_enabled();
for model in models.iter_mut() {
Expand All @@ -359,12 +359,12 @@ where

async fn tree(req: Self::Request) -> Self::Result {
let mut query = Self::default_list_query();
let mut res = req.query_validation(&mut query)?;
let extension = req.get_data::<<Self as ModelHooks>::Extension>();
Self::before_list(&mut query, extension.as_ref())
.await
.extract(&req)?;

let mut res = req.query_validation(&mut query)?;
let parent_id = req.get_query("parent_id").unwrap_or("null");
query.add_filter("parent_id", parent_id);

Expand Down

0 comments on commit 65605f8

Please sign in to comment.