Skip to content

Commit

Permalink
Add with field attribute for the FromRow derive macro
Browse files Browse the repository at this point in the history
  • Loading branch information
blackbeam committed Jan 1, 2024
1 parent c1a7aaf commit ad27648
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 3 deletions.
6 changes: 5 additions & 1 deletion derive/src/from_row/structs/attrs/field.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use darling::FromAttributes;
use darling::{util::SpannedValue, FromAttributes};

use crate::from_value::structs::attrs::container::FnPath;

#[derive(Debug, Default, FromAttributes)]
#[darling(attributes(mysql))]
Expand All @@ -7,4 +9,6 @@ pub struct Mysql {
pub json: bool,
#[darling(default)]
pub rename: Option<String>,
#[darling(default)]
pub with: Option<SpannedValue<FnPath>>,
}
15 changes: 13 additions & 2 deletions derive/src/from_row/structs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,17 @@ impl ToTokens for GenericStruct<'_> {
quote::quote!(<#ty as FromValue>::Intermediate)
};

let try_from = if let Some(ref path) = attrs.with {
let path = &path.0;
quote::quote!( #path(x) )
} else {
quote::quote!( <#intermediate_ty as std::convert::TryFrom<Value>>::try_from(x) )
};

quote::quote!(
let #ident = {
let val = match row.take_opt::<Value, &str>(#lit) {
Some(Ok(x)) => match <#intermediate_ty as std::convert::TryFrom<Value>>::try_from(x) {
Some(Ok(x)) => match #try_from {
Ok(x) => Some(x),
Err(e) => {
row.place(*indexes.get(#lit).unwrap(), e.0);
Expand Down Expand Up @@ -210,7 +217,11 @@ impl ToTokens for GenericStruct<'_> {
if attrs.json {
quote::quote!(#ident: #ident.commit().0)
} else {
quote::quote!(#ident: <<#ty as FromValue>::Intermediate as std::convert::Into<#ty>>::into(#ident))
if attrs.with.is_some() {
quote::quote!( #ident )
} else {
quote::quote!(#ident: <<#ty as FromValue>::Intermediate as std::convert::Into<#ty>>::into(#ident))
}
}
})
.collect::<Vec<_>>();
Expand Down
80 changes: 80 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@
//! * `#[mysql(rename = "some_name")]` – overrides column name of a field
//! * `#[mysql(json)]` - column will be interpreted as a JSON string containing
//! a value of a field type
//! * `#[mysql(with = path::to::convert_fn)]` – `convert_fn` will be used to deserialize
//! a field value (expects a function with a signature that mimics
//! `TryFrom<Value, Error=FromValueError>``)
//!
//! ### Example
//!
Expand Down Expand Up @@ -647,3 +650,80 @@ fn from_value_is_integer() {
assert_eq!(Value::from(SomeTypeIsInteger::SecondVariant), Value::Int(2));
assert_eq!(Value::from(SomeTypeIsInteger::ThirdVariant), Value::Int(3));
}

#[cfg(test)]
mod tests {
use crate::row::convert::FromRow;
use crate::{constants::ColumnType, packets::Column, row::new_row};

#[test]
fn from_row_derive() {
#[derive(FromRow)]
#[mysql(table_name = "Foos", rename_all = "camelCase")]
struct Foo {
id: u64,
text_data: String,
#[mysql(json)]
json_data: serde_json::Value,
#[mysql(with = "from_literal", rename = "custom")]
custom_bool: bool,
}

fn from_literal(value: crate::Value) -> Result<bool, crate::FromValueError> {
match value {
crate::Value::Bytes(x) if x == b"true" => Ok(true),
crate::Value::Bytes(x) if x == b"false" => Ok(false),
x => Err(crate::FromValueError(x)),
}
}

assert_eq!(Foo::TABLE_NAME, "Foos");
assert_eq!(Foo::ID_FIELD, "id");
assert_eq!(Foo::TEXT_DATA_FIELD, "textData");
assert_eq!(Foo::JSON_DATA_FIELD, "jsonData");
assert_eq!(Foo::CUSTOM_BOOL_FIELD, "custom");

let columns = vec![
Column::new(ColumnType::MYSQL_TYPE_LONGLONG)
.with_name(b"id")
.with_org_name(b"id")
.with_table(b"Foos")
.with_org_table(b"Foos"),
Column::new(ColumnType::MYSQL_TYPE_VARCHAR)
.with_name(b"textData")
.with_org_name(b"textData")
.with_table(b"Foos")
.with_org_table(b"Foos"),
Column::new(ColumnType::MYSQL_TYPE_JSON)
.with_name(b"jsonData")
.with_org_name(b"jsonData")
.with_table(b"Foos")
.with_org_table(b"Foos"),
Column::new(ColumnType::MYSQL_TYPE_VARCHAR)
.with_name(b"custom")
.with_org_name(b"custom")
.with_table(b"Foos")
.with_org_table(b"Foos"),
];

let row = new_row(
vec![
crate::Value::Int(10),
crate::Value::Bytes(b"bytes".into()),
crate::Value::Bytes(b"[true,false,\"not found\"]".into()),
crate::Value::Bytes(b"true".into()),
],
columns.into(),
);

let deserialized = Foo::from_row(row);

assert_eq!(deserialized.id, 10);
assert_eq!(deserialized.text_data, "bytes");
assert_eq!(
deserialized.json_data.to_string(),
"[true,false,\"not found\"]"
);
assert!(deserialized.custom_bool);
}
}

0 comments on commit ad27648

Please sign in to comment.