Skip to content

Commit

Permalink
Add full support for DecodeRow derivation
Browse files Browse the repository at this point in the history
  • Loading branch information
photino committed Nov 16, 2023
1 parent 8be0af2 commit 9ab563d
Show file tree
Hide file tree
Showing 33 changed files with 282 additions and 181 deletions.
4 changes: 2 additions & 2 deletions examples/actix-app/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ fn file_router(cfg: &mut ServiceConfig) {

fn user_router(cfg: &mut ServiceConfig) {
cfg.route("/user/new", post().to(user::new))
.route("/user/{id}/delete", post().to(User::delete))
.route("/user/{id}/delete", post().to(User::soft_delete))
.route("/user/{id}/update", post().to(User::update))
.route("/user/{id}/view", get().to(user::view))
.route("/user/list", get().to(User::list))
Expand All @@ -54,7 +54,7 @@ fn user_router(cfg: &mut ServiceConfig) {

fn tag_router(cfg: &mut ServiceConfig) {
cfg.route("/tag/new", post().to(Tag::new))
.route("/tag/{id}/delete", post().to(Tag::delete))
.route("/tag/{id}/delete", post().to(Tag::soft_delete))
.route("/tag/{id}/update", post().to(Tag::update))
.route("/tag/{id}/view", get().to(Tag::view))
.route("/tag/list", get().to(Tag::list))
Expand Down
4 changes: 2 additions & 2 deletions examples/axum-app/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub fn routes() -> Vec<Router> {
// User controller.
let router = Router::new()
.route("/user/new", post(user::new))
.route("/user/:id/delete", post(User::delete))
.route("/user/:id/delete", post(User::soft_delete))
.route("/user/:id/update", post(User::update))
.route("/user/:id/view", get(user::view))
.route("/user/list", get(User::list))
Expand All @@ -43,7 +43,7 @@ pub fn routes() -> Vec<Router> {
// Tag controller.
let router = Router::new()
.route("/tag/new", post(Tag::new))
.route("/tag/:id/delete", post(Tag::delete))
.route("/tag/:id/delete", post(Tag::soft_delete))
.route("/tag/:id/update", post(Tag::update))
.route("/tag/:id/view", get(Tag::view))
.route("/tag/list", get(Tag::list))
Expand Down
2 changes: 1 addition & 1 deletion zino-core/src/application/tracing_subscriber.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ pub(super) fn init<APP: Application + ?Sized>() {
.filename_suffix("log")
.max_log_files(max_log_files.try_into().unwrap_or(1))
.build(rolling_file_dir)
.expect("initializing rolling file appender failed");
.expect("fail to initialize the rolling file appender");
let (non_blocking_appender, worker_guard) = tracing_appender::non_blocking(file_appender);
let stdout = if in_dev_mode {
io::stdout.with_max_level(Level::DEBUG)
Expand Down
13 changes: 10 additions & 3 deletions zino-core/src/extension/json_object.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use super::JsonValueExt;
use crate::{
datetime::{self, DateTime},
helper, openapi, JsonValue, Map, Record, Uuid,
Expand Down Expand Up @@ -414,14 +415,20 @@ impl JsonObjectExt for Map {
fn parse_array<T: FromStr>(&self, key: &str) -> Option<Vec<T>> {
self.get(key)
.and_then(|v| match v {
JsonValue::String(s) => Some(helper::parse_str_array(s)),
JsonValue::Array(vec) => Some(vec.iter().filter_map(|v| v.as_str()).collect()),
JsonValue::String(s) => helper::parse_str_array(s)
.into_iter()
.filter_map(|s| (!s.is_empty()).then_some(Cow::Borrowed(s)))
.collect::<Vec<_>>()
.into(),
JsonValue::Array(vec) => {
Some(vec.iter().filter_map(|v| v.parse_string()).collect())
}
_ => None,
})
.and_then(|values| {
let vec = values
.iter()
.filter_map(|s| if s.is_empty() { None } else { s.parse().ok() })
.filter_map(|s| s.parse().ok())
.collect::<Vec<_>>();
(!vec.is_empty()).then_some(vec)
})
Expand Down
10 changes: 7 additions & 3 deletions zino-core/src/extension/json_value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,13 +282,17 @@ impl JsonValueExt for JsonValue {

fn parse_array<T: FromStr>(&self) -> Option<Vec<T>> {
let values = match &self {
JsonValue::String(s) => Some(helper::parse_str_array(s)),
JsonValue::Array(vec) => Some(vec.iter().filter_map(|v| v.as_str()).collect()),
JsonValue::String(s) => helper::parse_str_array(s)
.into_iter()
.filter_map(|s| (!s.is_empty()).then_some(Cow::Borrowed(s)))
.collect::<Vec<_>>()
.into(),
JsonValue::Array(vec) => Some(vec.iter().filter_map(|v| v.parse_string()).collect()),
_ => None,
};
let vec = values?
.iter()
.filter_map(|s| if s.is_empty() { None } else { s.parse().ok() })
.filter_map(|s| s.parse().ok())
.collect::<Vec<_>>();
(!vec.is_empty()).then_some(vec)
}
Expand Down
22 changes: 20 additions & 2 deletions zino-core/src/orm/decode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ where
}
}

/// Decodes a single value for the field in a row.
/// Decodes a single value as `T` for the field in a row.
#[inline]
pub fn decode<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<T, Error>
where
Expand All @@ -23,9 +23,27 @@ where
row.try_get_unchecked(field).map_err(Error::from)
}

/// Decodes a single value as `Vec<T>` for the field in a row.
#[inline]
pub fn decode_array<'r, T>(row: &'r DatabaseRow, field: &str) -> Result<Vec<T>, Error>
where
T: Decode<'r, DatabaseDriver> + std::str::FromStr,
{
#[cfg(feature = "orm-postgres")]
{
row.try_get_unchecked(field).map_err(Error::from)
}
#[cfg(not(feature = "orm-postgres"))]
{
use crate::{extension::JsonValueExt, JsonValue};

decode::<JsonValue>(row, field).map(|value| value.parse_array().unwrap_or_default())
}
}

/// Decodes a raw value at the index.
#[inline]
pub(super) fn decode_column<'r, T>(
pub(super) fn decode_raw<'r, T>(
field: &str,
value: <DatabaseDriver as HasValueRef<'r>>::ValueRef,
) -> Result<T, sqlx::Error>
Expand Down
4 changes: 2 additions & 2 deletions zino-core/src/orm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ mod query;
mod schema;

pub use accessor::ModelAccessor;
pub use decode::decode;
pub use decode::{decode, decode_array};
pub use helper::ModelHelper;
pub use schema::Schema;

Expand Down Expand Up @@ -317,7 +317,7 @@ static SHARED_CONNECTION_POOLS: LazyLock<ConnectionPools> = LazyLock::new(|| {
if database_type == driver {
tracing::warn!(
driver,
"connect to the {} database services lazily",
"connect to {} database services lazily",
DatabaseDriver::NAME
);
} else {
Expand Down
86 changes: 40 additions & 46 deletions zino-core/src/orm/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,35 +379,35 @@ impl DecodeRow<DatabaseRow> for Map {
let value = if raw_value.is_null() {
JsonValue::Null
} else {
use super::decode::decode_column;
use super::decode::decode_raw;
match col.type_info().name() {
"BOOLEAN" => decode_column::<bool>(field, raw_value)?.into(),
"TINYINT" => decode_column::<i8>(field, raw_value)?.into(),
"TINYINT UNSIGNED" => decode_column::<u8>(field, raw_value)?.into(),
"SMALLINT" => decode_column::<i16>(field, raw_value)?.into(),
"SMALLINT UNSIGNED" => decode_column::<u16>(field, raw_value)?.into(),
"INT" => decode_column::<i32>(field, raw_value)?.into(),
"INT UNSIGNED" => decode_column::<u32>(field, raw_value)?.into(),
"BIGINT" => decode_column::<i64>(field, raw_value)?.into(),
"BIGINT UNSIGNED" => decode_column::<u64>(field, raw_value)?.into(),
"FLOAT" => decode_column::<f32>(field, raw_value)?.into(),
"DOUBLE" => decode_column::<f64>(field, raw_value)?.into(),
"BOOLEAN" => decode_raw::<bool>(field, raw_value)?.into(),
"TINYINT" => decode_raw::<i8>(field, raw_value)?.into(),
"TINYINT UNSIGNED" => decode_raw::<u8>(field, raw_value)?.into(),
"SMALLINT" => decode_raw::<i16>(field, raw_value)?.into(),
"SMALLINT UNSIGNED" => decode_raw::<u16>(field, raw_value)?.into(),
"INT" => decode_raw::<i32>(field, raw_value)?.into(),
"INT UNSIGNED" => decode_raw::<u32>(field, raw_value)?.into(),
"BIGINT" => decode_raw::<i64>(field, raw_value)?.into(),
"BIGINT UNSIGNED" => decode_raw::<u64>(field, raw_value)?.into(),
"FLOAT" => decode_raw::<f32>(field, raw_value)?.into(),
"DOUBLE" => decode_raw::<f64>(field, raw_value)?.into(),
"NUMERIC" => {
let value = decode_column::<Decimal>(field, raw_value)?;
let value = decode_raw::<Decimal>(field, raw_value)?;
serde_json::to_value(value)?
}
"TIMESTAMP" => decode_column::<DateTime>(field, raw_value)?.into(),
"DATETIME" => decode_column::<NaiveDateTime>(field, raw_value)?
"TIMESTAMP" => decode_raw::<DateTime>(field, raw_value)?.into(),
"DATETIME" => decode_raw::<NaiveDateTime>(field, raw_value)?
.to_string()
.into(),
"DATE" => decode_column::<NaiveDate>(field, raw_value)?
"DATE" => decode_raw::<NaiveDate>(field, raw_value)?
.to_string()
.into(),
"TIME" => decode_column::<NaiveTime>(field, raw_value)?
"TIME" => decode_raw::<NaiveTime>(field, raw_value)?
.to_string()
.into(),
"BYTE" | "BINARY" | "VARBINARY" | "BLOB" => {
let bytes = decode_column::<Vec<u8>>(field, raw_value)?;
let bytes = decode_raw::<Vec<u8>>(field, raw_value)?;
if bytes.len() == 16 {
if let Ok(value) = Uuid::from_slice(&bytes) {
value.to_string().into()
Expand All @@ -418,8 +418,8 @@ impl DecodeRow<DatabaseRow> for Map {
bytes.into()
}
}
"JSON" => decode_column::<JsonValue>(field, raw_value)?,
_ => decode_column::<String>(field, raw_value)?.into(),
"JSON" => decode_raw::<JsonValue>(field, raw_value)?,
_ => decode_raw::<String>(field, raw_value)?.into(),
}
};
if !value.is_ignorable() {
Expand All @@ -443,40 +443,34 @@ impl DecodeRow<DatabaseRow> for Record {
let value = if raw_value.is_null() {
AvroValue::Null
} else {
use super::decode::decode_column;
use super::decode::decode_raw;
match col.type_info().name() {
"BOOLEAN" => decode_column::<bool>(field, raw_value)?.into(),
"TINYINT" => i32::from(decode_column::<i8>(field, raw_value)?).into(),
"TINYINT UNSIGNED" => i32::from(decode_column::<u8>(field, raw_value)?).into(),
"SMALLINT" => i32::from(decode_column::<i16>(field, raw_value)?).into(),
"SMALLINT UNSIGNED" => {
i32::from(decode_column::<u16>(field, raw_value)?).into()
}
"INT" => decode_column::<i32>(field, raw_value)?.into(),
"INT UNSIGNED" => {
i32::try_from(decode_column::<u32>(field, raw_value)?)?.into()
}
"BIGINT" => decode_column::<i64>(field, raw_value)?.into(),
"BOOLEAN" => decode_raw::<bool>(field, raw_value)?.into(),
"TINYINT" => i32::from(decode_raw::<i8>(field, raw_value)?).into(),
"TINYINT UNSIGNED" => i32::from(decode_raw::<u8>(field, raw_value)?).into(),
"SMALLINT" => i32::from(decode_raw::<i16>(field, raw_value)?).into(),
"SMALLINT UNSIGNED" => i32::from(decode_raw::<u16>(field, raw_value)?).into(),
"INT" => decode_raw::<i32>(field, raw_value)?.into(),
"INT UNSIGNED" => i32::try_from(decode_raw::<u32>(field, raw_value)?)?.into(),
"BIGINT" => decode_raw::<i64>(field, raw_value)?.into(),
"BIGINT UNSIGNED" => {
i64::try_from(decode_column::<u64>(field, raw_value)?)?.into()
i64::try_from(decode_raw::<u64>(field, raw_value)?)?.into()
}
"FLOAT" => decode_column::<f32>(field, raw_value)?.into(),
"DOUBLE" => decode_column::<f64>(field, raw_value)?.into(),
"NUMERIC" => decode_column::<Decimal>(field, raw_value)?
.to_string()
.into(),
"TIMESTAMP" => decode_column::<DateTime>(field, raw_value)?.into(),
"DATETIME" => decode_column::<NaiveDateTime>(field, raw_value)?
"FLOAT" => decode_raw::<f32>(field, raw_value)?.into(),
"DOUBLE" => decode_raw::<f64>(field, raw_value)?.into(),
"NUMERIC" => decode_raw::<Decimal>(field, raw_value)?.to_string().into(),
"TIMESTAMP" => decode_raw::<DateTime>(field, raw_value)?.into(),
"DATETIME" => decode_raw::<NaiveDateTime>(field, raw_value)?
.to_string()
.into(),
"DATE" => decode_column::<NaiveDate>(field, raw_value)?
"DATE" => decode_raw::<NaiveDate>(field, raw_value)?
.to_string()
.into(),
"TIME" => decode_column::<NaiveTime>(field, raw_value)?
"TIME" => decode_raw::<NaiveTime>(field, raw_value)?
.to_string()
.into(),
"BYTE" | "BINARY" | "VARBINARY" | "BLOB" => {
let bytes = decode_column::<Vec<u8>>(field, raw_value)?;
let bytes = decode_raw::<Vec<u8>>(field, raw_value)?;
if bytes.len() == 16 {
if let Ok(value) = Uuid::from_slice(&bytes) {
value.into()
Expand All @@ -487,8 +481,8 @@ impl DecodeRow<DatabaseRow> for Record {
bytes.into()
}
}
"JSON" => decode_column::<JsonValue>(field, raw_value)?.into(),
_ => decode_column::<String>(field, raw_value)?.into(),
"JSON" => decode_raw::<JsonValue>(field, raw_value)?.into(),
_ => decode_raw::<String>(field, raw_value)?.into(),
}
};
record.push((field.to_owned(), value));
Expand Down
Loading

0 comments on commit 9ab563d

Please sign in to comment.