Skip to content

Commit

Permalink
allow morph field select
Browse files Browse the repository at this point in the history
  • Loading branch information
shrynx committed Mar 2, 2024
1 parent 2399c9d commit 9823b1d
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 38 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "struct_morph"
version = "0.5.0"
version = "0.6.0"
edition = "2021"
license = "MIT"
description = "macro for morphing one struct into another."
Expand Down
21 changes: 19 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cargo add struct_morph
or manually

```toml
struct_morph = "0.5"
struct_morph = "0.6"
```

## Usage
Expand Down Expand Up @@ -50,7 +50,8 @@ use struct_morph::{morph, morph_field};
#[morph(ProductRow)]
struct ProductInfo {
id: i32,
name: String,
#[morph_field(select = name)]
title: String,
description: String,
#[morph_field(transform = "is_available")]
is_available: bool,
Expand Down Expand Up @@ -86,3 +87,19 @@ let product_info: ProductInfo = ProductInfo::from(product_row);

This will copy the values for fields with same name (and type) and for the rest one can provide customer transform functions.
It does so by implementing a `From` trait for the source and target struct.

There are 2 types field modifiers

- transform

```rust
#[morph_field(transform = "transform_func")]
```
this takes an transform function which takes &SourceStruct as a param and returns the correct type

- select

```rust
#[morph_field(select = source_field)]
```
this takes a source field to replace the value for the field
45 changes: 24 additions & 21 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@
//! # base_price: i32,
//! # discount: i32
//! # }
//! #
//! #
//! # #[derive(Debug)]
//! # #[morph(ProductRow)]
//! # struct ProductInfo {
//! # id: i32,
//! # name: String,
//! # #[morph_field(select = name)]
//! # title: String,
//! # description: String,
//! # #[morph_field(transform = "is_available")]
//! # is_available: bool,
Expand All @@ -42,8 +43,8 @@
//! # base_price: 50,
//! # discount: 10,
//! # };
//! #
//! # let product_info: ProductInfo = ProductInfo::from(product_row.clone());
//! #
//! # let product_info: ProductInfo = ProductInfo::from(product_row.clone());
//! #
//! # println!("{:?}", product_row);
//! # println!("{:?}", product_info);
Expand All @@ -61,24 +62,24 @@ use syn::{
parse_macro_input, Ident, ItemStruct, LitStr, Token,
};

struct MorphFieldArgs {
transform_function_name: LitStr,
enum MorphFieldArgs {
TransformFunction(LitStr),
SelectField(Ident),
}

impl Parse for MorphFieldArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let transform_keyword: Ident = input.parse()?;
let morph_keyword: Ident = input.parse()?;
let _eq_token: Token![=] = input.parse()?;

if transform_keyword != "transform" {
return Err(syn::Error::new_spanned(
transform_keyword,
"Expected 'transform' followed by function name",
));
match morph_keyword {
t if t == "transform" => Ok(MorphFieldArgs::TransformFunction(input.parse()?)),
t if t == "select" => Ok(MorphFieldArgs::SelectField(input.parse()?)),
_ => Err(syn::Error::new_spanned(
morph_keyword,
"Expected either 'transform' or 'select'",
)),
}
Ok(Self {
transform_function_name: input.parse()?,
})
}
}

Expand All @@ -98,18 +99,20 @@ pub fn morph(attr: TokenStream, item: TokenStream) -> TokenStream {
.iter()
.map(|f| {
let field_name = &f.ident;
let transform_function = f.attrs.iter().find_map(|attr| {
let morph_field_args = f.attrs.iter().find_map(|attr| {
attr.path().is_ident("morph_field").then(|| {
let args: MorphFieldArgs = attr.parse_args().unwrap();
args.transform_function_name.value()
args
})
});

match transform_function {
Some(func) => {
let func_ident = Ident::new(&func, proc_macro2::Span::call_site());
match morph_field_args {
Some(MorphFieldArgs::TransformFunction(func)) => {
let func_ident = Ident::new(&func.value(), proc_macro2::Span::call_site());
quote! { #field_name: #func_ident(&source) }
}
Some(MorphFieldArgs::SelectField(source_field)) => {
quote! { #field_name: source.#source_field.clone() }
}
None => quote! { #field_name: source.#field_name.clone() },
}
})
Expand Down
44 changes: 31 additions & 13 deletions tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ struct Foo {
d: (String, String),
}

impl Foo {
fn sample() -> Self {
Foo {
a: 10,
b: "Hello".to_string(),
c: vec![1, 3, 4],
d: ("Good".to_string(), "Bye".to_string()),
}
}
}

#[morph(Foo)]
struct Bar {
a: u16,
Expand All @@ -17,12 +28,7 @@ struct Bar {

#[test]
fn simple() {
let my_foo: Foo = Foo {
a: 10,
b: "Hello".to_string(),
c: vec![1, 3, 4],
d: ("Good".to_string(), "Bye".to_string()),
};
let my_foo: Foo = Foo::sample();

let auto_bar: Bar = Bar::from(my_foo.clone());

Expand Down Expand Up @@ -50,13 +56,8 @@ fn foo_d_sec_len(value: &Foo) -> usize {
}

#[test]
fn with_field_transform() {
let my_foo: Foo = Foo {
a: 10,
b: "Hello".to_string(),
c: vec![1, 3, 4],
d: ("Good".to_string(), "Bye".to_string()),
};
fn with_transform() {
let my_foo: Foo = Foo::sample();

let auto_baz: Baz = Baz::from(my_foo.clone());

Expand All @@ -65,3 +66,20 @@ fn with_field_transform() {
assert_eq!(foo_d_first(&my_foo), auto_baz.e);
assert_eq!(foo_d_sec_len(&my_foo), auto_baz.f);
}

#[morph(Foo)]
struct Qux {
c: Vec<u32>,
#[morph_field(select = a)]
f: u16,
}

#[test]
fn with_select() {
let my_foo: Foo = Foo::sample();

let auto_qux: Qux = Qux::from(my_foo.clone());

assert_eq!(my_foo.c, auto_qux.c);
assert_eq!(my_foo.a, auto_qux.f);
}

0 comments on commit 9823b1d

Please sign in to comment.