├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── src ├── ident_as_literal_list.rs └── lib.rs ├── structural-convert-derive ├── Cargo.toml ├── README.md └── src │ ├── lib.rs │ ├── structural_convert.rs │ └── structural_convert │ ├── attributes.rs │ ├── attributes │ ├── enum_variant │ │ ├── from_enum_variant_attributes.rs │ │ ├── into_enum_variant_attributes.rs │ │ ├── mod.rs │ │ ├── try_from_enum_variant_attributes.rs │ │ └── try_into_enum_variant_attributes.rs │ ├── from_container_attributes.rs │ ├── ident_as_literal_list.rs │ ├── into_container_attributes.rs │ ├── named_field │ │ ├── from_field_named_attributes.rs │ │ ├── into_field_named_attributes.rs │ │ ├── mod.rs │ │ ├── try_from_field_named_attributes.rs │ │ └── try_into_field_named_attributes.rs │ ├── try_from_container_attributes.rs │ └── try_into_container_attributes.rs │ ├── conversion_error.rs │ ├── on_enum_data.rs │ ├── on_enum_data │ ├── create_from_impl_for_enum.rs │ ├── create_into_impl_for_enum.rs │ ├── create_try_from_impl_for_enum.rs │ ├── create_try_into_impl_for_enum.rs │ └── utils.rs │ ├── on_field_type.rs │ ├── on_fields_named.rs │ ├── on_fields_named │ ├── create_from_match_branch_for_fields_named.rs │ ├── create_into_match_branch_for_fields_named.rs │ ├── create_match_branch_for_fields_named.rs │ ├── create_try_from_match_branch_for_fields_named.rs │ └── create_try_into_match_branch_for_fields_named.rs │ ├── on_fields_unnamed.rs │ ├── on_struct_data.rs │ └── on_struct_data │ ├── create_from_impl_for_struct.rs │ ├── create_into_impl_for_struct.rs │ ├── create_try_from_impl_for_struct.rs │ └── create_try_into_impl_for_struct.rs └── tests ├── from_enum.rs ├── from_enum_default.rs ├── from_enum_option.rs ├── from_enum_rename.rs ├── from_enum_skip.rs ├── from_struct.rs ├── from_struct_as.rs ├── from_struct_default.rs ├── from_struct_generic_types.rs ├── from_struct_option.rs ├── from_struct_rename.rs ├── from_struct_skip.rs ├── into_enum.rs ├── into_enum_default.rs ├── into_enum_option.rs ├── into_enum_rename.rs ├── into_enum_skip.rs ├── into_struct.rs ├── into_struct_as.rs ├── into_struct_default.rs ├── into_struct_generic_types.rs ├── into_struct_option.rs ├── into_struct_rename.rs ├── into_struct_skip.rs ├── try_from_enum.rs ├── try_from_enum_default.rs ├── try_from_enum_option.rs ├── try_from_enum_rename.rs ├── try_from_enum_skip.rs ├── try_from_struct.rs ├── try_from_struct_as.rs ├── try_from_struct_default.rs ├── try_from_struct_generic_types.rs ├── try_from_struct_option.rs ├── try_from_struct_rename.rs ├── try_from_struct_skip.rs ├── try_into_enum.rs ├── try_into_enum_default.rs ├── try_into_enum_option.rs ├── try_into_enum_rename.rs ├── try_into_enum_skip.rs ├── try_into_struct.rs ├── try_into_struct_as.rs ├── try_into_struct_generic_types.rs ├── try_into_struct_option.rs └── try_into_struct_skip.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .vscode 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "darling" 7 | version = "0.14.4" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" 10 | dependencies = [ 11 | "darling_core", 12 | "darling_macro", 13 | ] 14 | 15 | [[package]] 16 | name = "darling_core" 17 | version = "0.14.4" 18 | source = "registry+https://github.com/rust-lang/crates.io-index" 19 | checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" 20 | dependencies = [ 21 | "fnv", 22 | "ident_case", 23 | "proc-macro2", 24 | "quote", 25 | "strsim", 26 | "syn", 27 | ] 28 | 29 | [[package]] 30 | name = "darling_macro" 31 | version = "0.14.4" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" 34 | dependencies = [ 35 | "darling_core", 36 | "quote", 37 | "syn", 38 | ] 39 | 40 | [[package]] 41 | name = "fnv" 42 | version = "1.0.7" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 45 | 46 | [[package]] 47 | name = "ident_case" 48 | version = "1.0.1" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 51 | 52 | [[package]] 53 | name = "proc-macro2" 54 | version = "1.0.78" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" 57 | dependencies = [ 58 | "unicode-ident", 59 | ] 60 | 61 | [[package]] 62 | name = "quote" 63 | version = "1.0.35" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 66 | dependencies = [ 67 | "proc-macro2", 68 | ] 69 | 70 | [[package]] 71 | name = "strsim" 72 | version = "0.10.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 75 | 76 | [[package]] 77 | name = "structural-convert" 78 | version = "0.13.0" 79 | dependencies = [ 80 | "structural-convert-derive", 81 | ] 82 | 83 | [[package]] 84 | name = "structural-convert-derive" 85 | version = "0.3.0" 86 | dependencies = [ 87 | "darling", 88 | "proc-macro2", 89 | "quote", 90 | "syn", 91 | ] 92 | 93 | [[package]] 94 | name = "syn" 95 | version = "1.0.109" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 98 | dependencies = [ 99 | "proc-macro2", 100 | "quote", 101 | "unicode-ident", 102 | ] 103 | 104 | [[package]] 105 | name = "unicode-ident" 106 | version = "1.0.12" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 109 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["voidpumpkin <32368314+voidpumpkin@users.noreply.github.com>"] 3 | edition = "2021" 4 | name = "structural-convert" 5 | description = "Derive conversion traits (From, Into, TryFrom, TryInto) when fields are structurally similar in enums or structs" 6 | version = "0.13.0" 7 | license = "MIT" 8 | repository = "https://github.com/voidpumpkin/structural-convert/" 9 | readme = "README.md" 10 | keywords = ["mapping", "derive", "map", "from", "convert"] 11 | 12 | [dependencies] 13 | structural-convert-derive = { version = "0.3.0", path = "./structural-convert-derive" } 14 | 15 | [workspace] 16 | members = [".", "structural-convert-derive"] 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Julius Lungys 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # structural-convert 2 | 3 | Derive conversion traits when items are structurally similar. 4 | 5 | Inspired by serde and struct-convert crates. 6 | 7 | ## Features 8 | 9 | - One to one fields mapping derive for 10 | - From 11 | - Into 12 | - TryFrom 13 | - TryInto 14 | - Inner fields type conversion using `.into()`/`.try_into()` 15 | - Rename enum variants and named fields 16 | - Skip enum variants and named fields 17 | - Fallback to default enum variant 18 | - Named fields conversion fallback to default 19 | - Enum not matched variants fallback to enum default 20 | - Struct named fields only - Middle man type conversion using `as` attribute 21 | - handles std types: 22 | - `Option` 23 | - `Result` 24 | - types from `std::collections` like Vec, HashMap, etc... 25 | 26 | ## Features Wishlist 27 | 28 | - Implement attributes for unnamed fields (default, skip, as) 29 | - Handle `Type` to `Option` 30 | - Add more data on try error and provide ability to inject your own error type 31 | 32 | ## Examples 33 | 34 | Check the tests folder for more examples, but here is some samples: 35 | 36 | ### Struct 37 | 38 | ```rs 39 | #[derive(Debug, PartialEq)] 40 | struct Rhs { 41 | z: i8, 42 | x: u32, 43 | } 44 | 45 | #[derive(Debug, PartialEq, StructuralConvert)] 46 | #[convert(from(Rhs))] 47 | struct Lhs { 48 | z: i32, 49 | x: u32, 50 | } 51 | 52 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.into()); 53 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.into()); 54 | ``` 55 | 56 | Generated code: 57 | 58 | ```rs 59 | impl From for Lhs { 60 | fn from(value: Rhs) -> Self { 61 | match value { 62 | Rhs { z, x } => Lhs { 63 | z: z.into(), 64 | x: x.into(), 65 | }, 66 | } 67 | } 68 | } 69 | ``` 70 | 71 | ### Enum 72 | 73 | ```rs 74 | #[derive(Debug, PartialEq)] 75 | enum Rhs { 76 | A { z: i8, x: u32 }, 77 | } 78 | 79 | #[derive(Debug, PartialEq, StructuralConvert)] 80 | #[convert(from(Rhs))] 81 | enum Lhs { 82 | A { z: i32, x: u32 }, 83 | } 84 | 85 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { z: 1, x: 2 }.into()); 86 | ``` 87 | 88 | Generated code: 89 | 90 | ```rs 91 | impl From for Lhs { 92 | fn from(value: Rhs) -> Self { 93 | match value { 94 | Rhs::A { z, x } => Lhs::A { 95 | z: z.into(), 96 | x: x.into(), 97 | }, 98 | } 99 | } 100 | } 101 | ``` 102 | -------------------------------------------------------------------------------- /src/ident_as_literal_list.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::format_ident; 4 | use quote::ToTokens; 5 | use syn::Lit; 6 | use syn::NestedMeta; 7 | 8 | #[derive(Debug, Clone, Default)] 9 | pub struct IdentAsLiteralList(pub Vec); 10 | 11 | impl FromMeta for IdentAsLiteralList { 12 | fn from_list(items: &[NestedMeta]) -> darling::Result { 13 | let mut idents: Vec = vec![]; 14 | for item in items { 15 | match item { 16 | NestedMeta::Meta(_) => { 17 | return Err( 18 | darling::Error::custom("Expected list of literals here").with_span(item) 19 | ); 20 | } 21 | NestedMeta::Lit(lit) => { 22 | let ident = match lit { 23 | Lit::Str(inner_lit) => format_ident!("{}", inner_lit.value()), 24 | Lit::ByteStr(inner_lit) => { 25 | let s = String::from_utf8(inner_lit.value()).map_err(|_| { 26 | darling::Error::unsupported_shape( 27 | lit.to_token_stream().to_string().as_str(), 28 | ) 29 | })?; 30 | format_ident!("{s}",) 31 | } 32 | Lit::Byte(inner_lit) => { 33 | format_ident!("{}", (inner_lit.value() as char).to_string()) 34 | } 35 | Lit::Char(inner_lit) => { 36 | format_ident!("{}", inner_lit.value().to_string()) 37 | } 38 | _ => { 39 | return Err(darling::Error::custom(format!( 40 | "Literal not supported: {}", 41 | lit.to_token_stream() 42 | )) 43 | .with_span(item)) 44 | } 45 | }; 46 | idents.push(ident); 47 | } 48 | } 49 | } 50 | 51 | Ok(Self(idents)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny( 3 | clippy::unwrap_used, 4 | clippy::panic, 5 | clippy::expect_used, 6 | clippy::unimplemented, 7 | clippy::todo 8 | )] 9 | 10 | pub use structural_convert_derive::*; 11 | -------------------------------------------------------------------------------- /structural-convert-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["voidpumpkin <32368314+voidpumpkin@users.noreply.github.com>"] 3 | edition = "2021" 4 | name = "structural-convert-derive" 5 | description = "Macros for structural-convert crate" 6 | version = "0.3.0" 7 | license = "MIT" 8 | repository = "https://github.com/voidpumpkin/structural-convert/" 9 | readme = "README.md" 10 | keywords = ["mapping", "derive", "map", "from", "convert"] 11 | 12 | [lib] 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = "1" 17 | quote = "1" 18 | syn = { version = "1", features = ["full"] } 19 | darling = "0.14.4" 20 | -------------------------------------------------------------------------------- /structural-convert-derive/README.md: -------------------------------------------------------------------------------- 1 | # structural-convert-derive 2 | 3 | This is a complimentary procedural macros crate for the `structural-convert` crate. 4 | -------------------------------------------------------------------------------- /structural-convert-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![deny( 3 | clippy::unwrap_used, 4 | clippy::panic, 5 | clippy::expect_used, 6 | clippy::unimplemented, 7 | clippy::todo 8 | )] 9 | 10 | extern crate proc_macro; 11 | 12 | mod structural_convert; 13 | 14 | use syn::parse_macro_input; 15 | use syn::DeriveInput; 16 | 17 | use crate::proc_macro::TokenStream; 18 | 19 | #[proc_macro_derive(StructuralConvert, attributes(convert))] 20 | pub fn structural_convert(item: TokenStream) -> TokenStream { 21 | structural_convert::structural_convert_impl(parse_macro_input!(item as DeriveInput)) 22 | .unwrap_or_else(|err| err.write_errors()) 23 | .into() 24 | } 25 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert.rs: -------------------------------------------------------------------------------- 1 | // Most variables hold struct Idents or rust types, 2 | // so it is more readable when we keep them Pascal cased 3 | #![allow(non_snake_case)] 4 | 5 | use darling::FromDeriveInput; 6 | use on_enum_data::on_enum_data; 7 | use on_struct_data::on_struct_data; 8 | use proc_macro2::TokenStream; 9 | 10 | use syn::punctuated::Punctuated; 11 | use syn::token::Colon2; 12 | use syn::Data; 13 | use syn::DeriveInput; 14 | use syn::Path; 15 | use syn::PathSegment; 16 | 17 | use self::attributes::ContainerAttributes; 18 | 19 | pub mod attributes; 20 | mod conversion_error; 21 | mod on_enum_data; 22 | pub mod on_field_type; 23 | mod on_fields_named; 24 | mod on_fields_unnamed; 25 | mod on_struct_data; 26 | 27 | pub fn structural_convert_impl(input: DeriveInput) -> darling::Result { 28 | let container_attributes = ContainerAttributes::from_derive_input(&input)?; 29 | 30 | let DeriveInput { 31 | ident, 32 | data, 33 | attrs: _attrs, 34 | vis: _vis, 35 | generics: _generics, 36 | } = input.clone(); 37 | 38 | let input_ident_path_segment = PathSegment { 39 | ident: ident.clone(), 40 | arguments: Default::default(), 41 | }; 42 | 43 | let input_ident_path_segments: Punctuated = 44 | Punctuated::from_iter(vec![input_ident_path_segment]); 45 | 46 | let input_ident_path = Path { 47 | leading_colon: None, 48 | segments: input_ident_path_segments, 49 | }; 50 | 51 | match data { 52 | Data::Struct(struct_data) => on_struct_data( 53 | &input_ident_path, 54 | &struct_data, 55 | &container_attributes, 56 | &input, 57 | ), 58 | Data::Enum(enum_data) => on_enum_data(&input_ident_path, &enum_data, &container_attributes), 59 | Data::Union(_union_data) => { 60 | Err(darling::Error::custom("Unions are not implemented").with_span(&input)) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes.rs: -------------------------------------------------------------------------------- 1 | pub mod enum_variant; 2 | pub mod from_container_attributes; 3 | pub mod ident_as_literal_list; 4 | pub mod into_container_attributes; 5 | pub mod named_field; 6 | pub mod try_from_container_attributes; 7 | pub mod try_into_container_attributes; 8 | 9 | use darling::FromAttributes; 10 | use darling::FromDeriveInput; 11 | 12 | use self::enum_variant::from_enum_variant_attributes::FromEnumVariantAttributes; 13 | use self::enum_variant::into_enum_variant_attributes::IntoEnumVariantAttributes; 14 | use self::enum_variant::try_from_enum_variant_attributes::TryFromEnumVariantAttributes; 15 | use self::enum_variant::try_into_enum_variant_attributes::TryIntoEnumVariantAttributes; 16 | use self::from_container_attributes::FromContainerAttributes; 17 | use self::into_container_attributes::IntoContainerAttributes; 18 | use self::named_field::from_field_named_attributes::FromFieldNamedAttributes; 19 | use self::named_field::into_field_named_attributes::IntoFieldNamedAttributes; 20 | use self::named_field::try_from_field_named_attributes::TryFromFieldNamedAttributes; 21 | use self::named_field::try_into_field_named_attributes::TryIntoFieldNamedAttributes; 22 | use self::try_from_container_attributes::TryFromContainerAttributes; 23 | use self::try_into_container_attributes::TryIntoContainerAttributes; 24 | 25 | #[derive(Debug, Default, FromDeriveInput)] 26 | #[darling(default, attributes(convert))] 27 | pub struct ContainerAttributes { 28 | #[darling(multiple)] 29 | pub into: Vec, 30 | #[darling(multiple)] 31 | pub from: Vec, 32 | #[darling(multiple)] 33 | pub try_into: Vec, 34 | #[darling(multiple)] 35 | pub try_from: Vec, 36 | } 37 | 38 | #[derive(Debug, Default, Clone, FromAttributes)] 39 | #[darling(attributes(convert))] 40 | pub struct EnumVariantAttributes { 41 | #[darling(multiple)] 42 | pub from: Vec, 43 | #[darling(multiple)] 44 | pub into: Vec, 45 | #[darling(multiple)] 46 | pub try_from: Vec, 47 | #[darling(multiple)] 48 | pub try_into: Vec, 49 | } 50 | 51 | #[derive(Debug, Default, Clone, FromAttributes)] 52 | #[darling(attributes(convert))] 53 | pub struct FieldNamedAttributes { 54 | #[darling(multiple)] 55 | pub from: Vec, 56 | #[darling(multiple)] 57 | pub into: Vec, 58 | #[darling(multiple)] 59 | pub try_from: Vec, 60 | #[darling(multiple)] 61 | pub try_into: Vec, 62 | } 63 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/enum_variant/from_enum_variant_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | 7 | #[derive(Debug, Default, Clone)] 8 | pub struct FromEnumVariantAttributes { 9 | pub target: Option, 10 | pub skip: bool, 11 | pub rename: Option, 12 | pub default: bool, 13 | } 14 | 15 | impl FromMeta for FromEnumVariantAttributes { 16 | fn from_list(items: &[NestedMeta]) -> darling::Result { 17 | let mut target: Option = None; 18 | let mut default: bool = Default::default(); 19 | let mut rename: Option = None; 20 | let mut skip: bool = Default::default(); 21 | 22 | for (i, item) in items.iter().enumerate() { 23 | match item { 24 | NestedMeta::Meta(meta) => match meta { 25 | syn::Meta::Path(meta_path) => { 26 | match meta_path.to_token_stream().to_string().as_str() { 27 | "default" => { 28 | default = true; 29 | } 30 | "skip" => { 31 | skip = true; 32 | } 33 | _ if i == 0 => target = Some(meta_path.clone()), 34 | _ => { 35 | return Err(darling::Error::custom(format!( 36 | "Path only allowed in the first argument: {}", 37 | meta.to_token_stream() 38 | )) 39 | .with_span(meta_path)) 40 | } 41 | } 42 | } 43 | syn::Meta::List(list) => { 44 | return Err(darling::Error::custom(format!( 45 | "Attribute not supported: {}", 46 | list.to_token_stream() 47 | )) 48 | .with_span(list)) 49 | } 50 | syn::Meta::NameValue(name_value) => { 51 | match name_value.path.to_token_stream().to_string().as_str() { 52 | "rename" => { 53 | rename = Some(Ident::from_value(&name_value.lit)?); 54 | } 55 | _ => { 56 | return Err(darling::Error::custom(format!( 57 | "Attribute not supported: {}", 58 | name_value.to_token_stream() 59 | )) 60 | .with_span(name_value)) 61 | } 62 | } 63 | } 64 | }, 65 | NestedMeta::Lit(_) => { 66 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 67 | } 68 | } 69 | } 70 | 71 | Ok(Self { 72 | target, 73 | skip, 74 | rename, 75 | default, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/enum_variant/into_enum_variant_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | 7 | #[derive(Debug, Default, Clone)] 8 | pub struct IntoEnumVariantAttributes { 9 | pub target: Option, 10 | pub rename: Option, 11 | pub skip_after: Option, 12 | pub skip: bool, 13 | } 14 | 15 | impl FromMeta for IntoEnumVariantAttributes { 16 | fn from_list(items: &[NestedMeta]) -> darling::Result { 17 | let mut target: Option = None; 18 | let mut rename: Option = None; 19 | let mut skip_after: Option = Default::default(); 20 | let mut skip: bool = Default::default(); 21 | 22 | for (i, item) in items.iter().enumerate() { 23 | match item { 24 | NestedMeta::Meta(meta) => match meta { 25 | syn::Meta::Path(meta_path) => { 26 | match meta_path.to_token_stream().to_string().as_str() { 27 | "skip" => { 28 | skip = true; 29 | } 30 | _ if i == 0 => target = Some(meta_path.clone()), 31 | _ => { 32 | return Err(darling::Error::custom(format!( 33 | "Path only allowed in the first argument: {}", 34 | meta.to_token_stream() 35 | )) 36 | .with_span(meta_path)) 37 | } 38 | } 39 | } 40 | syn::Meta::List(list) => { 41 | return Err(darling::Error::custom(format!( 42 | "Attribute not supported: {}", 43 | list.to_token_stream() 44 | )) 45 | .with_span(list)) 46 | } 47 | syn::Meta::NameValue(name_value) => { 48 | match name_value.path.to_token_stream().to_string().as_str() { 49 | "skip_after" => { 50 | skip_after = Some(u32::from_value(&name_value.lit)? as usize); 51 | } 52 | "rename" => { 53 | rename = Some(Ident::from_value(&name_value.lit)?); 54 | } 55 | _ => { 56 | return Err(darling::Error::custom(format!( 57 | "Attribute not supported: {}", 58 | name_value.to_token_stream() 59 | )) 60 | .with_span(name_value)) 61 | } 62 | } 63 | } 64 | }, 65 | NestedMeta::Lit(_) => { 66 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 67 | } 68 | } 69 | } 70 | 71 | Ok(Self { 72 | target, 73 | rename, 74 | skip_after, 75 | skip, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/enum_variant/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod from_enum_variant_attributes; 2 | pub mod into_enum_variant_attributes; 3 | pub mod try_from_enum_variant_attributes; 4 | pub mod try_into_enum_variant_attributes; 5 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/enum_variant/try_from_enum_variant_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | 7 | #[derive(Debug, Default, Clone)] 8 | pub struct TryFromEnumVariantAttributes { 9 | pub target: Option, 10 | pub skip: bool, 11 | pub rename: Option, 12 | pub default: bool, 13 | } 14 | 15 | impl FromMeta for TryFromEnumVariantAttributes { 16 | fn from_list(items: &[NestedMeta]) -> darling::Result { 17 | let mut target: Option = None; 18 | let mut default: bool = Default::default(); 19 | let mut rename: Option = None; 20 | let mut skip: bool = Default::default(); 21 | 22 | for (i, item) in items.iter().enumerate() { 23 | match item { 24 | NestedMeta::Meta(meta) => match meta { 25 | syn::Meta::Path(meta_path) => { 26 | match meta_path.to_token_stream().to_string().as_str() { 27 | "default" => { 28 | default = true; 29 | } 30 | "skip" => { 31 | skip = true; 32 | } 33 | _ if i == 0 => target = Some(meta_path.clone()), 34 | _ => { 35 | return Err(darling::Error::custom(format!( 36 | "Path only allowed in the first argument: {}", 37 | meta.to_token_stream() 38 | )) 39 | .with_span(meta_path)) 40 | } 41 | } 42 | } 43 | syn::Meta::List(list) => { 44 | return Err(darling::Error::custom(format!( 45 | "Attribute not supported: {}", 46 | list.to_token_stream() 47 | )) 48 | .with_span(list)) 49 | } 50 | syn::Meta::NameValue(name_value) => { 51 | match name_value.path.to_token_stream().to_string().as_str() { 52 | "rename" => { 53 | rename = Some(Ident::from_value(&name_value.lit)?); 54 | } 55 | _ => { 56 | return Err(darling::Error::custom(format!( 57 | "Attribute not supported: {}", 58 | name_value.to_token_stream() 59 | )) 60 | .with_span(name_value)) 61 | } 62 | } 63 | } 64 | }, 65 | NestedMeta::Lit(_) => { 66 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 67 | } 68 | } 69 | } 70 | 71 | Ok(Self { 72 | target, 73 | skip, 74 | rename, 75 | default, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/enum_variant/try_into_enum_variant_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | 7 | #[derive(Debug, Default, Clone)] 8 | pub struct TryIntoEnumVariantAttributes { 9 | pub target: Option, 10 | pub rename: Option, 11 | pub skip_after: Option, 12 | pub skip: bool, 13 | } 14 | 15 | impl FromMeta for TryIntoEnumVariantAttributes { 16 | fn from_list(items: &[NestedMeta]) -> darling::Result { 17 | let mut target: Option = None; 18 | let mut rename: Option = None; 19 | let mut skip_after: Option = Default::default(); 20 | let mut skip: bool = Default::default(); 21 | 22 | for (i, item) in items.iter().enumerate() { 23 | match item { 24 | NestedMeta::Meta(meta) => match meta { 25 | syn::Meta::Path(meta_path) => { 26 | match meta_path.to_token_stream().to_string().as_str() { 27 | "skip" => { 28 | skip = true; 29 | } 30 | _ if i == 0 => target = Some(meta_path.clone()), 31 | _ => { 32 | return Err(darling::Error::custom(format!( 33 | "Path only allowed in the first argument: {}", 34 | meta.to_token_stream() 35 | )) 36 | .with_span(meta_path)) 37 | } 38 | } 39 | } 40 | syn::Meta::List(list) => { 41 | return Err(darling::Error::custom(format!( 42 | "Attribute not supported: {}", 43 | list.to_token_stream() 44 | )) 45 | .with_span(list)) 46 | } 47 | syn::Meta::NameValue(name_value) => { 48 | match name_value.path.to_token_stream().to_string().as_str() { 49 | "skip_after" => { 50 | skip_after = Some(u32::from_value(&name_value.lit)? as usize); 51 | } 52 | "rename" => { 53 | rename = Some(Ident::from_value(&name_value.lit)?); 54 | } 55 | _ => { 56 | return Err(darling::Error::custom(format!( 57 | "Attribute not supported: {}", 58 | name_value.to_token_stream() 59 | )) 60 | .with_span(name_value)) 61 | } 62 | } 63 | } 64 | }, 65 | NestedMeta::Lit(_) => { 66 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 67 | } 68 | } 69 | } 70 | 71 | Ok(Self { 72 | target, 73 | rename, 74 | skip_after, 75 | skip, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/from_container_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use quote::ToTokens; 3 | use syn::NestedMeta; 4 | use syn::Path; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct FromContainerAttributes { 8 | pub path: Path, 9 | } 10 | 11 | impl FromMeta for FromContainerAttributes { 12 | fn from_list(items: &[NestedMeta]) -> darling::Result { 13 | let Some(item) = items.get(0) else { 14 | return Err(darling::Error::custom("Missing arguments")); 15 | }; 16 | match item { 17 | NestedMeta::Meta(meta) => match meta { 18 | syn::Meta::Path(path) => Ok(Self { path: path.clone() }), 19 | syn::Meta::List(_) => Err(darling::Error::custom(format!( 20 | "Meta List not supported: {}", 21 | meta.to_token_stream() 22 | )) 23 | .with_span(item)), 24 | syn::Meta::NameValue(_) => Err(darling::Error::custom(format!( 25 | "Meta NameValue not supported: {}", 26 | meta.to_token_stream() 27 | )) 28 | .with_span(item)), 29 | }, 30 | NestedMeta::Lit(_) => Err(darling::Error::custom("Expected Meta here").with_span(item)), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/ident_as_literal_list.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::format_ident; 4 | use quote::ToTokens; 5 | use syn::Lit; 6 | use syn::NestedMeta; 7 | 8 | #[derive(Debug, Clone, Default)] 9 | pub struct IdentAsLiteralList(pub Vec); 10 | 11 | impl FromMeta for IdentAsLiteralList { 12 | fn from_list(items: &[NestedMeta]) -> darling::Result { 13 | let mut idents: Vec = vec![]; 14 | for item in items { 15 | match item { 16 | NestedMeta::Meta(_) => { 17 | return Err( 18 | darling::Error::custom("Expected list of literals here").with_span(item) 19 | ); 20 | } 21 | NestedMeta::Lit(lit) => { 22 | let ident = match lit { 23 | Lit::Str(inner_lit) => format_ident!("{}", inner_lit.value()), 24 | Lit::ByteStr(inner_lit) => { 25 | let s = String::from_utf8(inner_lit.value()).map_err(|_| { 26 | darling::Error::unsupported_shape( 27 | lit.to_token_stream().to_string().as_str(), 28 | ) 29 | })?; 30 | format_ident!("{s}",) 31 | } 32 | Lit::Byte(inner_lit) => { 33 | format_ident!("{}", (inner_lit.value() as char).to_string()) 34 | } 35 | Lit::Char(inner_lit) => { 36 | format_ident!("{}", inner_lit.value().to_string()) 37 | } 38 | _ => { 39 | return Err(darling::Error::custom(format!( 40 | "Literal not supported: {}", 41 | lit.to_token_stream() 42 | )) 43 | .with_span(item)) 44 | } 45 | }; 46 | idents.push(ident); 47 | } 48 | } 49 | } 50 | 51 | Ok(Self(idents)) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/into_container_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use quote::ToTokens; 3 | use syn::NestedMeta; 4 | use syn::Path; 5 | 6 | use super::ident_as_literal_list::IdentAsLiteralList; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct IntoContainerAttributes { 10 | pub path: Path, 11 | pub skip_after: Option, 12 | pub default_for_fields: IdentAsLiteralList, 13 | pub default: bool, 14 | } 15 | 16 | impl FromMeta for IntoContainerAttributes { 17 | fn from_list(items: &[NestedMeta]) -> darling::Result { 18 | let mut path: Option = None; 19 | let mut skip_after: Option = Default::default(); 20 | let mut default_for_fields: IdentAsLiteralList = Default::default(); 21 | let mut default: bool = Default::default(); 22 | 23 | for (i, item) in items.iter().enumerate() { 24 | match item { 25 | NestedMeta::Meta(meta) => match meta { 26 | syn::Meta::Path(meta_path) if i == 0 => path = Some(meta_path.clone()), 27 | syn::Meta::Path(meta_path) 28 | if meta_path.to_token_stream().to_string().as_str() == "default" => 29 | { 30 | default = true; 31 | } 32 | syn::Meta::Path(_) => { 33 | return Err(darling::Error::custom(format!( 34 | "Path only allowed in the first argument: {}", 35 | meta.to_token_stream() 36 | )) 37 | .with_span(item)) 38 | } 39 | syn::Meta::List(list) => { 40 | let items = list 41 | .nested 42 | .iter() 43 | .map(ToOwned::to_owned) 44 | .collect::>(); 45 | 46 | match list.path.to_token_stream().to_string().as_str() { 47 | "default_for_fields" => { 48 | default_for_fields = 49 | IdentAsLiteralList::from_list(items.as_slice())?; 50 | } 51 | _ => { 52 | return Err(darling::Error::custom(format!( 53 | "Attribute not supported: {}", 54 | list.to_token_stream() 55 | )) 56 | .with_span(list)) 57 | } 58 | } 59 | } 60 | syn::Meta::NameValue(name_value) => { 61 | match name_value.path.to_token_stream().to_string().as_str() { 62 | "skip_after" => { 63 | skip_after = Some(u32::from_value(&name_value.lit)? as usize); 64 | } 65 | _ => { 66 | return Err(darling::Error::custom(format!( 67 | "Attribute not supported: {}", 68 | name_value.to_token_stream() 69 | )) 70 | .with_span(name_value)) 71 | } 72 | } 73 | } 74 | }, 75 | NestedMeta::Lit(_) => { 76 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 77 | } 78 | } 79 | } 80 | 81 | let Some(path) = path else { 82 | return Err(darling::Error::missing_field("No path provided")); 83 | }; 84 | 85 | Ok(Self { 86 | path, 87 | skip_after, 88 | default_for_fields, 89 | default, 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/named_field/from_field_named_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | use syn::Type; 7 | 8 | #[derive(Debug, Default, Clone)] 9 | pub struct FromFieldNamedAttributes { 10 | pub target: Option, 11 | pub rename: Option, 12 | pub default: bool, 13 | pub as_type: Option, 14 | } 15 | 16 | impl FromMeta for FromFieldNamedAttributes { 17 | fn from_list(items: &[NestedMeta]) -> darling::Result { 18 | let mut target: Option = None; 19 | let mut rename: Option = None; 20 | let mut default: bool = Default::default(); 21 | let mut as_type: Option = None; 22 | 23 | for (i, item) in items.iter().enumerate() { 24 | match item { 25 | NestedMeta::Meta(meta) => match meta { 26 | syn::Meta::Path(meta_path) => { 27 | match meta_path.to_token_stream().to_string().as_str() { 28 | "default" => { 29 | default = true; 30 | } 31 | _ if i == 0 => target = Some(meta_path.clone()), 32 | _ => { 33 | return Err(darling::Error::custom(format!( 34 | "Path only allowed in the first argument: {}", 35 | meta.to_token_stream() 36 | )) 37 | .with_span(meta_path)) 38 | } 39 | } 40 | } 41 | syn::Meta::List(list) => { 42 | return Err(darling::Error::custom(format!( 43 | "Attribute not supported: {}", 44 | list.to_token_stream() 45 | )) 46 | .with_span(list)) 47 | } 48 | syn::Meta::NameValue(name_value) => { 49 | match name_value.path.to_token_stream().to_string().as_str() { 50 | "rename" => { 51 | rename = Some(Ident::from_value(&name_value.lit)?); 52 | } 53 | "as" => { 54 | as_type = Some(Type::from_value(&name_value.lit)?); 55 | } 56 | _ => { 57 | return Err(darling::Error::custom(format!( 58 | "Attribute not supported: {}", 59 | name_value.to_token_stream() 60 | )) 61 | .with_span(name_value)) 62 | } 63 | } 64 | } 65 | }, 66 | NestedMeta::Lit(_) => { 67 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 68 | } 69 | } 70 | } 71 | 72 | Ok(Self { 73 | target, 74 | rename, 75 | default, 76 | as_type, 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/named_field/into_field_named_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | use syn::Type; 7 | 8 | #[derive(Debug, Default, Clone)] 9 | pub struct IntoFieldNamedAttributes { 10 | pub target: Option, 11 | pub rename: Option, 12 | pub skip: bool, 13 | pub default: bool, 14 | pub as_type: Option, 15 | } 16 | 17 | impl FromMeta for IntoFieldNamedAttributes { 18 | fn from_list(items: &[NestedMeta]) -> darling::Result { 19 | let mut target: Option = None; 20 | let mut rename: Option = None; 21 | let mut skip: bool = Default::default(); 22 | let mut default: bool = Default::default(); 23 | let mut as_type: Option = None; 24 | 25 | for (i, item) in items.iter().enumerate() { 26 | match item { 27 | NestedMeta::Meta(meta) => match meta { 28 | syn::Meta::Path(meta_path) => { 29 | match meta_path.to_token_stream().to_string().as_str() { 30 | "default" => { 31 | default = true; 32 | } 33 | "skip" => { 34 | skip = true; 35 | } 36 | _ if i == 0 => target = Some(meta_path.clone()), 37 | _ => { 38 | return Err(darling::Error::custom(format!( 39 | "Path only allowed in the first argument: {}", 40 | meta.to_token_stream() 41 | )) 42 | .with_span(meta_path)) 43 | } 44 | } 45 | } 46 | syn::Meta::List(list) => { 47 | return Err(darling::Error::custom(format!( 48 | "Attribute not supported: {}", 49 | list.to_token_stream() 50 | )) 51 | .with_span(list)) 52 | } 53 | syn::Meta::NameValue(name_value) => { 54 | match name_value.path.to_token_stream().to_string().as_str() { 55 | "rename" => { 56 | rename = Some(Ident::from_value(&name_value.lit)?); 57 | } 58 | "as" => { 59 | as_type = Some(Type::from_value(&name_value.lit)?); 60 | } 61 | _ => { 62 | return Err(darling::Error::custom(format!( 63 | "Attribute not supported: {}", 64 | name_value.to_token_stream() 65 | )) 66 | .with_span(name_value)) 67 | } 68 | } 69 | } 70 | }, 71 | NestedMeta::Lit(_) => { 72 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 73 | } 74 | } 75 | } 76 | 77 | Ok(Self { 78 | target, 79 | rename, 80 | skip, 81 | default, 82 | as_type, 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/named_field/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod from_field_named_attributes; 2 | pub mod into_field_named_attributes; 3 | pub mod try_from_field_named_attributes; 4 | pub mod try_into_field_named_attributes; 5 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/named_field/try_from_field_named_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | use syn::Type; 7 | 8 | #[derive(Debug, Default, Clone)] 9 | pub struct TryFromFieldNamedAttributes { 10 | pub target: Option, 11 | pub rename: Option, 12 | pub default: bool, 13 | pub as_type: Option, 14 | } 15 | 16 | impl FromMeta for TryFromFieldNamedAttributes { 17 | fn from_list(items: &[NestedMeta]) -> darling::Result { 18 | let mut target: Option = None; 19 | let mut rename: Option = None; 20 | let mut default: bool = Default::default(); 21 | let mut as_type: Option = None; 22 | 23 | for (i, item) in items.iter().enumerate() { 24 | match item { 25 | NestedMeta::Meta(meta) => match meta { 26 | syn::Meta::Path(meta_path) => { 27 | match meta_path.to_token_stream().to_string().as_str() { 28 | "default" => { 29 | default = true; 30 | } 31 | _ if i == 0 => target = Some(meta_path.clone()), 32 | _ => { 33 | return Err(darling::Error::custom(format!( 34 | "Path only allowed in the first argument: {}", 35 | meta.to_token_stream() 36 | )) 37 | .with_span(meta_path)) 38 | } 39 | } 40 | } 41 | syn::Meta::List(list) => { 42 | return Err(darling::Error::custom(format!( 43 | "Attribute not supported: {}", 44 | list.to_token_stream() 45 | )) 46 | .with_span(list)) 47 | } 48 | syn::Meta::NameValue(name_value) => { 49 | match name_value.path.to_token_stream().to_string().as_str() { 50 | "rename" => { 51 | rename = Some(Ident::from_value(&name_value.lit)?); 52 | } 53 | "as" => { 54 | as_type = Some(Type::from_value(&name_value.lit)?); 55 | } 56 | _ => { 57 | return Err(darling::Error::custom(format!( 58 | "Attribute not supported: {}", 59 | name_value.to_token_stream() 60 | )) 61 | .with_span(name_value)) 62 | } 63 | } 64 | } 65 | }, 66 | NestedMeta::Lit(_) => { 67 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 68 | } 69 | } 70 | } 71 | 72 | Ok(Self { 73 | target, 74 | rename, 75 | default, 76 | as_type, 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/named_field/try_into_field_named_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use proc_macro2::Ident; 3 | use quote::ToTokens; 4 | use syn::NestedMeta; 5 | use syn::Path; 6 | use syn::Type; 7 | #[derive(Debug, Default, Clone)] 8 | pub struct TryIntoFieldNamedAttributes { 9 | pub target: Option, 10 | pub rename: Option, 11 | pub skip: bool, 12 | pub as_type: Option, 13 | } 14 | 15 | impl FromMeta for TryIntoFieldNamedAttributes { 16 | fn from_list(items: &[NestedMeta]) -> darling::Result { 17 | let mut target: Option = None; 18 | let mut rename: Option = None; 19 | let mut skip: bool = Default::default(); 20 | let mut as_type: Option = None; 21 | 22 | for (i, item) in items.iter().enumerate() { 23 | match item { 24 | NestedMeta::Meta(meta) => match meta { 25 | syn::Meta::Path(meta_path) => { 26 | match meta_path.to_token_stream().to_string().as_str() { 27 | "skip" => { 28 | skip = true; 29 | } 30 | _ if i == 0 => target = Some(meta_path.clone()), 31 | _ => { 32 | return Err(darling::Error::custom(format!( 33 | "Path only allowed in the first argument: {}", 34 | meta.to_token_stream() 35 | )) 36 | .with_span(meta_path)) 37 | } 38 | } 39 | } 40 | syn::Meta::List(list) => { 41 | return Err(darling::Error::custom(format!( 42 | "Attribute not supported: {}", 43 | list.to_token_stream() 44 | )) 45 | .with_span(list)) 46 | } 47 | syn::Meta::NameValue(name_value) => { 48 | match name_value.path.to_token_stream().to_string().as_str() { 49 | "rename" => { 50 | rename = Some(Ident::from_value(&name_value.lit)?); 51 | } 52 | "as" => { 53 | as_type = Some(Type::from_value(&name_value.lit)?); 54 | } 55 | _ => { 56 | return Err(darling::Error::custom(format!( 57 | "Attribute not supported: {}", 58 | name_value.to_token_stream() 59 | )) 60 | .with_span(name_value)) 61 | } 62 | } 63 | } 64 | }, 65 | NestedMeta::Lit(_) => { 66 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 67 | } 68 | } 69 | } 70 | 71 | Ok(Self { 72 | target, 73 | rename, 74 | skip, 75 | as_type, 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/try_from_container_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use quote::ToTokens; 3 | use syn::NestedMeta; 4 | use syn::Path; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct TryFromContainerAttributes { 8 | pub path: Path, 9 | } 10 | 11 | impl FromMeta for TryFromContainerAttributes { 12 | fn from_list(items: &[NestedMeta]) -> darling::Result { 13 | let Some(item) = items.get(0) else { 14 | return Err(darling::Error::custom("Missing arguments")); 15 | }; 16 | match item { 17 | NestedMeta::Meta(meta) => match meta { 18 | syn::Meta::Path(path) => Ok(Self { path: path.clone() }), 19 | syn::Meta::List(_) => Err(darling::Error::custom(format!( 20 | "Meta List not supported: {}", 21 | meta.to_token_stream() 22 | )) 23 | .with_span(item)), 24 | syn::Meta::NameValue(_) => Err(darling::Error::custom(format!( 25 | "Meta NameValue not supported: {}", 26 | meta.to_token_stream() 27 | )) 28 | .with_span(item)), 29 | }, 30 | NestedMeta::Lit(_) => Err(darling::Error::custom("Expected Meta here").with_span(item)), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/attributes/try_into_container_attributes.rs: -------------------------------------------------------------------------------- 1 | use darling::FromMeta; 2 | use quote::ToTokens; 3 | use syn::NestedMeta; 4 | use syn::Path; 5 | 6 | use super::ident_as_literal_list::IdentAsLiteralList; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct TryIntoContainerAttributes { 10 | pub path: Path, 11 | pub skip_after: Option, 12 | pub default_for_fields: IdentAsLiteralList, 13 | pub default: bool, 14 | } 15 | 16 | impl FromMeta for TryIntoContainerAttributes { 17 | fn from_list(items: &[NestedMeta]) -> darling::Result { 18 | let mut path: Option = None; 19 | let mut skip_after: Option = Default::default(); 20 | let mut default_for_fields: IdentAsLiteralList = Default::default(); 21 | let mut default: bool = Default::default(); 22 | 23 | for (i, item) in items.iter().enumerate() { 24 | match item { 25 | NestedMeta::Meta(meta) => match meta { 26 | syn::Meta::Path(meta_path) if i == 0 => path = Some(meta_path.clone()), 27 | syn::Meta::Path(meta_path) 28 | if meta_path.to_token_stream().to_string().as_str() == "default" => 29 | { 30 | default = true; 31 | } 32 | syn::Meta::Path(_) => { 33 | return Err(darling::Error::custom(format!( 34 | "Path only allowed in the first argument: {}", 35 | meta.to_token_stream() 36 | )) 37 | .with_span(item)) 38 | } 39 | syn::Meta::List(list) => { 40 | let items = list 41 | .nested 42 | .iter() 43 | .map(ToOwned::to_owned) 44 | .collect::>(); 45 | 46 | match list.path.to_token_stream().to_string().as_str() { 47 | "default_for_fields" => { 48 | default_for_fields = 49 | IdentAsLiteralList::from_list(items.as_slice())?; 50 | } 51 | _ => { 52 | return Err(darling::Error::custom(format!( 53 | "Attribute not supported: {}", 54 | list.to_token_stream() 55 | )) 56 | .with_span(list)) 57 | } 58 | } 59 | } 60 | syn::Meta::NameValue(name_value) => { 61 | match name_value.path.to_token_stream().to_string().as_str() { 62 | "skip_after" => { 63 | skip_after = Some(u32::from_value(&name_value.lit)? as usize); 64 | } 65 | _ => { 66 | return Err(darling::Error::custom(format!( 67 | "Attribute not supported: {}", 68 | name_value.to_token_stream() 69 | )) 70 | .with_span(name_value)) 71 | } 72 | } 73 | } 74 | }, 75 | NestedMeta::Lit(_) => { 76 | return Err(darling::Error::custom("Expected Meta here").with_span(item)); 77 | } 78 | } 79 | } 80 | 81 | let Some(path) = path else { 82 | return Err(darling::Error::missing_field("No path provided")); 83 | }; 84 | 85 | Ok(Self { 86 | path, 87 | skip_after, 88 | default_for_fields, 89 | default, 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/conversion_error.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use quote::ToTokens; 4 | use quote::TokenStreamExt; 5 | use std::fmt; 6 | 7 | #[derive(Clone)] 8 | pub struct ConversionError { 9 | pub from: ConversionPath, 10 | pub into: ConversionPath, 11 | } 12 | 13 | impl ConversionError { 14 | pub fn new(from: impl ToTokens, into: impl ToTokens) -> ConversionError { 15 | Self { 16 | from: ConversionPath::start(from), 17 | into: ConversionPath::start(into), 18 | } 19 | } 20 | 21 | pub fn empty() -> ConversionError { 22 | Self { 23 | from: ConversionPath::empty(), 24 | into: ConversionPath::empty(), 25 | } 26 | } 27 | } 28 | 29 | impl fmt::Display for ConversionError { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | writeln!(f, "Failed to convert:")?; 32 | writeln!(f, " from: {}", self.from)?; 33 | writeln!(f, " into: {}", self.into) 34 | } 35 | } 36 | 37 | impl ToTokens for ConversionError { 38 | fn to_tokens(&self, tokens: &mut TokenStream) { 39 | let err = self.to_string(); 40 | tokens.append_all(quote!(format!( 41 | "{}\noriginal error:\n{}", 42 | format!(#err), 43 | err 44 | ))) 45 | } 46 | } 47 | 48 | #[derive(Clone)] 49 | pub struct ConversionPath { 50 | pub(crate) path: Vec, 51 | } 52 | 53 | impl fmt::Display for ConversionPath { 54 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 55 | let path = self 56 | .path 57 | .iter() 58 | .map(|p| p.to_string()) 59 | .collect::() 60 | .to_token_stream(); 61 | write!(f, "{path}") 62 | } 63 | } 64 | 65 | impl ConversionPath { 66 | pub fn start(start_ident: impl ToTokens) -> ConversionPath { 67 | ConversionPath { 68 | path: vec![ConversionStep::StartIdent( 69 | start_ident.to_token_stream().to_string(), 70 | )], 71 | } 72 | } 73 | 74 | pub fn unnamed(&mut self, i: usize) { 75 | self.path.push(ConversionStep::UnnamedField(i)); 76 | } 77 | 78 | pub fn named(&mut self, ident: impl ToTokens) { 79 | self.path.push(ConversionStep::NamedField( 80 | ident.to_token_stream().to_string(), 81 | )); 82 | } 83 | 84 | pub fn named_str(&mut self, ident: &str) { 85 | self.path 86 | .push(ConversionStep::NamedField(ident.to_string())); 87 | } 88 | 89 | pub fn enum_variant(&mut self, ident: &str) { 90 | self.path 91 | .push(ConversionStep::EnumVariant(ident.to_string())); 92 | } 93 | 94 | pub fn dyn_unnamed(&mut self, ident: impl ToTokens) { 95 | self.path.push(ConversionStep::DynamicUnnamed( 96 | ident.to_token_stream().to_string(), 97 | )); 98 | } 99 | 100 | pub fn empty() -> ConversionPath { 101 | ConversionPath { path: vec![] } 102 | } 103 | } 104 | 105 | #[derive(Clone)] 106 | pub enum ConversionStep { 107 | StartIdent(String), 108 | UnnamedField(usize), 109 | NamedField(String), 110 | EnumVariant(String), 111 | DynamicUnnamed(String), 112 | } 113 | 114 | impl fmt::Display for ConversionStep { 115 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 116 | match self { 117 | ConversionStep::StartIdent(ident) => write!(f, "{ident}"), 118 | ConversionStep::UnnamedField(ident) => write!(f, ".{ident}"), 119 | ConversionStep::NamedField(ident) => write!(f, ".{ident}"), 120 | ConversionStep::EnumVariant(ident) => write!(f, "::{ident}"), 121 | ConversionStep::DynamicUnnamed(ident) => write!(f, ".{{{ident}}}"), 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data.rs: -------------------------------------------------------------------------------- 1 | use super::attributes::ContainerAttributes; 2 | use create_from_impl_for_enum::create_from_impl_for_enum; 3 | use create_into_impl_for_enum::create_into_impl_for_enum; 4 | use create_try_from_impl_for_enum::create_try_from_impl_for_enum; 5 | use create_try_into_impl_for_enum::create_try_into_impl_for_enum; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | use syn::DataEnum; 9 | use syn::Path; 10 | 11 | pub mod create_from_impl_for_enum; 12 | pub mod create_into_impl_for_enum; 13 | pub mod create_try_from_impl_for_enum; 14 | pub mod create_try_into_impl_for_enum; 15 | pub mod utils; 16 | 17 | pub fn on_enum_data( 18 | input_ident_path: &Path, 19 | enum_data: &DataEnum, 20 | container_attributes: &ContainerAttributes, 21 | ) -> darling::Result { 22 | let ContainerAttributes { 23 | into, 24 | from, 25 | try_into, 26 | try_from, 27 | } = container_attributes; 28 | 29 | let into_tokens = into 30 | .iter() 31 | .map(|attrs| { 32 | create_into_impl_for_enum( 33 | input_ident_path, 34 | enum_data, 35 | &attrs.path, 36 | &attrs.default_for_fields.0, 37 | attrs.default, 38 | ) 39 | }) 40 | .collect::>>()?; 41 | let from_tokens = from 42 | .iter() 43 | .map(|attrs| create_from_impl_for_enum(&attrs.path, enum_data, input_ident_path)) 44 | .collect::>>()?; 45 | let try_into_tokens = try_into 46 | .iter() 47 | .map(|attrs| { 48 | create_try_into_impl_for_enum( 49 | input_ident_path, 50 | enum_data, 51 | &attrs.path, 52 | &attrs.default_for_fields.0, 53 | attrs.default, 54 | ) 55 | }) 56 | .collect::>>()?; 57 | let try_from_tokens = try_from 58 | .iter() 59 | .map(|attrs| create_try_from_impl_for_enum(&attrs.path, enum_data, input_ident_path)) 60 | .collect::>>()?; 61 | 62 | Ok(quote!( 63 | #( 64 | #into_tokens 65 | )* 66 | #( 67 | #from_tokens 68 | )* 69 | #( 70 | #try_into_tokens 71 | )* 72 | #( 73 | #try_from_tokens 74 | )* 75 | )) 76 | } 77 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data/create_from_impl_for_enum.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::on_enum_data::utils::concat_enum_with_variant; 2 | use crate::structural_convert::on_fields_named::create_from_match_branch_for_fields_named::create_from_match_branch_for_fields_named; 3 | 4 | use crate::structural_convert::attributes::EnumVariantAttributes; 5 | use crate::structural_convert::conversion_error::ConversionError; 6 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 7 | use darling::FromAttributes; 8 | use proc_macro2::Ident; 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | use syn::DataEnum; 12 | use syn::Fields; 13 | use syn::Path; 14 | 15 | pub fn create_from_impl_for_enum( 16 | from_path: &Path, 17 | enum_data: &DataEnum, 18 | into_path: &Path, 19 | ) -> darling::Result { 20 | let mut catch_all_branches = vec![]; 21 | 22 | let match_branches = enum_data 23 | .variants 24 | .iter() 25 | .filter_map(|variant| { 26 | let variant_ident = variant.ident.clone(); 27 | let into_variant_ident = &variant_ident; 28 | let attrs = match EnumVariantAttributes::from_attributes(&variant.attrs) { 29 | Ok(ok) => ok, 30 | Err(err) => return Some(Err(err)), 31 | } 32 | .from; 33 | 34 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 35 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 36 | if default_attrs.is_some() && has_targeted_attrs { 37 | return Some(Err(darling::Error::custom( 38 | "Mixing attributes with 'for' path and no path is not allowed", 39 | ) 40 | .with_span(variant))); 41 | } 42 | let skip = attrs.iter().any(|e| match &e.target { 43 | Some(target) if target == from_path => e.skip, 44 | Some(_) => false, 45 | None => e.skip, 46 | }); 47 | if skip { 48 | return None; 49 | } 50 | 51 | let default = attrs.iter().any(|e| match &e.target { 52 | Some(target) if target == from_path => e.default, 53 | Some(_) => false, 54 | None => e.default, 55 | }); 56 | 57 | let from_variant_ident: &Ident = attrs 58 | .iter() 59 | .find_map(|e| match &e.target { 60 | Some(target) if target == from_path => e.rename.as_ref(), 61 | Some(_) => None, 62 | _ => e.rename.as_ref(), 63 | }) 64 | .unwrap_or(&variant_ident); 65 | 66 | let from_path = concat_enum_with_variant(from_path, from_variant_ident); 67 | let into_path = concat_enum_with_variant(into_path, into_variant_ident); 68 | 69 | let branch = match &variant.fields { 70 | Fields::Unit if default => { 71 | catch_all_branches.push(quote!(_ => #into_path.into())); 72 | return None; 73 | } 74 | Fields::Unit => { 75 | quote! { 76 | #from_path => #into_path.into() 77 | } 78 | } 79 | Fields::Unnamed(fields_unnamed) if default => { 80 | let default_expr = fields_unnamed 81 | .unnamed 82 | .iter() 83 | .map(|_| quote!(Default::default())) 84 | .collect::>(); 85 | catch_all_branches.push(quote!(_ => #into_path(#(#default_expr,)*))); 86 | return None; 87 | } 88 | Fields::Unnamed(fields_unnamed) => match create_match_branch_for_fields_unnamed( 89 | &from_path, 90 | |field, _| quote! {#field.into()}, 91 | &into_path, 92 | fields_unnamed, 93 | None, 94 | ConversionError::empty(), 95 | ) { 96 | Ok(ok) => ok, 97 | Err(err) => return Some(Err(err)), 98 | }, 99 | Fields::Named(fields_named) => { 100 | match create_from_match_branch_for_fields_named( 101 | &from_path, 102 | fields_named, 103 | &into_path, 104 | ) { 105 | Ok(ok) => ok, 106 | Err(err) => return Some(Err(err)), 107 | } 108 | } 109 | }; 110 | Some(Ok(branch)) 111 | }) 112 | .collect::>>()?; 113 | Ok(quote!( 114 | #[automatically_derived] 115 | impl From<#from_path> for #into_path { 116 | fn from(value: #from_path) -> Self { 117 | match value { 118 | #(#match_branches),* 119 | #(#catch_all_branches),* 120 | } 121 | } 122 | } 123 | )) 124 | } 125 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data/create_into_impl_for_enum.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::on_enum_data::utils::concat_enum_with_variant; 2 | use crate::structural_convert::on_fields_named::create_into_match_branch_for_fields_named::create_into_match_branch_for_fields_named; 3 | 4 | use crate::structural_convert::attributes::EnumVariantAttributes; 5 | use crate::structural_convert::conversion_error::ConversionError; 6 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 7 | use darling::FromAttributes; 8 | use proc_macro2::Ident; 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | use syn::DataEnum; 12 | use syn::Fields; 13 | use syn::Path; 14 | 15 | pub fn create_into_impl_for_enum( 16 | from_path: &Path, 17 | enum_data: &DataEnum, 18 | into_path: &Path, 19 | default_for_fields: &[Ident], 20 | default: bool, 21 | ) -> darling::Result { 22 | let mut catch_all_branches = vec![]; 23 | 24 | if default { 25 | catch_all_branches.push(quote! { _ => #into_path::default()}) 26 | } 27 | 28 | let match_branches = enum_data 29 | .variants 30 | .iter() 31 | .filter_map(|variant| { 32 | let variant_ident = variant.ident.clone(); 33 | let from_variant_ident = &variant_ident; 34 | let attrs = match EnumVariantAttributes::from_attributes(&variant.attrs) { 35 | Ok(ok) => ok, 36 | Err(err) => return Some(Err(err)), 37 | } 38 | .into; 39 | 40 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 41 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 42 | if default_attrs.is_some() && has_targeted_attrs { 43 | return Some(Err(darling::Error::custom( 44 | "Mixing attributes with 'for' path and no path is not allowed", 45 | ) 46 | .with_span(variant))); 47 | } 48 | let skip = attrs.iter().any(|e| match &e.target { 49 | Some(target) if target == into_path => e.skip, 50 | Some(_) => false, 51 | None => e.skip, 52 | }); 53 | if skip { 54 | return None; 55 | } 56 | 57 | let skip_after = attrs.iter().find_map(|e| match &e.target { 58 | Some(target) if target == into_path => e.skip_after, 59 | Some(_) => None, 60 | None => e.skip_after, 61 | }); 62 | 63 | let into_variant_ident: &Ident = attrs 64 | .iter() 65 | .find_map(|e| match &e.target { 66 | Some(target) if target == into_path => e.rename.as_ref(), 67 | Some(_) => None, 68 | _ => e.rename.as_ref(), 69 | }) 70 | .unwrap_or(&variant_ident); 71 | 72 | let from_path = concat_enum_with_variant(from_path, from_variant_ident); 73 | let into_path = concat_enum_with_variant(into_path, into_variant_ident); 74 | 75 | let branch = match &variant.fields { 76 | Fields::Unit => { 77 | quote! { 78 | #from_path => #into_path.into() 79 | } 80 | } 81 | Fields::Unnamed(fields_unnamed) => match create_match_branch_for_fields_unnamed( 82 | &from_path, 83 | |field, _| quote!(#field.into()), 84 | &into_path, 85 | fields_unnamed, 86 | skip_after, 87 | ConversionError::empty(), 88 | ) { 89 | Ok(ok) => ok, 90 | Err(err) => return Some(Err(err)), 91 | }, 92 | Fields::Named(fields_named) => match create_into_match_branch_for_fields_named( 93 | &from_path, 94 | fields_named, 95 | &into_path, 96 | default_for_fields, 97 | ) { 98 | Ok(ok) => ok, 99 | Err(err) => return Some(Err(err)), 100 | }, 101 | }; 102 | Some(Ok(branch)) 103 | }) 104 | .collect::>>()?; 105 | Ok(quote!( 106 | #[automatically_derived] 107 | impl From<#from_path> for #into_path { 108 | fn from(value: #from_path) -> Self { 109 | match value { 110 | #(#match_branches),* 111 | #(#catch_all_branches),* 112 | } 113 | } 114 | } 115 | )) 116 | } 117 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data/create_try_from_impl_for_enum.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::on_enum_data::utils::concat_enum_with_variant; 2 | use crate::structural_convert::on_fields_named::create_try_from_match_branch_for_fields_named::create_try_from_match_branch_for_fields_named; 3 | 4 | use crate::structural_convert::attributes::EnumVariantAttributes; 5 | use crate::structural_convert::conversion_error::ConversionError; 6 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 7 | use darling::FromAttributes; 8 | use proc_macro2::Ident; 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | use syn::DataEnum; 12 | use syn::Fields; 13 | use syn::Path; 14 | 15 | pub fn create_try_from_impl_for_enum( 16 | from_path: &Path, 17 | enum_data: &DataEnum, 18 | into_path: &Path, 19 | ) -> darling::Result { 20 | let mut catch_all_branches = vec![]; 21 | 22 | let match_branches = enum_data 23 | .variants 24 | .iter() 25 | .filter_map(|variant| { 26 | let mut conversion_error = ConversionError::new(from_path, into_path); 27 | 28 | let variant_ident = variant.ident.clone(); 29 | let into_variant_ident = &variant_ident; 30 | let attrs = match EnumVariantAttributes::from_attributes(&variant.attrs) { 31 | Ok(ok) => ok, 32 | Err(err) => return Some(Err(err)), 33 | } 34 | .try_from; 35 | 36 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 37 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 38 | if default_attrs.is_some() && has_targeted_attrs { 39 | return Some(Err(darling::Error::custom( 40 | "Mixing attributes with 'for' path and no path is not allowed", 41 | ) 42 | .with_span(variant))); 43 | } 44 | let skip = attrs.iter().any(|e| match &e.target { 45 | Some(target) if target == from_path => e.skip, 46 | Some(_) => false, 47 | None => e.skip, 48 | }); 49 | if skip { 50 | return None; 51 | } 52 | 53 | let default = attrs.iter().any(|e| match &e.target { 54 | Some(target) if target == from_path => e.default, 55 | Some(_) => false, 56 | None => e.default, 57 | }); 58 | 59 | let from_variant_ident: &Ident = attrs 60 | .iter() 61 | .find_map(|e| match &e.target { 62 | Some(target) if target == from_path => e.rename.as_ref(), 63 | Some(_) => None, 64 | _ => e.rename.as_ref(), 65 | }) 66 | .unwrap_or(&variant_ident); 67 | 68 | conversion_error.from.named(from_variant_ident); 69 | conversion_error.into.named(into_variant_ident); 70 | 71 | let from_path = concat_enum_with_variant(from_path, from_variant_ident); 72 | let into_path = concat_enum_with_variant(into_path, into_variant_ident); 73 | 74 | let branch = match &variant.fields { 75 | Fields::Unit if default => { 76 | catch_all_branches.push(quote!(_ => #into_path.into())); 77 | return None; 78 | } 79 | Fields::Unit => { 80 | let err = conversion_error.to_string(); 81 | quote! { 82 | #from_path => #into_path.try_into().map_err(|err| #err)? 83 | } 84 | } 85 | Fields::Unnamed(fields_unnamed) if default => { 86 | let default_expr = fields_unnamed 87 | .unnamed 88 | .iter() 89 | .map(|_| quote!(Default::default())) 90 | .collect::>(); 91 | catch_all_branches.push(quote!(_ => #into_path(#(#default_expr,)*))); 92 | return None; 93 | } 94 | Fields::Unnamed(fields_unnamed) => { 95 | match create_match_branch_for_fields_unnamed( 96 | &from_path, 97 | |field, err| quote!(#field.try_into().map_err(|err| #err)?), 98 | &into_path, 99 | fields_unnamed, 100 | None, 101 | conversion_error, 102 | ) { 103 | Ok(ok) => ok, 104 | Err(err) => return Some(Err(err)), 105 | } 106 | } 107 | Fields::Named(fields_named) => { 108 | match create_try_from_match_branch_for_fields_named( 109 | &from_path, 110 | fields_named, 111 | &into_path, 112 | conversion_error, 113 | ) { 114 | Ok(ok) => ok, 115 | Err(err) => return Some(Err(err)), 116 | } 117 | } 118 | }; 119 | Some(Ok(branch)) 120 | }) 121 | .collect::>>()?; 122 | Ok(quote!( 123 | #[automatically_derived] 124 | impl TryFrom<#from_path> for #into_path { 125 | type Error = String; 126 | 127 | fn try_from(value: #from_path) -> Result { 128 | Ok(match value { 129 | #(#match_branches),* 130 | #(#catch_all_branches),* 131 | }) 132 | } 133 | } 134 | )) 135 | } 136 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data/create_try_into_impl_for_enum.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::on_enum_data::utils::concat_enum_with_variant; 2 | use crate::structural_convert::on_fields_named::create_try_into_match_branch_for_fields_named::create_try_into_match_branch_for_fields_named; 3 | 4 | use crate::structural_convert::attributes::EnumVariantAttributes; 5 | use crate::structural_convert::conversion_error::ConversionError; 6 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 7 | use darling::FromAttributes; 8 | use proc_macro2::Ident; 9 | use proc_macro2::TokenStream; 10 | use quote::quote; 11 | use syn::DataEnum; 12 | use syn::Fields; 13 | use syn::Path; 14 | 15 | pub fn create_try_into_impl_for_enum( 16 | from_path: &Path, 17 | enum_data: &DataEnum, 18 | into_path: &Path, 19 | default_for_fields: &[Ident], 20 | default: bool, 21 | ) -> darling::Result { 22 | let mut catch_all_branches = vec![]; 23 | 24 | if default { 25 | catch_all_branches.push(quote! { _ => #into_path::default()}) 26 | } 27 | 28 | let match_branches = enum_data 29 | .variants 30 | .iter() 31 | .filter_map(|variant| { 32 | let mut conversion_error = ConversionError::new(from_path, into_path); 33 | 34 | let variant_ident = variant.ident.clone(); 35 | let from_variant_ident = &variant_ident; 36 | let attrs = match EnumVariantAttributes::from_attributes(&variant.attrs) { 37 | Ok(ok) => ok, 38 | Err(err) => return Some(Err(err)), 39 | } 40 | .try_into; 41 | 42 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 43 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 44 | if default_attrs.is_some() && has_targeted_attrs { 45 | return Some(Err(darling::Error::custom( 46 | "Mixing attributes with 'for' path and no path is not allowed", 47 | ) 48 | .with_span(variant))); 49 | } 50 | let skip = attrs.iter().any(|e| match &e.target { 51 | Some(target) if target == into_path => e.skip, 52 | Some(_) => false, 53 | None => e.skip, 54 | }); 55 | if skip { 56 | return None; 57 | } 58 | 59 | let skip_after = attrs.iter().find_map(|e| match &e.target { 60 | Some(target) if target == into_path => e.skip_after, 61 | Some(_) => None, 62 | None => e.skip_after, 63 | }); 64 | 65 | let into_variant_ident: &Ident = attrs 66 | .iter() 67 | .find_map(|e| match &e.target { 68 | Some(target) if target == into_path => e.rename.as_ref(), 69 | Some(_) => None, 70 | _ => e.rename.as_ref(), 71 | }) 72 | .unwrap_or(&variant_ident); 73 | 74 | conversion_error.from.named(from_variant_ident); 75 | conversion_error.into.named(into_variant_ident); 76 | 77 | let from_path = concat_enum_with_variant(from_path, from_variant_ident); 78 | let into_path = concat_enum_with_variant(into_path, into_variant_ident); 79 | 80 | let branch = match &variant.fields { 81 | Fields::Unit => { 82 | let err = conversion_error.to_string(); 83 | quote! { 84 | #from_path => #into_path.try_into().map_err(|err| #err)? 85 | } 86 | } 87 | Fields::Unnamed(fields_unnamed) => { 88 | match create_match_branch_for_fields_unnamed( 89 | &from_path, 90 | |field, err| quote!(#field.try_into().map_err(|err| #err)?), 91 | &into_path, 92 | fields_unnamed, 93 | skip_after, 94 | conversion_error, 95 | ) { 96 | Ok(ok) => ok, 97 | Err(err) => return Some(Err(err)), 98 | } 99 | } 100 | Fields::Named(fields_named) => { 101 | match create_try_into_match_branch_for_fields_named( 102 | &from_path, 103 | fields_named, 104 | &into_path, 105 | default_for_fields, 106 | conversion_error, 107 | ) { 108 | Ok(ok) => ok, 109 | Err(err) => return Some(Err(err)), 110 | } 111 | } 112 | }; 113 | Some(Ok(branch)) 114 | }) 115 | .collect::>>()?; 116 | Ok(quote!( 117 | #[automatically_derived] 118 | impl TryFrom<#from_path> for #into_path { 119 | type Error = String; 120 | 121 | fn try_from(value: #from_path) -> Result { 122 | Ok(match value { 123 | #(#match_branches),* 124 | #(#catch_all_branches),* 125 | }) 126 | } 127 | } 128 | )) 129 | } 130 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_enum_data/utils.rs: -------------------------------------------------------------------------------- 1 | use syn::Ident; 2 | use syn::Path; 3 | use syn::PathSegment; 4 | 5 | pub fn concat_enum_with_variant(enum_path: &Path, variant_ident: &Ident) -> Path { 6 | let segment = PathSegment { 7 | ident: variant_ident.clone(), 8 | arguments: Default::default(), 9 | }; 10 | Path { 11 | leading_colon: None, 12 | segments: enum_path 13 | .clone() 14 | .segments 15 | .into_iter() 16 | .chain(std::iter::once(segment)) 17 | .collect(), 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_field_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use quote::ToTokens; 4 | use syn::GenericArgument; 5 | use syn::PathArguments; 6 | use syn::Type; 7 | use syn::TypePath; 8 | use syn::TypeTuple; 9 | 10 | #[derive(Clone)] 11 | pub enum MyType { 12 | Simple(Type), 13 | Option(TypePath, Box), 14 | Result(TypePath, Box, Box), 15 | List(TypePath, Box), 16 | Map(TypePath, Box, Box), 17 | Tuple(TypeTuple, Vec), 18 | } 19 | 20 | impl fmt::Debug for MyType { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | match self { 23 | MyType::Simple(ty) => write!(f, "Simple({})", ty.to_token_stream()), 24 | MyType::Option(tp, g) => write!(f, "Option({}, {g:?})", tp.to_token_stream(),), 25 | MyType::List(tp, g) => { 26 | write!(f, "List({}, {g:?})", tp.to_token_stream()) 27 | } 28 | MyType::Result(tp, g1, g2) => { 29 | write!(f, "Result({}, {g1:?}, {g2:?})", tp.to_token_stream(),) 30 | } 31 | MyType::Map(tp, g1, g2) => { 32 | write!(f, "Map({}, {g1:?}, {g2:?})", tp.to_token_stream(),) 33 | } 34 | MyType::Tuple(tt, members) => { 35 | write!(f, "Map({}, {members:?})", tt.to_token_stream(),) 36 | } 37 | } 38 | } 39 | } 40 | 41 | pub fn recursive_type(ty: &Type) -> darling::Result { 42 | let simple = MyType::Simple(ty.clone()); 43 | 44 | match &ty { 45 | Type::Path(typepath) if typepath.qself.is_none() => { 46 | let Some(end_path) = typepath.path.segments.last() else { 47 | return Err(darling::Error::unexpected_type( 48 | &ty.to_token_stream().to_string(), 49 | )); 50 | }; 51 | let end_name = end_path.ident.to_string(); 52 | let generics = &end_path.arguments; 53 | 54 | (|| match end_name.as_str() { 55 | "Option" => { 56 | let PathArguments::AngleBracketed(generics) = generics else { 57 | return Ok(simple); 58 | }; 59 | let Some(GenericArgument::Type(generic)) = generics.args.iter().next() else { 60 | return Ok(simple); 61 | }; 62 | let my_generic = recursive_type(generic)?; 63 | Ok(MyType::Option(typepath.clone(), Box::new(my_generic))) 64 | } 65 | "Vec" | "VecDeque" | "LinkedList" | "BTreeSet" | "HashSet" | "BinaryHeap" => { 66 | let PathArguments::AngleBracketed(generics) = generics else { 67 | return Ok(simple); 68 | }; 69 | let Some(GenericArgument::Type(generic)) = generics.args.iter().next() else { 70 | return Ok(simple); 71 | }; 72 | let my_generic = recursive_type(generic)?; 73 | Ok(MyType::List(typepath.clone(), Box::new(my_generic))) 74 | } 75 | "Result" => { 76 | let PathArguments::AngleBracketed(generics) = generics else { 77 | return Ok(simple); 78 | }; 79 | let mut generics = generics.args.iter(); 80 | let Some(GenericArgument::Type(generic1)) = generics.next() else { 81 | return Ok(simple); 82 | }; 83 | let Some(GenericArgument::Type(generic2)) = generics.next() else { 84 | return Ok(simple); 85 | }; 86 | let my_generic1 = recursive_type(generic1)?; 87 | let my_generic2 = recursive_type(generic2)?; 88 | Ok(MyType::Result( 89 | typepath.clone(), 90 | Box::new(my_generic1), 91 | Box::new(my_generic2), 92 | )) 93 | } 94 | "BTreeMap" | "HashMap" => { 95 | let PathArguments::AngleBracketed(generics) = generics else { 96 | return Ok(simple); 97 | }; 98 | let mut generics = generics.args.iter(); 99 | let Some(GenericArgument::Type(generic1)) = generics.next() else { 100 | return Ok(simple); 101 | }; 102 | let Some(GenericArgument::Type(generic2)) = generics.next() else { 103 | return Ok(simple); 104 | }; 105 | let my_generic1 = recursive_type(generic1)?; 106 | let my_generic2 = recursive_type(generic2)?; 107 | Ok(MyType::Map( 108 | typepath.clone(), 109 | Box::new(my_generic1), 110 | Box::new(my_generic2), 111 | )) 112 | } 113 | _ => Ok(simple), 114 | })() 115 | } 116 | Type::Tuple(tuple) => { 117 | let membs = tuple 118 | .elems 119 | .iter() 120 | .map(recursive_type) 121 | .collect::>()?; 122 | Ok(MyType::Tuple(tuple.clone(), membs)) 123 | } 124 | _ => Ok(simple), 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named.rs: -------------------------------------------------------------------------------- 1 | pub mod create_from_match_branch_for_fields_named; 2 | pub mod create_into_match_branch_for_fields_named; 3 | pub mod create_match_branch_for_fields_named; 4 | pub mod create_try_from_match_branch_for_fields_named; 5 | pub mod create_try_into_match_branch_for_fields_named; 6 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named/create_from_match_branch_for_fields_named.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::attributes::FieldNamedAttributes; 2 | use crate::structural_convert::conversion_error::ConversionError; 3 | use darling::FromAttributes; 4 | use proc_macro2::Ident; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | use syn::FieldsNamed; 9 | use syn::Path; 10 | 11 | use super::create_match_branch_for_fields_named::create_match_branch_for_fields_named; 12 | use super::create_match_branch_for_fields_named::FieldsNamedMatchBranchData; 13 | use super::create_match_branch_for_fields_named::IntoFromPair; 14 | 15 | pub fn create_from_match_branch_for_fields_named( 16 | from_path: &Path, 17 | fields_named: &FieldsNamed, 18 | into_path: &Path, 19 | ) -> darling::Result { 20 | let match_branch_data = fields_named 21 | .named 22 | .iter() 23 | .map(|f| { 24 | let Some(ident) = f.ident.as_ref() else { 25 | unreachable!() 26 | }; 27 | let field_type = f.ty.clone(); 28 | 29 | let attrs = FieldNamedAttributes::from_attributes(&f.attrs)?.from; 30 | 31 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 32 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 33 | if default_attrs.is_some() && has_targeted_attrs { 34 | return Err(darling::Error::custom( 35 | "Mixing attributes with 'for' path and no path is not allowed", 36 | ) 37 | .with_span(f)); 38 | } 39 | 40 | let default = attrs.iter().any(|e| match &e.target { 41 | Some(target) if target == from_path => e.default, 42 | Some(_) => false, 43 | None => e.default, 44 | }); 45 | 46 | let as_type = attrs.iter().find_map(|e| match &e.target { 47 | Some(target) if target == from_path => e.as_type.clone(), 48 | Some(_) => None, 49 | None => e.as_type.clone(), 50 | }); 51 | 52 | let from_field_ident: Ident = attrs 53 | .iter() 54 | .find_map(|e| match &e.target { 55 | Some(target) if target == from_path => e.rename.clone(), 56 | Some(_) => None, 57 | _ => e.rename.clone(), 58 | }) 59 | .unwrap_or_else(|| ident.clone()); 60 | 61 | let into_from_pair = IntoFromPair { 62 | into_field_name: ident.clone(), 63 | from_field_ident: (!default).then_some(from_field_ident.clone()), 64 | }; 65 | 66 | Ok(FieldsNamedMatchBranchData { 67 | lhs_field_name: (!default).then_some(from_field_ident), 68 | into_from_pair, 69 | as_type, 70 | field_type, 71 | }) 72 | }) 73 | .collect::>>()?; 74 | 75 | create_match_branch_for_fields_named( 76 | from_path, 77 | |field_name, _| quote!(#field_name.into()), 78 | into_path, 79 | match_branch_data, 80 | &[], 81 | ConversionError::empty(), 82 | ) 83 | } 84 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named/create_into_match_branch_for_fields_named.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::attributes::FieldNamedAttributes; 2 | use crate::structural_convert::conversion_error::ConversionError; 3 | use darling::FromAttributes; 4 | use proc_macro2::Ident; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | use syn::FieldsNamed; 9 | use syn::Path; 10 | 11 | use super::create_match_branch_for_fields_named::create_match_branch_for_fields_named; 12 | use super::create_match_branch_for_fields_named::FieldsNamedMatchBranchData; 13 | use super::create_match_branch_for_fields_named::IntoFromPair; 14 | 15 | pub fn create_into_match_branch_for_fields_named( 16 | from_path: &Path, 17 | fields_named: &FieldsNamed, 18 | into_path: &Path, 19 | added_default_fields: &[Ident], 20 | ) -> darling::Result { 21 | let match_branch_data = fields_named 22 | .named 23 | .iter() 24 | .filter_map(|f| { 25 | let Some(ident) = f.ident.as_ref() else { 26 | unreachable!() 27 | }; 28 | let field_type = f.ty.clone(); 29 | 30 | let attrs = match FieldNamedAttributes::from_attributes(&f.attrs) { 31 | Ok(ok) => ok, 32 | Err(err) => return Some(Err(err)), 33 | } 34 | .into; 35 | 36 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 37 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 38 | if default_attrs.is_some() && has_targeted_attrs { 39 | return Some(Err(darling::Error::custom( 40 | "Mixing attributes with 'for' path and no path is not allowed", 41 | ) 42 | .with_span(f))); 43 | } 44 | 45 | let skip = attrs.iter().any(|e| match &e.target { 46 | Some(target) if target == into_path => e.skip, 47 | Some(_) => false, 48 | None => e.skip, 49 | }); 50 | if skip { 51 | return None; 52 | } 53 | 54 | let as_type = attrs.iter().find_map(|e| match &e.target { 55 | Some(target) if target == into_path => e.as_type.clone(), 56 | Some(_) => None, 57 | None => e.as_type.clone(), 58 | }); 59 | 60 | let into_field_ident: Ident = attrs 61 | .iter() 62 | .find_map(|e| match &e.target { 63 | Some(target) if target == into_path => e.rename.clone(), 64 | Some(_) => None, 65 | _ => e.rename.clone(), 66 | }) 67 | .unwrap_or_else(|| ident.clone()); 68 | 69 | let into_from_pair = IntoFromPair { 70 | into_field_name: into_field_ident.clone(), 71 | from_field_ident: Some(ident.clone()), 72 | }; 73 | 74 | Some(Ok(FieldsNamedMatchBranchData { 75 | lhs_field_name: Some(ident.clone()), 76 | into_from_pair, 77 | as_type, 78 | field_type, 79 | })) 80 | }) 81 | .collect::>>()?; 82 | 83 | create_match_branch_for_fields_named( 84 | from_path, 85 | |field_name, _| quote!(#field_name.into()), 86 | into_path, 87 | match_branch_data, 88 | added_default_fields, 89 | ConversionError::empty(), 90 | ) 91 | } 92 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named/create_match_branch_for_fields_named.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::Ident; 2 | use proc_macro2::TokenStream; 3 | use quote::quote; 4 | use syn::Path; 5 | use syn::Type; 6 | 7 | use crate::structural_convert::conversion_error::ConversionError; 8 | use crate::structural_convert::on_field_type::recursive_type; 9 | use crate::structural_convert::on_field_type::MyType; 10 | use crate::structural_convert::on_fields_unnamed::recursively_create_expr; 11 | 12 | /// Expected to become these tokens: 13 | /// #from_path{ 14 | /// #(#lhs_field_name,)* 15 | /// .. 16 | /// } => #into_path{ 17 | /// #(#into_field_name: #from_field_expr,)* // This is into_from_pair 18 | /// } 19 | /// aka 20 | /// ```compile_fail 21 | /// SomeStructA { x, a, ..} => SomeStructB { 22 | /// x: x.into(), 23 | /// z: a.into(), 24 | /// y: Default::default(), 25 | /// } 26 | /// ``` 27 | pub struct FieldsNamedMatchBranchData { 28 | pub lhs_field_name: Option, 29 | pub into_from_pair: IntoFromPair, 30 | pub as_type: Option, 31 | pub field_type: Type, 32 | } 33 | 34 | /// Expected to become these tokens: 35 | /// #(#into_field_name: #from_field_ident.into(),)* 36 | /// aka 37 | /// ```compile_fail 38 | /// x: y.into(), 39 | /// z: z.into(), 40 | /// y: Default::default(), 41 | /// ``` 42 | pub struct IntoFromPair { 43 | pub into_field_name: Ident, 44 | pub from_field_ident: Option, 45 | } 46 | 47 | pub fn create_match_branch_for_fields_named( 48 | from_path: &Path, 49 | into_expr: impl Fn(TokenStream, TokenStream) -> TokenStream, 50 | into_path: &Path, 51 | mut match_branch_data: Vec, 52 | added_default_fields: &[Ident], 53 | conversion_error: ConversionError, 54 | ) -> darling::Result { 55 | for default_field_name in added_default_fields { 56 | match_branch_data.push(FieldsNamedMatchBranchData { 57 | lhs_field_name: None, 58 | into_from_pair: IntoFromPair { 59 | into_field_name: default_field_name.clone(), 60 | from_field_ident: None, 61 | }, 62 | as_type: None, 63 | field_type: Type::Verbatim(Default::default()), 64 | }) 65 | } 66 | 67 | let mut lhs_field_name = vec![]; 68 | let mut into_field_name = vec![]; 69 | let mut from_field_expr = vec![]; 70 | for item in match_branch_data.into_iter() { 71 | let mut conversion_error = conversion_error.clone(); 72 | 73 | if let Some(field_name) = item.lhs_field_name { 74 | lhs_field_name.push(field_name); 75 | 76 | conversion_error 77 | .from 78 | .named(&item.into_from_pair.into_field_name); 79 | } 80 | 81 | conversion_error 82 | .into 83 | .named(&item.into_from_pair.into_field_name); 84 | into_field_name.push(item.into_from_pair.into_field_name); 85 | 86 | let mut expr = quote!(Default::default()); 87 | let mut tiep = item.field_type.clone(); 88 | 89 | if let Some(field_name) = item.into_from_pair.from_field_ident { 90 | expr = Default::default(); 91 | let mut identy = quote!(#field_name); 92 | 93 | if let Some(as_type) = item.as_type { 94 | let from_type = recursive_type(&tiep)?; 95 | 96 | tiep = as_type.clone(); 97 | 98 | let mut parsed_as_type = recursive_type(&as_type)?; 99 | 100 | // From for Option is implemented 101 | if let (MyType::Simple(_), MyType::Option(_, inner)) = (&from_type, &parsed_as_type) 102 | { 103 | parsed_as_type = *inner.clone(); 104 | } 105 | 106 | let as_expr = recursively_create_expr( 107 | expr.clone(), 108 | parsed_as_type, 109 | &identy, 110 | &into_expr, 111 | &mut conversion_error, 112 | 0, 113 | ); 114 | 115 | identy = quote!({ 116 | let temp: #as_type = #as_expr; 117 | temp 118 | }); 119 | } 120 | 121 | expr = recursively_create_expr( 122 | expr, 123 | recursive_type(&tiep)?, 124 | &identy, 125 | &into_expr, 126 | &mut conversion_error, 127 | 0, 128 | ); 129 | } 130 | 131 | from_field_expr.push(expr); 132 | } 133 | 134 | Ok(quote! { 135 | #from_path{ 136 | #(#lhs_field_name,)* 137 | .. 138 | } => #into_path{ 139 | #(#into_field_name: #from_field_expr,)* 140 | } 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named/create_try_from_match_branch_for_fields_named.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::attributes::FieldNamedAttributes; 2 | use crate::structural_convert::conversion_error::ConversionError; 3 | use darling::FromAttributes; 4 | use proc_macro2::Ident; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | use syn::FieldsNamed; 9 | use syn::Path; 10 | 11 | use super::create_match_branch_for_fields_named::create_match_branch_for_fields_named; 12 | use super::create_match_branch_for_fields_named::FieldsNamedMatchBranchData; 13 | use super::create_match_branch_for_fields_named::IntoFromPair; 14 | 15 | pub fn create_try_from_match_branch_for_fields_named( 16 | from_path: &Path, 17 | fields_named: &FieldsNamed, 18 | into_path: &Path, 19 | conversion_error: ConversionError, 20 | ) -> darling::Result { 21 | let match_branch_data = fields_named 22 | .named 23 | .iter() 24 | .map(|f| { 25 | let Some(ident) = f.ident.as_ref() else { 26 | unreachable!() 27 | }; 28 | let field_type = f.ty.clone(); 29 | 30 | let attrs = FieldNamedAttributes::from_attributes(&f.attrs)?.try_from; 31 | 32 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 33 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 34 | if default_attrs.is_some() && has_targeted_attrs { 35 | return Err(darling::Error::custom( 36 | "Mixing attributes with 'for' path and no path is not allowed", 37 | ) 38 | .with_span(f)); 39 | } 40 | 41 | let default = attrs.iter().any(|e| match &e.target { 42 | Some(target) if target == from_path => e.default, 43 | Some(_) => false, 44 | None => e.default, 45 | }); 46 | 47 | let as_type = attrs.iter().find_map(|e| match &e.target { 48 | Some(target) if target == from_path => e.as_type.clone(), 49 | Some(_) => None, 50 | None => e.as_type.clone(), 51 | }); 52 | 53 | let from_field_ident: Ident = attrs 54 | .iter() 55 | .find_map(|e| match &e.target { 56 | Some(target) if target == from_path => e.rename.clone(), 57 | Some(_) => None, 58 | _ => e.rename.clone(), 59 | }) 60 | .unwrap_or_else(|| ident.clone()); 61 | 62 | let into_from_pair = IntoFromPair { 63 | into_field_name: ident.clone(), 64 | from_field_ident: (!default).then_some(from_field_ident.clone()), 65 | }; 66 | 67 | Ok(FieldsNamedMatchBranchData { 68 | lhs_field_name: (!default).then_some(from_field_ident), 69 | into_from_pair, 70 | as_type, 71 | field_type, 72 | }) 73 | }) 74 | .collect::>>()?; 75 | 76 | create_match_branch_for_fields_named( 77 | from_path, 78 | |field_name, err| { 79 | quote!( 80 | #field_name 81 | .try_into().map_err(|err| #err)? 82 | ) 83 | }, 84 | into_path, 85 | match_branch_data, 86 | &[], 87 | conversion_error, 88 | ) 89 | } 90 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_named/create_try_into_match_branch_for_fields_named.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::attributes::FieldNamedAttributes; 2 | use crate::structural_convert::conversion_error::ConversionError; 3 | use darling::FromAttributes; 4 | use proc_macro2::Ident; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | 8 | use syn::FieldsNamed; 9 | use syn::Path; 10 | 11 | use super::create_match_branch_for_fields_named::create_match_branch_for_fields_named; 12 | use super::create_match_branch_for_fields_named::FieldsNamedMatchBranchData; 13 | use super::create_match_branch_for_fields_named::IntoFromPair; 14 | 15 | pub fn create_try_into_match_branch_for_fields_named( 16 | from_path: &Path, 17 | fields_named: &FieldsNamed, 18 | into_path: &Path, 19 | added_default_fields: &[Ident], 20 | conversion_error: ConversionError, 21 | ) -> darling::Result { 22 | let match_branch_data = fields_named 23 | .named 24 | .iter() 25 | .filter_map(|f| { 26 | let Some(ident) = f.ident.as_ref() else { 27 | unreachable!() 28 | }; 29 | let field_type = f.ty.clone(); 30 | 31 | let attrs = match FieldNamedAttributes::from_attributes(&f.attrs) { 32 | Ok(ok) => ok, 33 | Err(err) => return Some(Err(err)), 34 | } 35 | .try_into; 36 | 37 | let default_attrs = attrs.iter().find(|e| e.target.is_none()); 38 | let has_targeted_attrs = attrs.iter().any(|e| e.target.is_some()); 39 | if default_attrs.is_some() && has_targeted_attrs { 40 | return Some(Err(darling::Error::custom( 41 | "Mixing attributes with 'for' path and no path is not allowed", 42 | ) 43 | .with_span(f))); 44 | } 45 | 46 | let skip = attrs.iter().any(|e| match &e.target { 47 | Some(target) if target == into_path => e.skip, 48 | Some(_) => false, 49 | None => e.skip, 50 | }); 51 | if skip { 52 | return None; 53 | } 54 | 55 | let as_type = attrs.iter().find_map(|e| match &e.target { 56 | Some(target) if target == into_path => e.as_type.clone(), 57 | Some(_) => None, 58 | None => e.as_type.clone(), 59 | }); 60 | 61 | let into_field_ident: Ident = attrs 62 | .iter() 63 | .find_map(|e| match &e.target { 64 | Some(target) if target == into_path => e.rename.clone(), 65 | Some(_) => None, 66 | _ => e.rename.clone(), 67 | }) 68 | .unwrap_or_else(|| ident.clone()); 69 | 70 | let into_from_pair = IntoFromPair { 71 | into_field_name: into_field_ident.clone(), 72 | from_field_ident: Some(ident.clone()), 73 | }; 74 | 75 | Some(Ok(FieldsNamedMatchBranchData { 76 | lhs_field_name: Some(ident.clone()), 77 | into_from_pair, 78 | as_type, 79 | field_type, 80 | })) 81 | }) 82 | .collect::>>()?; 83 | 84 | create_match_branch_for_fields_named( 85 | from_path, 86 | |field_name, err| quote!(#field_name.try_into().map_err(|err| #err)?), 87 | into_path, 88 | match_branch_data, 89 | added_default_fields, 90 | conversion_error, 91 | ) 92 | } 93 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_fields_unnamed.rs: -------------------------------------------------------------------------------- 1 | use std::ops::AddAssign; 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::format_ident; 5 | use quote::quote; 6 | use quote::ToTokens; 7 | use syn::spanned::Spanned; 8 | use syn::FieldsUnnamed; 9 | use syn::Index; 10 | use syn::Path; 11 | 12 | use crate::structural_convert::on_field_type::recursive_type; 13 | use crate::structural_convert::on_field_type::MyType; 14 | 15 | use super::conversion_error::ConversionError; 16 | 17 | /// item1, item2, item3 ... 18 | pub fn create_match_branch_for_fields_unnamed( 19 | from_path: &Path, 20 | into_expr_fn: impl Fn(TokenStream, TokenStream) -> TokenStream, 21 | into_path: &Path, 22 | fields_unnamed: &FieldsUnnamed, 23 | skip_after: Option, 24 | conversion_error: ConversionError, 25 | ) -> darling::Result { 26 | let take_len = skip_after.unwrap_or(fields_unnamed.unnamed.len()); 27 | let (field_ident, into_expr): (Vec<_>, Vec<_>) = fields_unnamed 28 | .unnamed 29 | .iter() 30 | .enumerate() 31 | .map(|(i, field)| { 32 | let mut conversion_error = conversion_error.clone(); 33 | let ident = format_ident!("item{i}"); 34 | let mut into_expr = Default::default(); 35 | 36 | conversion_error.from.unnamed(i); 37 | conversion_error.into.unnamed(i); 38 | 39 | into_expr = recursively_create_expr( 40 | into_expr, 41 | recursive_type(&field.ty)?, 42 | "e!(#ident), 43 | &into_expr_fn, 44 | &mut conversion_error, 45 | 0, 46 | ); 47 | 48 | Ok((ident, into_expr)) 49 | }) 50 | .collect::>>()? 51 | .into_iter() 52 | .take(take_len) 53 | .unzip(); 54 | Ok(quote! { 55 | #from_path(#(#field_ident,)* ..) => #into_path(#(#into_expr,)*) 56 | }) 57 | } 58 | 59 | pub fn recursively_create_expr( 60 | into_expr: TokenStream, 61 | my_type: MyType, 62 | ident: &TokenStream, 63 | into_expr_fn: &impl Fn(TokenStream, TokenStream) -> TokenStream, 64 | conversion_error: &mut ConversionError, 65 | mut i_count: usize, 66 | ) -> TokenStream { 67 | match my_type { 68 | MyType::Simple(_) => into_expr_fn(ident.clone(), conversion_error.to_token_stream()), 69 | MyType::Option(_, generic) => { 70 | conversion_error.from.enum_variant("Some"); 71 | conversion_error.into.enum_variant("Some"); 72 | conversion_error.from.unnamed(0); 73 | conversion_error.into.unnamed(0); 74 | 75 | let new_ident = format_ident!("some").to_token_stream(); 76 | let into_expr = recursively_create_expr( 77 | into_expr, 78 | *generic, 79 | &new_ident, 80 | into_expr_fn, 81 | conversion_error, 82 | i_count, 83 | ); 84 | quote!(match #ident { 85 | None => None, 86 | Some(#new_ident) => Some(#into_expr), 87 | }) 88 | } 89 | MyType::List(_, generic) => { 90 | let i_ident = format_ident!("i{i_count}").to_token_stream(); 91 | i_count.add_assign(1); 92 | conversion_error.from.dyn_unnamed(&i_ident); 93 | conversion_error.into.dyn_unnamed(&i_ident); 94 | 95 | let new_ident = format_ident!("li").to_token_stream(); 96 | let into_expr = recursively_create_expr( 97 | into_expr, 98 | *generic, 99 | &new_ident, 100 | into_expr_fn, 101 | conversion_error, 102 | i_count, 103 | ); 104 | quote!({ 105 | let mut tmp = Vec::default(); 106 | for (#i_ident, #new_ident) in #ident.into_iter().enumerate() { 107 | tmp.push(#into_expr); 108 | } 109 | tmp.into_iter().collect() 110 | }) 111 | } 112 | MyType::Result(_, g1, g2) => { 113 | let new_ok_ident = format_ident!("ok").to_token_stream(); 114 | let into_ok_expr = { 115 | let mut conversion_error = conversion_error.clone(); 116 | 117 | conversion_error.from.enum_variant("Ok"); 118 | conversion_error.into.enum_variant("Ok"); 119 | conversion_error.from.unnamed(0); 120 | conversion_error.into.unnamed(0); 121 | 122 | recursively_create_expr( 123 | into_expr.clone(), 124 | *g1, 125 | &new_ok_ident, 126 | into_expr_fn, 127 | &mut conversion_error, 128 | i_count, 129 | ) 130 | }; 131 | 132 | let new_err_ident = format_ident!("err").to_token_stream(); 133 | let into_err_expr = { 134 | let mut conversion_error = conversion_error.clone(); 135 | 136 | conversion_error.from.enum_variant("Err"); 137 | conversion_error.into.enum_variant("Err"); 138 | conversion_error.from.unnamed(0); 139 | conversion_error.into.unnamed(0); 140 | 141 | recursively_create_expr( 142 | into_expr, 143 | *g2, 144 | &new_err_ident, 145 | into_expr_fn, 146 | &mut conversion_error, 147 | i_count, 148 | ) 149 | }; 150 | 151 | quote!(match #ident { 152 | Ok(#new_ok_ident) => Ok(#into_ok_expr), 153 | Err(#new_err_ident) => Err(#into_err_expr), 154 | }) 155 | } 156 | MyType::Map(_, g1, g2) => { 157 | let i_ident = format_ident!("i{i_count}").to_token_stream(); 158 | i_count.add_assign(1); 159 | conversion_error.from.dyn_unnamed(&i_ident); 160 | conversion_error.into.dyn_unnamed(&i_ident); 161 | 162 | let new_key_ident = format_ident!("key").to_token_stream(); 163 | let into_key_expr = { 164 | let mut conversion_error = conversion_error.clone(); 165 | conversion_error.from.enum_variant("key"); 166 | conversion_error.into.enum_variant("key"); 167 | 168 | recursively_create_expr( 169 | into_expr.clone(), 170 | *g1, 171 | &new_key_ident, 172 | into_expr_fn, 173 | &mut conversion_error, 174 | i_count, 175 | ) 176 | }; 177 | 178 | let new_val_ident = format_ident!("val").to_token_stream(); 179 | let into_val_expr = { 180 | let mut conversion_error = conversion_error.clone(); 181 | conversion_error.from.named_str("value"); 182 | conversion_error.into.named_str("value"); 183 | 184 | recursively_create_expr( 185 | into_expr, 186 | *g2, 187 | &new_val_ident, 188 | into_expr_fn, 189 | &mut conversion_error, 190 | i_count, 191 | ) 192 | }; 193 | 194 | quote!({ 195 | let mut tmp = Vec::default(); 196 | for (#i_ident, (#new_key_ident, #new_val_ident)) in #ident.into_iter().enumerate() { 197 | tmp.push((#into_key_expr, #into_val_expr)); 198 | } 199 | tmp.into_iter().collect() 200 | }) 201 | } 202 | MyType::Tuple(tt, members) => { 203 | let into_exprs = members.into_iter().enumerate().map(|(i, m)| { 204 | conversion_error.from.unnamed(i); 205 | conversion_error.into.unnamed(i); 206 | 207 | let i = Index { 208 | index: i as u32, 209 | span: tt.span(), 210 | }; 211 | 212 | recursively_create_expr( 213 | into_expr.clone(), 214 | m, 215 | "e!(#ident.#i), 216 | into_expr_fn, 217 | conversion_error, 218 | i_count, 219 | ) 220 | }); 221 | quote!((#(#into_exprs),*)) 222 | } 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_struct_data.rs: -------------------------------------------------------------------------------- 1 | use create_from_impl_for_struct::create_from_impl_for_struct; 2 | use create_into_impl_for_struct::create_into_impl_for_struct; 3 | use create_try_from_impl_for_struct::create_try_from_impl_for_struct; 4 | use create_try_into_impl_for_struct::create_try_into_impl_for_struct; 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | use syn::spanned::Spanned; 8 | use syn::DataStruct; 9 | use syn::Path; 10 | 11 | pub mod create_from_impl_for_struct; 12 | pub mod create_into_impl_for_struct; 13 | pub mod create_try_from_impl_for_struct; 14 | pub mod create_try_into_impl_for_struct; 15 | 16 | use super::attributes::ContainerAttributes; 17 | 18 | pub fn on_struct_data( 19 | input_ident_path: &Path, 20 | struct_data: &DataStruct, 21 | container_attributes: &ContainerAttributes, 22 | span: &impl Spanned, 23 | ) -> darling::Result { 24 | let ContainerAttributes { 25 | into, 26 | from, 27 | try_into, 28 | try_from, 29 | } = container_attributes; 30 | 31 | let into_default = into.iter().any(|e| e.default); 32 | let try_into_default = try_into.iter().any(|e| e.default); 33 | if into_default || try_into_default { 34 | return Err(darling::Error::custom("default on structs is not supported").with_span(span)); 35 | } 36 | 37 | let into_tokens = into 38 | .iter() 39 | .map(|attrs| { 40 | create_into_impl_for_struct( 41 | input_ident_path, 42 | struct_data, 43 | &attrs.path, 44 | attrs.skip_after, 45 | &attrs.default_for_fields.0, 46 | ) 47 | }) 48 | .collect::>>()?; 49 | let from_tokens = from 50 | .iter() 51 | .map(|attrs| create_from_impl_for_struct(&attrs.path, struct_data, input_ident_path)) 52 | .collect::>>()?; 53 | let try_into_tokens = try_into 54 | .iter() 55 | .map(|attrs| { 56 | create_try_into_impl_for_struct( 57 | input_ident_path, 58 | struct_data, 59 | &attrs.path, 60 | attrs.skip_after, 61 | &attrs.default_for_fields.0, 62 | ) 63 | }) 64 | .collect::>>()?; 65 | let try_from_tokens = try_from 66 | .iter() 67 | .map(|attrs| create_try_from_impl_for_struct(&attrs.path, struct_data, input_ident_path)) 68 | .collect::>>()?; 69 | 70 | Ok(quote!( 71 | #( 72 | #into_tokens 73 | )* 74 | #( 75 | #from_tokens 76 | )* 77 | #( 78 | #try_into_tokens 79 | )* 80 | #( 81 | #try_from_tokens 82 | )* 83 | )) 84 | } 85 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_struct_data/create_from_impl_for_struct.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::conversion_error::ConversionError; 2 | use crate::structural_convert::on_fields_named::create_from_match_branch_for_fields_named::create_from_match_branch_for_fields_named; 3 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 4 | 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | use syn::DataStruct; 8 | use syn::Fields; 9 | use syn::Path; 10 | 11 | pub fn create_from_impl_for_struct( 12 | from_path: &Path, 13 | struct_data: &DataStruct, 14 | into_path: &Path, 15 | ) -> darling::Result { 16 | let match_branches = match &struct_data.fields { 17 | Fields::Unit => { 18 | quote! { 19 | #from_path => #into_path 20 | } 21 | } 22 | Fields::Unnamed(fields_unnamed) => create_match_branch_for_fields_unnamed( 23 | from_path, 24 | |field, _| quote! {#field.into()}, 25 | into_path, 26 | fields_unnamed, 27 | None, 28 | ConversionError::empty(), 29 | )?, 30 | Fields::Named(fields_named) => { 31 | create_from_match_branch_for_fields_named(from_path, fields_named, into_path)? 32 | } 33 | }; 34 | Ok(quote!( 35 | #[automatically_derived] 36 | impl From<#from_path> for #into_path { 37 | fn from(value: #from_path) -> Self { 38 | match value { 39 | #match_branches 40 | } 41 | } 42 | } 43 | )) 44 | } 45 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_struct_data/create_into_impl_for_struct.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::conversion_error::ConversionError; 2 | use crate::structural_convert::on_fields_named::create_into_match_branch_for_fields_named::create_into_match_branch_for_fields_named; 3 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 4 | 5 | use proc_macro2::Ident; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | use syn::DataStruct; 9 | use syn::Fields; 10 | use syn::Path; 11 | 12 | pub fn create_into_impl_for_struct( 13 | from_path: &Path, 14 | struct_data: &DataStruct, 15 | into_path: &Path, 16 | skip_after: Option, 17 | default_for_fields: &[Ident], 18 | ) -> darling::Result { 19 | let match_branches = match &struct_data.fields { 20 | Fields::Unit => { 21 | quote! { 22 | #from_path => #into_path 23 | } 24 | } 25 | Fields::Unnamed(fields_unnamed) => create_match_branch_for_fields_unnamed( 26 | from_path, 27 | |field, _| quote! {#field.into()}, 28 | into_path, 29 | fields_unnamed, 30 | skip_after, 31 | ConversionError::empty(), 32 | )?, 33 | Fields::Named(fields_named) => create_into_match_branch_for_fields_named( 34 | from_path, 35 | fields_named, 36 | into_path, 37 | default_for_fields, 38 | )?, 39 | }; 40 | Ok(quote!( 41 | #[automatically_derived] 42 | impl From<#from_path> for #into_path { 43 | fn from(value: #from_path) -> Self { 44 | match value { 45 | #match_branches 46 | } 47 | } 48 | } 49 | )) 50 | } 51 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_struct_data/create_try_from_impl_for_struct.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::conversion_error::ConversionError; 2 | use crate::structural_convert::on_fields_named::create_try_from_match_branch_for_fields_named::create_try_from_match_branch_for_fields_named; 3 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 4 | 5 | use proc_macro2::TokenStream; 6 | use quote::quote; 7 | use syn::DataStruct; 8 | use syn::Fields; 9 | use syn::Path; 10 | 11 | pub fn create_try_from_impl_for_struct( 12 | from_path: &Path, 13 | struct_data: &DataStruct, 14 | into_path: &Path, 15 | ) -> darling::Result { 16 | let conversion_error = ConversionError::new(from_path, into_path); 17 | 18 | let match_branches = match &struct_data.fields { 19 | Fields::Unit => { 20 | quote! { 21 | #from_path => #into_path 22 | } 23 | } 24 | Fields::Unnamed(fields_unnamed) => create_match_branch_for_fields_unnamed( 25 | from_path, 26 | |field, err| quote! {#field.try_into().map_err(|err| #err)?}, 27 | into_path, 28 | fields_unnamed, 29 | None, 30 | conversion_error, 31 | )?, 32 | Fields::Named(fields_named) => create_try_from_match_branch_for_fields_named( 33 | from_path, 34 | fields_named, 35 | into_path, 36 | conversion_error, 37 | )?, 38 | }; 39 | Ok(quote!( 40 | #[automatically_derived] 41 | impl TryFrom<#from_path> for #into_path { 42 | type Error = String; 43 | 44 | fn try_from(value: #from_path) -> Result { 45 | Ok(match value { 46 | #match_branches 47 | }) 48 | } 49 | } 50 | )) 51 | } 52 | -------------------------------------------------------------------------------- /structural-convert-derive/src/structural_convert/on_struct_data/create_try_into_impl_for_struct.rs: -------------------------------------------------------------------------------- 1 | use crate::structural_convert::conversion_error::ConversionError; 2 | use crate::structural_convert::on_fields_named::create_try_into_match_branch_for_fields_named::create_try_into_match_branch_for_fields_named; 3 | use crate::structural_convert::on_fields_unnamed::create_match_branch_for_fields_unnamed; 4 | 5 | use proc_macro2::Ident; 6 | use proc_macro2::TokenStream; 7 | use quote::quote; 8 | use syn::DataStruct; 9 | use syn::Fields; 10 | use syn::Path; 11 | 12 | pub fn create_try_into_impl_for_struct( 13 | from_path: &Path, 14 | struct_data: &DataStruct, 15 | into_path: &Path, 16 | skip_after: Option, 17 | default_for_fields: &[Ident], 18 | ) -> darling::Result { 19 | let conversion_error = ConversionError::new(from_path, into_path); 20 | 21 | let match_branches = match &struct_data.fields { 22 | Fields::Unit => { 23 | quote! { 24 | #from_path => #into_path 25 | } 26 | } 27 | Fields::Unnamed(fields_unnamed) => create_match_branch_for_fields_unnamed( 28 | from_path, 29 | |field, err| quote! {#field.try_into().map_err(|err| #err)?}, 30 | into_path, 31 | fields_unnamed, 32 | skip_after, 33 | conversion_error, 34 | )?, 35 | Fields::Named(fields_named) => create_try_into_match_branch_for_fields_named( 36 | from_path, 37 | fields_named, 38 | into_path, 39 | default_for_fields, 40 | conversion_error, 41 | )?, 42 | }; 43 | Ok(quote!( 44 | #[automatically_derived] 45 | impl TryFrom<#from_path> for #into_path { 46 | type Error = String; 47 | 48 | fn try_from(value: #from_path) -> Result { 49 | Ok(match value { 50 | #match_branches 51 | }) 52 | } 53 | } 54 | )) 55 | } 56 | -------------------------------------------------------------------------------- /tests/from_enum.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | A, 8 | B, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(from(Rhs))] 13 | enum Lhs { 14 | A, 15 | B, 16 | } 17 | 18 | assert_eq!(Lhs::A, Rhs::A.into()); 19 | assert_eq!(Lhs::B, Rhs::B.into()); 20 | } 21 | 22 | #[test] 23 | fn variant_is_unnamed() { 24 | #[derive(Debug, PartialEq)] 25 | enum Rhs { 26 | A(i8, u32), 27 | } 28 | 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(from(Rhs))] 31 | enum Lhs { 32 | A(i32, u32), 33 | } 34 | 35 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2).into()); 36 | } 37 | 38 | #[test] 39 | fn variant_is_named() { 40 | #[derive(Debug, PartialEq)] 41 | enum Rhs { 42 | A { z: i8, x: u32 }, 43 | } 44 | 45 | #[derive(Debug, PartialEq, StructuralConvert)] 46 | #[convert(from(Rhs))] 47 | enum Lhs { 48 | A { z: i32, x: u32 }, 49 | } 50 | 51 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { z: 1, x: 2 }.into()); 52 | } 53 | -------------------------------------------------------------------------------- /tests/from_enum_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_default_to_field() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | X, 8 | } 9 | 10 | #[derive(Debug, PartialEq, StructuralConvert)] 11 | #[convert(from(Rhs))] 12 | enum Lhs { 13 | #[convert(from(default))] 14 | A, 15 | } 16 | 17 | assert_eq!(Lhs::A, Rhs::X.into()); 18 | } 19 | 20 | #[test] 21 | fn variant_is_unnamed_default_to_field() { 22 | #[derive(Debug, PartialEq)] 23 | enum Rhs { 24 | X(i8, u32, u8), 25 | } 26 | 27 | #[derive(Debug, PartialEq, StructuralConvert)] 28 | #[convert(from(Rhs))] 29 | enum Lhs { 30 | #[convert(from(default))] 31 | A(i32, u32), 32 | } 33 | 34 | assert_eq!( 35 | Lhs::A(Default::default(), Default::default()), 36 | Rhs::X(1, 2, 3).into() 37 | ); 38 | } 39 | 40 | #[test] 41 | fn variant_is_named_default_to_field() { 42 | #[derive(Debug, PartialEq)] 43 | enum Rhs { 44 | A { z: i8, x: u32 }, 45 | } 46 | 47 | #[derive(Debug, PartialEq, StructuralConvert)] 48 | #[convert(from(Rhs))] 49 | enum Lhs { 50 | A { 51 | z: i32, 52 | x: u32, 53 | #[convert(from(default))] 54 | y: u8, 55 | }, 56 | } 57 | 58 | assert_eq!( 59 | Lhs::A { 60 | z: 1, 61 | x: 2, 62 | y: Default::default() 63 | }, 64 | Rhs::A { z: 1, x: 2 }.into() 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /tests/from_enum_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn variant_is_unnamed() { 11 | #[derive(Debug, PartialEq)] 12 | enum Rhs { 13 | A(i8, Option), 14 | } 15 | 16 | #[derive(Debug, PartialEq, StructuralConvert)] 17 | #[convert(from(Rhs))] 18 | enum Lhs { 19 | A(i32, Option), 20 | } 21 | 22 | assert_eq!(Lhs::A(1, Some(W(2))), Rhs::A(1, Some(Q(2))).into()); 23 | } 24 | 25 | #[test] 26 | fn variant_is_named() { 27 | #[derive(Debug, PartialEq)] 28 | enum Rhs { 29 | A { z: i8, x: Option }, 30 | } 31 | 32 | #[derive(Debug, PartialEq, StructuralConvert)] 33 | #[convert(from(Rhs))] 34 | enum Lhs { 35 | A { z: i32, x: Option }, 36 | } 37 | 38 | assert_eq!( 39 | Lhs::A { 40 | z: 1, 41 | x: Some(W(2)) 42 | }, 43 | Rhs::A { 44 | z: 1, 45 | x: Some(Q(2)) 46 | } 47 | .into() 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /tests/from_enum_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | X, 8 | B, 9 | C, 10 | } 11 | 12 | #[derive(Debug, PartialEq, StructuralConvert)] 13 | #[convert(from(Rhs))] 14 | enum Lhs { 15 | #[convert(from(rename = "X"))] 16 | A, 17 | B, 18 | C, 19 | } 20 | 21 | assert_eq!(Lhs::A, Rhs::X.into()); 22 | assert_eq!(Lhs::B, Rhs::B.into()); 23 | assert_eq!(Lhs::C, Rhs::C.into()); 24 | } 25 | 26 | #[test] 27 | fn variant_is_unit_targeted() { 28 | #[derive(Debug, PartialEq)] 29 | enum Rhs1 { 30 | X, 31 | B, 32 | C, 33 | } 34 | 35 | #[derive(Debug, PartialEq)] 36 | enum Rhs2 { 37 | A, 38 | B, 39 | C, 40 | } 41 | 42 | #[derive(Debug, PartialEq, StructuralConvert)] 43 | #[convert(from(Rhs1), from(Rhs2))] 44 | enum Lhs { 45 | #[convert(from(Rhs1, rename = "X"), from(Rhs2))] 46 | A, 47 | B, 48 | C, 49 | } 50 | 51 | assert_eq!(Lhs::A, Rhs1::X.into()); 52 | assert_eq!(Lhs::B, Rhs1::B.into()); 53 | assert_eq!(Lhs::C, Rhs1::C.into()); 54 | 55 | assert_eq!(Lhs::A, Rhs2::A.into()); 56 | assert_eq!(Lhs::B, Rhs2::B.into()); 57 | assert_eq!(Lhs::C, Rhs2::C.into()); 58 | } 59 | 60 | #[test] 61 | fn variant_is_unnamed() { 62 | #[derive(Debug, PartialEq)] 63 | enum Rhs { 64 | X(i8, u32, u8), 65 | } 66 | 67 | #[derive(Debug, PartialEq, StructuralConvert)] 68 | #[convert(from(Rhs))] 69 | enum Lhs { 70 | #[convert(from(rename = "X"))] 71 | A(i32, u32), 72 | } 73 | 74 | assert_eq!(Lhs::A(1, 2), Rhs::X(1, 2, 3).into()); 75 | } 76 | 77 | #[test] 78 | fn variant_is_named() { 79 | #[derive(Debug, PartialEq)] 80 | enum Rhs { 81 | X { z: i8, x: u32, y: u8 }, 82 | } 83 | 84 | #[derive(Debug, PartialEq, StructuralConvert)] 85 | #[convert(from(Rhs))] 86 | enum Lhs { 87 | #[convert(from(rename = "X"))] 88 | A { z: i32, x: u32 }, 89 | } 90 | 91 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::X { z: 1, x: 2, y: 3 }.into()); 92 | } 93 | 94 | #[test] 95 | fn variant_is_named_fields_named_not_targeted() { 96 | #[derive(Debug, PartialEq)] 97 | enum Rhs { 98 | A { z: i8, x: u32 }, 99 | } 100 | 101 | #[derive(Debug, PartialEq, StructuralConvert)] 102 | #[convert(from(Rhs))] 103 | enum Lhs { 104 | A { 105 | #[convert(from(rename = "z"))] 106 | a: i32, 107 | x: u32, 108 | }, 109 | } 110 | 111 | assert_eq!(Lhs::A { a: 1, x: 2 }, Rhs::A { z: 1, x: 2 }.into()); 112 | } 113 | 114 | #[test] 115 | fn variant_is_named_fields_named_targeted() { 116 | #[derive(Debug, PartialEq)] 117 | enum Rhs1 { 118 | A { z: i8, x: u32 }, 119 | } 120 | 121 | #[derive(Debug, PartialEq)] 122 | enum Rhs2 { 123 | A { a: i8, x: u32 }, 124 | } 125 | 126 | #[derive(Debug, PartialEq, StructuralConvert)] 127 | #[convert(from(Rhs1), from(Rhs2))] 128 | enum Lhs { 129 | A { 130 | #[convert(from(Rhs1::A, rename = "z"))] 131 | a: i32, 132 | x: u32, 133 | }, 134 | } 135 | 136 | assert_eq!(Lhs::A { a: 1, x: 2 }, Rhs1::A { z: 1, x: 2 }.into()); 137 | assert_eq!(Lhs::A { a: 1, x: 2 }, Rhs2::A { a: 1, x: 2 }.into()); 138 | } 139 | -------------------------------------------------------------------------------- /tests/from_enum_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | A, 8 | B, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(from(Rhs))] 13 | enum Lhs { 14 | #[convert(from(skip))] 15 | _C, 16 | A, 17 | B, 18 | } 19 | 20 | assert_eq!(Lhs::A, Rhs::A.into()); 21 | assert_eq!(Lhs::B, Rhs::B.into()); 22 | } 23 | 24 | #[test] 25 | fn variant_is_unit_targeted() { 26 | #[derive(Debug, PartialEq)] 27 | enum Rhs1 { 28 | A, 29 | B, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | enum Rhs2 { 34 | C, 35 | A, 36 | B, 37 | } 38 | 39 | #[derive(Debug, PartialEq, StructuralConvert)] 40 | #[convert(from(Rhs1), from(Rhs2))] 41 | enum Lhs { 42 | #[convert(from(Rhs1, skip), from(Rhs2))] 43 | C, 44 | A, 45 | B, 46 | } 47 | 48 | assert_eq!(Lhs::A, Rhs1::A.into()); 49 | assert_eq!(Lhs::B, Rhs1::B.into()); 50 | assert_eq!(Lhs::A, Rhs2::A.into()); 51 | assert_eq!(Lhs::B, Rhs2::B.into()); 52 | assert_eq!(Lhs::C, Rhs2::C.into()); 53 | } 54 | 55 | #[test] 56 | fn variant_is_unnamed() { 57 | #[derive(Debug, PartialEq)] 58 | enum Rhs { 59 | A(i8, u32, u8), 60 | } 61 | 62 | #[derive(Debug, PartialEq, StructuralConvert)] 63 | #[convert(from(Rhs))] 64 | enum Lhs { 65 | A(i32, u32), 66 | } 67 | 68 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2, 3).into()); 69 | } 70 | 71 | #[test] 72 | fn variant_is_named() { 73 | #[derive(Debug, PartialEq)] 74 | enum Rhs { 75 | A { z: i8, x: u32, y: u8 }, 76 | } 77 | 78 | #[derive(Debug, PartialEq, StructuralConvert)] 79 | #[convert(from(Rhs))] 80 | enum Lhs { 81 | A { z: i32, x: u32 }, 82 | } 83 | 84 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { z: 1, x: 2, y: 3 }.into()); 85 | } 86 | -------------------------------------------------------------------------------- /tests/from_struct.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn unit() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs; 7 | 8 | #[derive(Debug, PartialEq, StructuralConvert)] 9 | #[convert(from(Rhs))] 10 | struct Lhs; 11 | 12 | assert_eq!(Lhs, Rhs.into()); 13 | assert_eq!(Lhs, Rhs.into()); 14 | } 15 | 16 | #[test] 17 | fn fields_unnamed() { 18 | #[derive(Debug, PartialEq)] 19 | struct Rhs(i8, u32); 20 | 21 | #[derive(Debug, PartialEq, StructuralConvert)] 22 | #[convert(from(Rhs))] 23 | struct Lhs(i32, u32); 24 | 25 | assert_eq!(Lhs(1, 2), Rhs(1, 2).into()); 26 | } 27 | 28 | #[test] 29 | fn fields_named() { 30 | #[derive(Debug, PartialEq)] 31 | struct Rhs { 32 | z: i8, 33 | x: u32, 34 | } 35 | 36 | #[derive(Debug, PartialEq, StructuralConvert)] 37 | #[convert(from(Rhs))] 38 | struct Lhs { 39 | z: i32, 40 | x: u32, 41 | } 42 | 43 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.into()); 44 | } 45 | -------------------------------------------------------------------------------- /tests/from_struct_as.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named() { 5 | #[derive(Debug, PartialEq)] 6 | pub struct Rhs { 7 | pub r#type: i32, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 11 | pub enum RhsEnum { 12 | Mobile = 0, 13 | Home = 1, 14 | } 15 | 16 | impl From for RhsEnum { 17 | fn from(value: i32) -> Self { 18 | match value { 19 | 0 => RhsEnum::Mobile, 20 | 1 => RhsEnum::Home, 21 | _ => unimplemented!(), 22 | } 23 | } 24 | } 25 | 26 | #[derive(Debug, PartialEq, StructuralConvert)] 27 | #[convert(from(Rhs))] 28 | pub struct Lhs { 29 | #[convert(from(as = "RhsEnum"))] 30 | pub r#type: LhsEnum, 31 | } 32 | 33 | #[derive(Debug, PartialEq, Eq, StructuralConvert, Default)] 34 | #[convert(from(RhsEnum))] 35 | pub enum LhsEnum { 36 | #[default] 37 | Mobile, 38 | Home, 39 | } 40 | 41 | assert_eq!( 42 | Lhs { 43 | r#type: LhsEnum::Home 44 | }, 45 | Rhs { r#type: 1 }.into() 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/from_struct_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_named_default_to_field() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs { 7 | z: i8, 8 | x: u32, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(from(Rhs))] 13 | struct Lhs { 14 | z: i32, 15 | x: u32, 16 | #[convert(from(default))] 17 | y: u8, 18 | } 19 | 20 | assert_eq!( 21 | Lhs { 22 | z: 1, 23 | x: 2, 24 | y: Default::default() 25 | }, 26 | Rhs { z: 1, x: 2 }.into() 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /tests/from_struct_generic_types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use structural_convert::StructuralConvert; 4 | 5 | #[test] 6 | fn unnamed_fields_option() { 7 | #[derive(Debug, PartialEq)] 8 | struct Rhs(Option); 9 | 10 | #[derive(Debug, PartialEq, StructuralConvert)] 11 | #[convert(from(Rhs))] 12 | struct Lhs(Option); 13 | 14 | assert_eq!(Lhs(Some(1)), Rhs(Some(1)).into()); 15 | } 16 | 17 | #[test] 18 | fn unnamed_fields_vec() { 19 | #[derive(Debug, PartialEq)] 20 | struct Rhs(Vec); 21 | 22 | #[derive(Debug, PartialEq, StructuralConvert)] 23 | #[convert(from(Rhs))] 24 | struct Lhs(Vec); 25 | 26 | assert_eq!(Lhs(vec![1]), Rhs(vec![1]).into()); 27 | } 28 | 29 | #[test] 30 | fn unnamed_fields_tuple() { 31 | #[derive(Debug, PartialEq)] 32 | struct Rhs((i8, i8)); 33 | 34 | #[derive(Debug, PartialEq, StructuralConvert)] 35 | #[convert(from(Rhs))] 36 | struct Lhs((i32, i8)); 37 | 38 | assert_eq!(Lhs((1, 2)), Rhs((1, 2)).into()); 39 | } 40 | 41 | #[test] 42 | fn unnamed_fields_vec_tuples() { 43 | #[derive(Debug, PartialEq)] 44 | struct Rhs(Vec<(i8, i8)>); 45 | 46 | #[derive(Debug, PartialEq, StructuralConvert)] 47 | #[convert(from(Rhs))] 48 | struct Lhs(Vec<(i32, i8)>); 49 | 50 | assert_eq!(Lhs(vec![(1, 2)]), Rhs(vec![(1, 2)]).into()); 51 | } 52 | 53 | #[test] 54 | fn unnamed_fields_option_vec() { 55 | #[derive(Debug, PartialEq)] 56 | struct Rhs(Option>); 57 | 58 | #[derive(Debug, PartialEq, StructuralConvert)] 59 | #[convert(from(Rhs))] 60 | struct Lhs(Option>); 61 | 62 | assert_eq!(Lhs(Some(vec![1])), Rhs(Some(vec![1])).into()); 63 | } 64 | 65 | #[test] 66 | fn unnamed_fields_vec_option() { 67 | #[derive(Debug, PartialEq)] 68 | struct Rhs(Vec>); 69 | 70 | #[derive(Debug, PartialEq, StructuralConvert)] 71 | #[convert(from(Rhs))] 72 | struct Lhs(Vec>); 73 | 74 | assert_eq!(Lhs(vec![Some(1)]), Rhs(vec![Some(1)]).into()); 75 | } 76 | 77 | #[test] 78 | fn unnamed_fields_vec_option_tuple() { 79 | #[derive(Debug, PartialEq)] 80 | struct Rhs(Vec>); 81 | 82 | #[derive(Debug, PartialEq, StructuralConvert)] 83 | #[convert(from(Rhs))] 84 | struct Lhs(Vec>); 85 | 86 | assert_eq!(Lhs(vec![Some((1, 2))]), Rhs(vec![Some((1, 2))]).into()); 87 | } 88 | 89 | #[test] 90 | fn unnamed_fields_vec_tuple_option() { 91 | #[derive(Debug, PartialEq)] 92 | struct Rhs(Vec<(Option, Option)>); 93 | 94 | #[derive(Debug, PartialEq, StructuralConvert)] 95 | #[convert(from(Rhs))] 96 | struct Lhs(Vec<(Option, Option)>); 97 | 98 | assert_eq!( 99 | Lhs(vec![(Some(1), Some(1))]), 100 | Rhs(vec![(Some(1), Some(1))]).into() 101 | ); 102 | } 103 | 104 | #[test] 105 | fn unnamed_fields_tuple_vec_option() { 106 | #[derive(Debug, PartialEq)] 107 | struct Rhs((Vec>, Vec>)); 108 | 109 | #[derive(Debug, PartialEq, StructuralConvert)] 110 | #[convert(from(Rhs))] 111 | struct Lhs((Vec>, Vec>)); 112 | 113 | assert_eq!( 114 | Lhs((vec![Some(1)], vec![Some(1)])), 115 | Rhs((vec![Some(1)], vec![Some(1)])).into() 116 | ); 117 | } 118 | 119 | #[test] 120 | fn unnamed_fields_result() { 121 | #[derive(Debug, PartialEq)] 122 | struct Rhs(Result); 123 | 124 | #[derive(Debug, PartialEq, StructuralConvert)] 125 | #[convert(from(Rhs))] 126 | struct Lhs(Result); 127 | 128 | assert_eq!(Lhs(Ok(1)), Rhs(Ok(1)).into()); 129 | assert_eq!(Lhs(Err(1)), Rhs(Err(1)).into()); 130 | } 131 | 132 | #[test] 133 | fn unnamed_fields_hash_map() { 134 | #[derive(Debug, PartialEq)] 135 | struct Rhs(HashMap); 136 | 137 | #[derive(Debug, PartialEq, StructuralConvert)] 138 | #[convert(from(Rhs))] 139 | struct Lhs(HashMap); 140 | 141 | let mut lhs = HashMap::new(); 142 | lhs.insert(1, 2); 143 | 144 | let mut rhs = HashMap::new(); 145 | rhs.insert(1, 2); 146 | 147 | assert_eq!(Lhs(lhs), Rhs(rhs).into()); 148 | } 149 | 150 | #[test] 151 | fn named_fields_option() { 152 | #[derive(Debug, PartialEq)] 153 | struct Rhs { 154 | item: Option, 155 | } 156 | 157 | #[derive(Debug, PartialEq, StructuralConvert)] 158 | #[convert(from(Rhs))] 159 | struct Lhs { 160 | item: Option, 161 | } 162 | 163 | assert_eq!(Lhs { item: Some(1) }, Rhs { item: Some(1) }.into()); 164 | } 165 | 166 | #[test] 167 | fn named_fields_vec() { 168 | #[derive(Debug, PartialEq)] 169 | struct Rhs { 170 | item: Vec, 171 | } 172 | 173 | #[derive(Debug, PartialEq, StructuralConvert)] 174 | #[convert(from(Rhs))] 175 | struct Lhs { 176 | item: Vec, 177 | } 178 | 179 | assert_eq!(Lhs { item: vec![1] }, Rhs { item: vec![1] }.into()); 180 | } 181 | 182 | #[test] 183 | fn named_fields_tuple() { 184 | #[derive(Debug, PartialEq)] 185 | struct Rhs { 186 | item: (i8, i8), 187 | } 188 | 189 | #[derive(Debug, PartialEq, StructuralConvert)] 190 | #[convert(from(Rhs))] 191 | struct Lhs { 192 | item: (i32, i8), 193 | } 194 | 195 | assert_eq!(Lhs { item: (1, 2) }, Rhs { item: (1, 2) }.into()); 196 | } 197 | 198 | #[test] 199 | fn named_fields_vec_tuples() { 200 | #[derive(Debug, PartialEq)] 201 | struct Rhs { 202 | item: Vec<(i8, i8)>, 203 | } 204 | 205 | #[derive(Debug, PartialEq, StructuralConvert)] 206 | #[convert(from(Rhs))] 207 | struct Lhs { 208 | item: Vec<(i32, i8)>, 209 | } 210 | 211 | assert_eq!( 212 | Lhs { item: vec![(1, 2)] }, 213 | Rhs { item: vec![(1, 2)] }.into() 214 | ); 215 | } 216 | 217 | #[test] 218 | fn named_fields_option_vec() { 219 | #[derive(Debug, PartialEq)] 220 | struct Rhs { 221 | item: Option>, 222 | } 223 | 224 | #[derive(Debug, PartialEq, StructuralConvert)] 225 | #[convert(from(Rhs))] 226 | struct Lhs { 227 | item: Option>, 228 | } 229 | 230 | assert_eq!( 231 | Lhs { 232 | item: Some(vec![1]) 233 | }, 234 | Rhs { 235 | item: Some(vec![1]) 236 | } 237 | .try_into() 238 | .unwrap() 239 | ); 240 | } 241 | 242 | #[test] 243 | fn named_fields_vec_option() { 244 | #[derive(Debug, PartialEq)] 245 | struct Rhs { 246 | item: Vec>, 247 | } 248 | 249 | #[derive(Debug, PartialEq, StructuralConvert)] 250 | #[convert(from(Rhs))] 251 | struct Lhs { 252 | item: Vec>, 253 | } 254 | 255 | assert_eq!( 256 | Lhs { 257 | item: vec![Some(1)] 258 | }, 259 | Rhs { 260 | item: vec![Some(1)] 261 | } 262 | .try_into() 263 | .unwrap() 264 | ); 265 | } 266 | 267 | #[test] 268 | fn named_fields_vec_option_tuple() { 269 | #[derive(Debug, PartialEq)] 270 | struct Rhs { 271 | item: Vec>, 272 | } 273 | 274 | #[derive(Debug, PartialEq, StructuralConvert)] 275 | #[convert(from(Rhs))] 276 | struct Lhs { 277 | item: Vec>, 278 | } 279 | 280 | assert_eq!( 281 | Lhs { 282 | item: vec![Some((1, 2))] 283 | }, 284 | Rhs { 285 | item: vec![Some((1, 2))] 286 | } 287 | .try_into() 288 | .unwrap() 289 | ) 290 | } 291 | 292 | #[test] 293 | fn named_fields_vec_tuple_option() { 294 | #[derive(Debug, PartialEq)] 295 | struct Rhs { 296 | item: Vec<(Option, Option)>, 297 | } 298 | 299 | #[derive(Debug, PartialEq, StructuralConvert)] 300 | #[convert(from(Rhs))] 301 | struct Lhs { 302 | item: Vec<(Option, Option)>, 303 | } 304 | 305 | assert_eq!( 306 | Lhs { 307 | item: vec![(Some(1), Some(1))] 308 | }, 309 | Rhs { 310 | item: vec![(Some(1), Some(1))] 311 | } 312 | .try_into() 313 | .unwrap() 314 | ) 315 | } 316 | 317 | #[test] 318 | fn named_fields_tuple_vec_option() { 319 | #[derive(Debug, PartialEq)] 320 | struct Rhs { 321 | item: (Vec>, Vec>), 322 | } 323 | 324 | #[derive(Debug, PartialEq, StructuralConvert)] 325 | #[convert(from(Rhs))] 326 | struct Lhs { 327 | item: (Vec>, Vec>), 328 | } 329 | 330 | assert_eq!( 331 | Lhs { 332 | item: (vec![Some(1)], vec![Some(1)]) 333 | }, 334 | Rhs { 335 | item: (vec![Some(1)], vec![Some(1)]) 336 | } 337 | .try_into() 338 | .unwrap() 339 | ) 340 | } 341 | 342 | #[test] 343 | fn named_fields_result() { 344 | #[derive(Debug, PartialEq)] 345 | struct Rhs { 346 | item: Result, 347 | } 348 | 349 | #[derive(Debug, PartialEq, StructuralConvert)] 350 | #[convert(from(Rhs))] 351 | struct Lhs { 352 | item: Result, 353 | } 354 | 355 | assert_eq!(Lhs { item: Ok(1) }, Rhs { item: Ok(1) }.into()); 356 | assert_eq!(Lhs { item: Err(1) }, Rhs { item: Err(1) }.into()); 357 | } 358 | 359 | #[test] 360 | fn named_fields_hash_map() { 361 | #[derive(Debug, PartialEq)] 362 | struct Rhs { 363 | item: HashMap, 364 | } 365 | 366 | #[derive(Debug, PartialEq, StructuralConvert)] 367 | #[convert(from(Rhs))] 368 | struct Lhs { 369 | item: HashMap, 370 | } 371 | 372 | let mut lhs = HashMap::new(); 373 | lhs.insert(1, 2); 374 | 375 | let mut rhs = HashMap::new(); 376 | rhs.insert(1, 2); 377 | 378 | assert_eq!(Lhs { item: lhs }, Rhs { item: rhs }.into()); 379 | } 380 | -------------------------------------------------------------------------------- /tests/from_struct_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn fields_unnamed() { 11 | #[derive(Debug, PartialEq)] 12 | struct Rhs(i8, Option); 13 | 14 | #[derive(Debug, PartialEq, StructuralConvert)] 15 | #[convert(from(Rhs))] 16 | struct Lhs(i32, Option); 17 | 18 | assert_eq!(Lhs(1, Some(W(2))), Rhs(1, Some(Q(2))).into()); 19 | } 20 | 21 | #[test] 22 | fn fields_named() { 23 | #[derive(Debug, PartialEq)] 24 | struct Rhs { 25 | z: i8, 26 | x: Option, 27 | } 28 | 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(from(Rhs))] 31 | struct Lhs { 32 | z: i32, 33 | x: Option, 34 | } 35 | 36 | assert_eq!( 37 | Lhs { 38 | z: 1, 39 | x: Some(W(2)) 40 | }, 41 | Rhs { 42 | z: 1, 43 | x: Some(Q(2)) 44 | } 45 | .into() 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/from_struct_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named_not_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs { 7 | z: i8, 8 | x: u32, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(from(Rhs))] 13 | struct Lhs { 14 | #[convert(from(rename = "z"))] 15 | a: i32, 16 | x: u32, 17 | } 18 | 19 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs { z: 1, x: 2 }.into()); 20 | } 21 | 22 | #[test] 23 | fn fields_named_targeted() { 24 | #[derive(Debug, PartialEq)] 25 | struct Rhs1 { 26 | z: i8, 27 | x: u32, 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | struct Rhs2 { 32 | a: i8, 33 | x: u32, 34 | } 35 | 36 | #[derive(Debug, PartialEq, StructuralConvert)] 37 | #[convert(from(Rhs1), from(Rhs2))] 38 | struct Lhs { 39 | #[convert(from(Rhs1, rename = "z"))] 40 | a: i32, 41 | x: u32, 42 | } 43 | 44 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs1 { z: 1, x: 2 }.into()); 45 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs2 { a: 1, x: 2 }.into()); 46 | } 47 | -------------------------------------------------------------------------------- /tests/from_struct_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_unnamed() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs(i8, u32, u8); 7 | 8 | #[derive(Debug, PartialEq, StructuralConvert)] 9 | #[convert(from(Rhs))] 10 | struct Lhs(i32, u32); 11 | 12 | assert_eq!(Lhs(1, 2), Rhs(1, 2, 3).into()); 13 | } 14 | 15 | #[test] 16 | fn fields_named() { 17 | #[derive(Debug, PartialEq)] 18 | struct Rhs { 19 | z: i8, 20 | x: u32, 21 | y: u8, 22 | } 23 | 24 | #[derive(Debug, PartialEq, StructuralConvert)] 25 | #[convert(from(Rhs))] 26 | struct Lhs { 27 | z: i32, 28 | x: u32, 29 | } 30 | 31 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2, y: 3 }.into()); 32 | } 33 | -------------------------------------------------------------------------------- /tests/into_enum.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs))] 7 | enum Rhs { 8 | A, 9 | B, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | enum Lhs { 14 | A, 15 | B, 16 | } 17 | 18 | assert_eq!(Lhs::A, Rhs::A.into()); 19 | assert_eq!(Lhs::B, Rhs::B.into()); 20 | } 21 | 22 | #[test] 23 | fn variant_is_unnamed() { 24 | #[derive(Debug, PartialEq, StructuralConvert)] 25 | #[convert(into(Lhs))] 26 | enum Rhs { 27 | A(i8, u32), 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | enum Lhs { 32 | A(i32, u32), 33 | } 34 | 35 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2).into()); 36 | } 37 | 38 | #[test] 39 | fn variant_is_named() { 40 | #[derive(Debug, PartialEq, StructuralConvert)] 41 | #[convert(into(Lhs))] 42 | enum Rhs { 43 | A { z: i8, x: u32 }, 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | enum Lhs { 48 | A { z: i32, x: u32 }, 49 | } 50 | 51 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { z: 1, x: 2 }.into()); 52 | } 53 | -------------------------------------------------------------------------------- /tests/into_enum_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_default() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs, default))] 7 | enum Rhs { 8 | #[convert(into(skip))] 9 | A, 10 | } 11 | 12 | #[derive(Debug, PartialEq, Default)] 13 | enum Lhs { 14 | #[default] 15 | X, 16 | } 17 | 18 | assert_eq!(Lhs::default(), Rhs::A.into()); 19 | } 20 | 21 | #[test] 22 | fn variant_is_unnamed_default() { 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(into(Lhs, default))] 25 | enum Rhs { 26 | #[convert(into(skip))] 27 | A(i8, u32), 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | enum Lhs { 32 | X(i32, u32), 33 | } 34 | 35 | impl Default for Lhs { 36 | fn default() -> Self { 37 | Lhs::X(Default::default(), Default::default()) 38 | } 39 | } 40 | 41 | assert_eq!(Lhs::default(), Rhs::A(1, 2).into()); 42 | } 43 | 44 | #[test] 45 | fn variant_is_named_default() { 46 | #[derive(Debug, PartialEq, StructuralConvert)] 47 | #[convert(into(Lhs, default))] 48 | enum Rhs { 49 | #[convert(into(skip))] 50 | A { z: i8, x: u32 }, 51 | } 52 | 53 | #[derive(Debug, PartialEq)] 54 | enum Lhs { 55 | X {}, 56 | } 57 | 58 | impl Default for Lhs { 59 | fn default() -> Self { 60 | Lhs::X {} 61 | } 62 | } 63 | 64 | assert_eq!(Lhs::default(), Rhs::A { z: 1, x: 2 }.into()); 65 | } 66 | 67 | #[test] 68 | fn variant_is_named_default_to_field() { 69 | #[derive(Debug, PartialEq, StructuralConvert)] 70 | #[convert(into(Lhs, default_for_fields("y", "e")))] 71 | enum Rhs { 72 | A { z: i8, x: u32 }, 73 | } 74 | 75 | #[derive(Debug, PartialEq)] 76 | enum Lhs { 77 | A { z: i32, x: u32, y: u32, e: u32 }, 78 | } 79 | 80 | assert_eq!( 81 | Lhs::A { 82 | z: 1, 83 | x: 2, 84 | y: Default::default(), 85 | e: Default::default() 86 | }, 87 | Rhs::A { z: 1, x: 2 }.into() 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /tests/into_enum_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn variant_is_unnamed() { 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(into(Lhs))] 13 | enum Rhs { 14 | A(i8, Option), 15 | } 16 | 17 | #[derive(Debug, PartialEq)] 18 | enum Lhs { 19 | A(i32, Option), 20 | } 21 | 22 | assert_eq!(Lhs::A(1, Some(W(2))), Rhs::A(1, Some(Q(2))).into()); 23 | } 24 | 25 | #[test] 26 | fn variant_is_named() { 27 | #[derive(Debug, PartialEq, StructuralConvert)] 28 | #[convert(into(Lhs))] 29 | enum Rhs { 30 | A { z: i8, x: Option }, 31 | } 32 | 33 | #[derive(Debug, PartialEq)] 34 | enum Lhs { 35 | A { z: i32, x: Option }, 36 | } 37 | 38 | assert_eq!( 39 | Lhs::A { 40 | z: 1, 41 | x: Some(W(2)) 42 | }, 43 | Rhs::A { 44 | z: 1, 45 | x: Some(Q(2)) 46 | } 47 | .into() 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /tests/into_enum_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs))] 7 | enum Rhs { 8 | #[convert(into(rename = "X"))] 9 | A, 10 | B, 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | enum Lhs { 15 | X, 16 | B, 17 | } 18 | 19 | assert_eq!(Lhs::X, Rhs::A.into()); 20 | assert_eq!(Lhs::B, Rhs::B.into()); 21 | } 22 | 23 | #[test] 24 | fn variant_is_unit_targeted() { 25 | #[derive(Debug, PartialEq, StructuralConvert)] 26 | #[convert(into(Lhs1), into(Lhs2))] 27 | enum Rhs { 28 | #[convert(into(Lhs1, rename = "X"), into(Lhs2))] 29 | A, 30 | B, 31 | } 32 | 33 | #[derive(Debug, PartialEq)] 34 | enum Lhs1 { 35 | X, 36 | B, 37 | } 38 | 39 | #[derive(Debug, PartialEq)] 40 | enum Lhs2 { 41 | A, 42 | B, 43 | } 44 | 45 | assert_eq!(Lhs1::X, Rhs::A.into()); 46 | assert_eq!(Lhs1::B, Rhs::B.into()); 47 | 48 | assert_eq!(Lhs2::A, Rhs::A.into()); 49 | assert_eq!(Lhs2::B, Rhs::B.into()); 50 | } 51 | 52 | #[test] 53 | fn variant_is_unnamed() { 54 | #[derive(Debug, PartialEq, StructuralConvert)] 55 | #[convert(into(Lhs))] 56 | enum Rhs { 57 | #[convert(into(rename = "X"))] 58 | A(i8, u32), 59 | } 60 | 61 | #[derive(Debug, PartialEq)] 62 | enum Lhs { 63 | X(i32, u32), 64 | } 65 | 66 | assert_eq!(Lhs::X(1, 2), Rhs::A(1, 2).into()); 67 | } 68 | 69 | #[test] 70 | fn variant_is_named() { 71 | #[derive(Debug, PartialEq, StructuralConvert)] 72 | #[convert(into(Lhs))] 73 | enum Rhs { 74 | #[convert(into(rename = "X"))] 75 | A { z: i8, x: u32 }, 76 | } 77 | 78 | #[derive(Debug, PartialEq)] 79 | enum Lhs { 80 | X { z: i32, x: u32 }, 81 | } 82 | 83 | assert_eq!(Lhs::X { z: 1, x: 2 }, Rhs::A { z: 1, x: 2 }.into()); 84 | } 85 | 86 | #[test] 87 | fn fields_named_not_targeted() { 88 | #[derive(Debug, PartialEq, StructuralConvert)] 89 | #[convert(into(Lhs))] 90 | enum Rhs { 91 | A { 92 | #[convert(into(rename = "z"))] 93 | a: i8, 94 | x: u32, 95 | }, 96 | } 97 | 98 | #[derive(Debug, PartialEq)] 99 | enum Lhs { 100 | A { z: i8, x: u32 }, 101 | } 102 | 103 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { a: 1, x: 2 }.into()); 104 | } 105 | 106 | #[test] 107 | fn fields_named_targeted() { 108 | #[derive(Debug, PartialEq, StructuralConvert)] 109 | #[convert(into(Lhs1), into(Lhs2))] 110 | enum Rhs { 111 | A { 112 | #[convert(into(Lhs1::A, rename = "z"))] 113 | a: i8, 114 | x: u32, 115 | }, 116 | } 117 | 118 | #[derive(Debug, PartialEq)] 119 | enum Lhs1 { 120 | A { z: i8, x: u32 }, 121 | } 122 | 123 | #[derive(Debug, PartialEq)] 124 | enum Lhs2 { 125 | A { a: i8, x: u32 }, 126 | } 127 | 128 | assert_eq!(Lhs1::A { z: 1, x: 2 }, Rhs::A { a: 1, x: 2 }.into()); 129 | assert_eq!(Lhs2::A { a: 1, x: 2 }, Rhs::A { a: 1, x: 2 }.into()); 130 | } 131 | -------------------------------------------------------------------------------- /tests/into_enum_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs))] 7 | enum Rhs { 8 | A, 9 | B, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | enum Lhs { 14 | A, 15 | B, 16 | _C, 17 | } 18 | 19 | assert_eq!(Lhs::A, Rhs::A.into()); 20 | assert_eq!(Lhs::B, Rhs::B.into()); 21 | } 22 | 23 | #[test] 24 | fn variant_is_unit_targeted() { 25 | #[derive(Debug, PartialEq, StructuralConvert)] 26 | #[convert(into(Lhs))] 27 | enum Rhs { 28 | A, 29 | B, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | enum Lhs { 34 | A, 35 | B, 36 | _C, 37 | } 38 | 39 | assert_eq!(Lhs::A, Rhs::A.into()); 40 | assert_eq!(Lhs::B, Rhs::B.into()); 41 | } 42 | 43 | #[test] 44 | fn variant_is_unnamed() { 45 | #[derive(Debug, PartialEq, StructuralConvert)] 46 | #[convert(into(Lhs))] 47 | enum Rhs { 48 | #[convert(into(Lhs, skip_after = 2))] 49 | A(i8, u32, u8), 50 | } 51 | 52 | #[derive(Debug, PartialEq)] 53 | enum Lhs { 54 | A(i32, u32), 55 | } 56 | 57 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2, 3).into()); 58 | } 59 | 60 | #[test] 61 | fn variant_is_named() { 62 | #[derive(Debug, PartialEq, StructuralConvert)] 63 | #[convert(into(Lhs))] 64 | enum Rhs { 65 | A { 66 | z: i8, 67 | x: u32, 68 | #[convert(into(Lhs::A, skip))] 69 | y: u8, 70 | }, 71 | } 72 | 73 | #[derive(Debug, PartialEq)] 74 | enum Lhs { 75 | A { z: i32, x: u32 }, 76 | } 77 | 78 | assert_eq!(Lhs::A { z: 1, x: 2 }, Rhs::A { z: 1, x: 2, y: 3 }.into()); 79 | } 80 | -------------------------------------------------------------------------------- /tests/into_struct.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn unit() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs))] 7 | struct Rhs; 8 | 9 | #[derive(Debug, PartialEq)] 10 | struct Lhs; 11 | 12 | assert_eq!(Lhs, Rhs.into()); 13 | } 14 | 15 | #[test] 16 | fn fields_unnamed() { 17 | #[derive(Debug, PartialEq, StructuralConvert)] 18 | #[convert(into(Lhs))] 19 | struct Rhs(i8, u32); 20 | 21 | #[derive(Debug, PartialEq)] 22 | struct Lhs(i32, u32); 23 | 24 | assert_eq!(Lhs(1, 2), Rhs(1, 2).into()); 25 | } 26 | 27 | #[test] 28 | fn fields_named() { 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(into(Lhs))] 31 | struct Rhs { 32 | z: i8, 33 | x: u32, 34 | } 35 | 36 | #[derive(Debug, PartialEq)] 37 | struct Lhs { 38 | z: i32, 39 | x: u32, 40 | } 41 | 42 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.into()); 43 | } 44 | -------------------------------------------------------------------------------- /tests/into_struct_as.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named_i32() { 5 | #[derive(Debug, PartialEq)] 6 | pub struct Rhs { 7 | pub r#type: i32, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 11 | pub enum RhsEnum { 12 | Mobile = 0, 13 | Home = 1, 14 | Work = 2, 15 | } 16 | 17 | impl From for i32 { 18 | fn from(value: RhsEnum) -> Self { 19 | value as i32 20 | } 21 | } 22 | 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(into(Rhs))] 25 | pub struct Lhs { 26 | #[convert(into(as = "RhsEnum"))] 27 | pub r#type: LhsEnum, 28 | } 29 | 30 | #[derive(Debug, PartialEq, Eq, StructuralConvert)] 31 | #[convert(into(RhsEnum))] 32 | pub enum LhsEnum { 33 | Mobile, 34 | Home, 35 | Work, 36 | } 37 | 38 | assert_eq!( 39 | Rhs { r#type: 1 }, 40 | Lhs { 41 | r#type: LhsEnum::Home 42 | } 43 | .into(), 44 | ); 45 | } 46 | 47 | #[test] 48 | fn fields_named_option() { 49 | #[derive(Debug, PartialEq)] 50 | struct Q(u32); 51 | #[derive(Debug, PartialEq, StructuralConvert)] 52 | #[convert(from(Q))] 53 | struct W(u32); 54 | 55 | #[derive(Debug, PartialEq, StructuralConvert)] 56 | #[convert(into(Lhs))] 57 | struct Rhs { 58 | z: i8, 59 | #[convert(into(Lhs, as = "Option::"))] 60 | x: Q, 61 | } 62 | 63 | #[derive(Debug, PartialEq)] 64 | struct Lhs { 65 | z: i32, 66 | x: Option, 67 | } 68 | 69 | assert_eq!( 70 | Lhs { 71 | z: 1, 72 | x: Some(W(2)) 73 | }, 74 | Rhs { z: 1, x: Q(2) }.into() 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /tests/into_struct_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_named_default_to_field() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs, default_for_fields("y", "e")))] 7 | struct Rhs { 8 | z: i8, 9 | x: u32, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | struct Lhs { 14 | z: i32, 15 | x: u32, 16 | y: u32, 17 | e: u32, 18 | } 19 | 20 | assert_eq!( 21 | Lhs { 22 | z: 1, 23 | x: 2, 24 | y: Default::default(), 25 | e: Default::default() 26 | }, 27 | Rhs { z: 1, x: 2 }.into() 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /tests/into_struct_generic_types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use structural_convert::StructuralConvert; 4 | 5 | #[test] 6 | fn unnamed_fields_option() { 7 | #[derive(Debug, PartialEq, StructuralConvert)] 8 | #[convert(into(Lhs))] 9 | struct Rhs(Option); 10 | 11 | #[derive(Debug, PartialEq)] 12 | struct Lhs(Option); 13 | 14 | assert_eq!(Lhs(Some(1)), Rhs(Some(1)).into()); 15 | } 16 | 17 | #[test] 18 | fn unnamed_fields_vec() { 19 | #[derive(Debug, PartialEq, StructuralConvert)] 20 | #[convert(into(Lhs))] 21 | struct Rhs(Vec); 22 | 23 | #[derive(Debug, PartialEq)] 24 | struct Lhs(Vec); 25 | 26 | assert_eq!(Lhs(vec![1]), Rhs(vec![1]).into()); 27 | } 28 | 29 | #[test] 30 | fn unnamed_fields_tuple() { 31 | #[derive(Debug, PartialEq, StructuralConvert)] 32 | #[convert(into(Lhs))] 33 | struct Rhs((i8, i8)); 34 | 35 | #[derive(Debug, PartialEq)] 36 | struct Lhs((i32, i8)); 37 | 38 | assert_eq!(Lhs((1, 2)), Rhs((1, 2)).into()); 39 | } 40 | 41 | #[test] 42 | fn unnamed_fields_vec_tuples() { 43 | #[derive(Debug, PartialEq, StructuralConvert)] 44 | #[convert(into(Lhs))] 45 | struct Rhs(Vec<(i8, i8)>); 46 | 47 | #[derive(Debug, PartialEq)] 48 | struct Lhs(Vec<(i32, i8)>); 49 | 50 | assert_eq!(Lhs(vec![(1, 2)]), Rhs(vec![(1, 2)]).into()); 51 | } 52 | 53 | #[test] 54 | fn unnamed_fields_option_vec() { 55 | #[derive(Debug, PartialEq, StructuralConvert)] 56 | #[convert(into(Lhs))] 57 | struct Rhs(Option>); 58 | 59 | #[derive(Debug, PartialEq)] 60 | struct Lhs(Option>); 61 | 62 | assert_eq!(Lhs(Some(vec![1])), Rhs(Some(vec![1])).into()); 63 | } 64 | 65 | #[test] 66 | fn unnamed_fields_vec_option() { 67 | #[derive(Debug, PartialEq, StructuralConvert)] 68 | #[convert(into(Lhs))] 69 | struct Rhs(Vec>); 70 | 71 | #[derive(Debug, PartialEq)] 72 | struct Lhs(Vec>); 73 | 74 | assert_eq!(Lhs(vec![Some(1)]), Rhs(vec![Some(1)]).into()); 75 | } 76 | 77 | #[test] 78 | fn unnamed_fields_vec_option_tuple() { 79 | #[derive(Debug, PartialEq, StructuralConvert)] 80 | #[convert(into(Lhs))] 81 | struct Rhs(Vec>); 82 | 83 | #[derive(Debug, PartialEq)] 84 | struct Lhs(Vec>); 85 | 86 | assert_eq!(Lhs(vec![Some((1, 2))]), Rhs(vec![Some((1, 2))]).into()); 87 | } 88 | #[test] 89 | fn unnamed_fields_vec_tuple_option() { 90 | #[derive(Debug, PartialEq, StructuralConvert)] 91 | #[convert(into(Lhs))] 92 | struct Rhs(Vec<(Option, Option)>); 93 | 94 | #[derive(Debug, PartialEq)] 95 | struct Lhs(Vec<(Option, Option)>); 96 | 97 | assert_eq!( 98 | Lhs(vec![(Some(1), Some(1))]), 99 | Rhs(vec![(Some(1), Some(1))]).into() 100 | ); 101 | } 102 | #[test] 103 | fn unnamed_fields_tuple_vec_option() { 104 | #[derive(Debug, PartialEq, StructuralConvert)] 105 | #[convert(into(Lhs))] 106 | struct Rhs((Vec>, Vec>)); 107 | 108 | #[derive(Debug, PartialEq)] 109 | struct Lhs((Vec>, Vec>)); 110 | 111 | assert_eq!( 112 | Lhs((vec![Some(1)], vec![Some(1)])), 113 | Rhs((vec![Some(1)], vec![Some(1)])).into() 114 | ); 115 | } 116 | #[test] 117 | fn unnamed_fields_result() { 118 | #[derive(Debug, PartialEq, StructuralConvert)] 119 | #[convert(into(Lhs))] 120 | struct Rhs(Result); 121 | 122 | #[derive(Debug, PartialEq)] 123 | struct Lhs(Result); 124 | 125 | assert_eq!(Lhs(Ok(1)), Rhs(Ok(1)).into()); 126 | assert_eq!(Lhs(Err(1)), Rhs(Err(1)).into()); 127 | } 128 | #[test] 129 | fn unnamed_fields_hash_map() { 130 | #[derive(Debug, PartialEq, StructuralConvert)] 131 | #[convert(into(Lhs))] 132 | struct Rhs(HashMap); 133 | 134 | #[derive(Debug, PartialEq)] 135 | struct Lhs(HashMap); 136 | 137 | let mut lhs = HashMap::new(); 138 | lhs.insert(1, 2); 139 | 140 | let mut rhs = HashMap::new(); 141 | rhs.insert(1, 2); 142 | 143 | assert_eq!(Lhs(lhs), Rhs(rhs).into()); 144 | } 145 | #[test] 146 | fn named_fields_option() { 147 | #[derive(Debug, PartialEq, StructuralConvert)] 148 | #[convert(into(Lhs))] 149 | struct Rhs { 150 | item: Option, 151 | } 152 | 153 | #[derive(Debug, PartialEq)] 154 | struct Lhs { 155 | item: Option, 156 | } 157 | 158 | assert_eq!(Lhs { item: Some(1) }, Rhs { item: Some(1) }.into()); 159 | } 160 | #[test] 161 | fn named_fields_vec() { 162 | #[derive(Debug, PartialEq, StructuralConvert)] 163 | #[convert(into(Lhs))] 164 | struct Rhs { 165 | item: Vec, 166 | } 167 | 168 | #[derive(Debug, PartialEq)] 169 | struct Lhs { 170 | item: Vec, 171 | } 172 | 173 | assert_eq!(Lhs { item: vec![1] }, Rhs { item: vec![1] }.into()); 174 | } 175 | #[test] 176 | fn named_fields_tuple() { 177 | #[derive(Debug, PartialEq, StructuralConvert)] 178 | #[convert(into(Lhs))] 179 | struct Rhs { 180 | item: (i8, i8), 181 | } 182 | 183 | #[derive(Debug, PartialEq)] 184 | struct Lhs { 185 | item: (i32, i8), 186 | } 187 | 188 | assert_eq!(Lhs { item: (1, 2) }, Rhs { item: (1, 2) }.into()); 189 | } 190 | #[test] 191 | fn named_fields_vec_tuples() { 192 | #[derive(Debug, PartialEq, StructuralConvert)] 193 | #[convert(into(Lhs))] 194 | struct Rhs { 195 | item: Vec<(i8, i8)>, 196 | } 197 | 198 | #[derive(Debug, PartialEq)] 199 | struct Lhs { 200 | item: Vec<(i32, i8)>, 201 | } 202 | 203 | assert_eq!( 204 | Lhs { item: vec![(1, 2)] }, 205 | Rhs { item: vec![(1, 2)] }.into() 206 | ); 207 | } 208 | #[test] 209 | fn named_fields_option_vec() { 210 | #[derive(Debug, PartialEq, StructuralConvert)] 211 | #[convert(into(Lhs))] 212 | struct Rhs { 213 | item: Option>, 214 | } 215 | 216 | #[derive(Debug, PartialEq)] 217 | struct Lhs { 218 | item: Option>, 219 | } 220 | 221 | assert_eq!( 222 | Lhs { 223 | item: Some(vec![1]) 224 | }, 225 | Rhs { 226 | item: Some(vec![1]) 227 | } 228 | .try_into() 229 | .unwrap() 230 | ); 231 | } 232 | #[test] 233 | fn named_fields_vec_option() { 234 | #[derive(Debug, PartialEq, StructuralConvert)] 235 | #[convert(into(Lhs))] 236 | struct Rhs { 237 | item: Vec>, 238 | } 239 | 240 | #[derive(Debug, PartialEq)] 241 | struct Lhs { 242 | item: Vec>, 243 | } 244 | 245 | assert_eq!( 246 | Lhs { 247 | item: vec![Some(1)] 248 | }, 249 | Rhs { 250 | item: vec![Some(1)] 251 | } 252 | .try_into() 253 | .unwrap() 254 | ); 255 | } 256 | #[test] 257 | fn named_fields_vec_option_tuple() { 258 | #[derive(Debug, PartialEq, StructuralConvert)] 259 | #[convert(into(Lhs))] 260 | struct Rhs { 261 | item: Vec>, 262 | } 263 | 264 | #[derive(Debug, PartialEq)] 265 | struct Lhs { 266 | item: Vec>, 267 | } 268 | 269 | assert_eq!( 270 | Lhs { 271 | item: vec![Some((1, 2))] 272 | }, 273 | Rhs { 274 | item: vec![Some((1, 2))] 275 | } 276 | .try_into() 277 | .unwrap() 278 | ) 279 | } 280 | #[test] 281 | fn named_fields_vec_tuple_option() { 282 | #[derive(Debug, PartialEq, StructuralConvert)] 283 | #[convert(into(Lhs))] 284 | struct Rhs { 285 | item: Vec<(Option, Option)>, 286 | } 287 | 288 | #[derive(Debug, PartialEq)] 289 | struct Lhs { 290 | item: Vec<(Option, Option)>, 291 | } 292 | 293 | assert_eq!( 294 | Lhs { 295 | item: vec![(Some(1), Some(1))] 296 | }, 297 | Rhs { 298 | item: vec![(Some(1), Some(1))] 299 | } 300 | .try_into() 301 | .unwrap() 302 | ) 303 | } 304 | #[test] 305 | fn named_fields_tuple_vec_option() { 306 | #[derive(Debug, PartialEq, StructuralConvert)] 307 | #[convert(into(Lhs))] 308 | struct Rhs { 309 | item: (Vec>, Vec>), 310 | } 311 | 312 | #[derive(Debug, PartialEq)] 313 | struct Lhs { 314 | item: (Vec>, Vec>), 315 | } 316 | 317 | assert_eq!( 318 | Lhs { 319 | item: (vec![Some(1)], vec![Some(1)]) 320 | }, 321 | Rhs { 322 | item: (vec![Some(1)], vec![Some(1)]) 323 | } 324 | .try_into() 325 | .unwrap() 326 | ) 327 | } 328 | #[test] 329 | fn named_fields_result() { 330 | #[derive(Debug, PartialEq, StructuralConvert)] 331 | #[convert(into(Lhs))] 332 | struct Rhs { 333 | item: Result, 334 | } 335 | 336 | #[derive(Debug, PartialEq)] 337 | struct Lhs { 338 | item: Result, 339 | } 340 | 341 | assert_eq!(Lhs { item: Ok(1) }, Rhs { item: Ok(1) }.into()); 342 | assert_eq!(Lhs { item: Err(1) }, Rhs { item: Err(1) }.into()); 343 | } 344 | #[test] 345 | fn named_fields_hash_map() { 346 | #[derive(Debug, PartialEq, StructuralConvert)] 347 | #[convert(into(Lhs))] 348 | struct Rhs { 349 | item: HashMap, 350 | } 351 | 352 | #[derive(Debug, PartialEq)] 353 | struct Lhs { 354 | item: HashMap, 355 | } 356 | 357 | let mut lhs = HashMap::new(); 358 | lhs.insert(1, 2); 359 | 360 | let mut rhs = HashMap::new(); 361 | rhs.insert(1, 2); 362 | 363 | assert_eq!(Lhs { item: lhs }, Rhs { item: rhs }.into()); 364 | } 365 | -------------------------------------------------------------------------------- /tests/into_struct_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn fields_unnamed() { 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(into(Lhs))] 13 | struct Rhs(i8, Option); 14 | 15 | #[derive(Debug, PartialEq)] 16 | struct Lhs(i32, Option); 17 | 18 | assert_eq!(Lhs(1, Some(W(2))), Rhs(1, Some(Q(2))).into()); 19 | } 20 | 21 | #[test] 22 | fn fields_named() { 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(into(Lhs))] 25 | struct Rhs { 26 | z: i8, 27 | x: Option, 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | struct Lhs { 32 | z: i32, 33 | x: Option, 34 | } 35 | 36 | assert_eq!( 37 | Lhs { 38 | z: 1, 39 | x: Some(W(2)) 40 | }, 41 | Rhs { 42 | z: 1, 43 | x: Some(Q(2)) 44 | } 45 | .into() 46 | ); 47 | } 48 | -------------------------------------------------------------------------------- /tests/into_struct_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named_not_targeted() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs))] 7 | struct Rhs { 8 | #[convert(into(rename = "z"))] 9 | a: i8, 10 | x: u32, 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | struct Lhs { 15 | z: i8, 16 | x: u32, 17 | } 18 | 19 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { a: 1, x: 2 }.into()); 20 | } 21 | 22 | #[test] 23 | fn fields_named_targeted() { 24 | #[derive(Debug, PartialEq, StructuralConvert)] 25 | #[convert(into(Lhs1), into(Lhs2))] 26 | struct Rhs { 27 | #[convert(into(Lhs1, rename = "z"))] 28 | a: i8, 29 | x: u32, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | struct Lhs1 { 34 | z: i8, 35 | x: u32, 36 | } 37 | 38 | #[derive(Debug, PartialEq)] 39 | struct Lhs2 { 40 | a: i8, 41 | x: u32, 42 | } 43 | 44 | assert_eq!(Lhs1 { z: 1, x: 2 }, Rhs { a: 1, x: 2 }.into()); 45 | assert_eq!(Lhs2 { a: 1, x: 2 }, Rhs { a: 1, x: 2 }.into()); 46 | } 47 | -------------------------------------------------------------------------------- /tests/into_struct_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_unnamed() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(into(Lhs, skip_after = 2))] 7 | struct Rhs(i8, u32, u8); 8 | 9 | #[derive(Debug, PartialEq)] 10 | struct Lhs(i32, u32); 11 | 12 | assert_eq!(Lhs(1, 2), Rhs(1, 2, 3).into()); 13 | } 14 | 15 | #[test] 16 | fn fields_named() { 17 | #[derive(Debug, PartialEq, StructuralConvert)] 18 | #[convert(into(Lhs))] 19 | struct Rhs { 20 | z: i8, 21 | x: u32, 22 | #[convert(into(Lhs, skip))] 23 | y: u8, 24 | } 25 | 26 | #[derive(Debug, PartialEq)] 27 | struct Lhs { 28 | z: i32, 29 | x: u32, 30 | } 31 | 32 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2, y: 3 }.into()); 33 | } 34 | -------------------------------------------------------------------------------- /tests/try_from_enum.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | A, 8 | B, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_from(Rhs))] 13 | enum Lhs { 14 | A, 15 | B, 16 | } 17 | 18 | assert_eq!(Lhs::A, Rhs::A.try_into().unwrap()); 19 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 20 | } 21 | 22 | #[test] 23 | fn variant_is_unnamed() { 24 | #[derive(Debug, PartialEq)] 25 | enum Rhs { 26 | A(i8, u32), 27 | } 28 | 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(try_from(Rhs))] 31 | enum Lhs { 32 | A(i32, u32), 33 | } 34 | 35 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2).try_into().unwrap()); 36 | } 37 | 38 | #[test] 39 | fn variant_is_named() { 40 | #[derive(Debug, PartialEq)] 41 | enum Rhs { 42 | A { z: i8, x: u32 }, 43 | } 44 | 45 | #[derive(Debug, PartialEq, StructuralConvert)] 46 | #[convert(try_from(Rhs))] 47 | enum Lhs { 48 | A { z: i32, x: u32 }, 49 | } 50 | 51 | assert_eq!( 52 | Lhs::A { z: 1, x: 2 }, 53 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /tests/try_from_enum_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_default_to_field() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | X, 8 | } 9 | 10 | #[derive(Debug, PartialEq, StructuralConvert)] 11 | #[convert(try_from(Rhs))] 12 | enum Lhs { 13 | #[convert(try_from(default))] 14 | A, 15 | } 16 | 17 | assert_eq!(Lhs::A, Rhs::X.try_into().unwrap()); 18 | } 19 | 20 | #[test] 21 | fn variant_is_unnamed_default_to_field() { 22 | #[derive(Debug, PartialEq)] 23 | enum Rhs { 24 | X(i8, u32, u8), 25 | } 26 | 27 | #[derive(Debug, PartialEq, StructuralConvert)] 28 | #[convert(try_from(Rhs))] 29 | enum Lhs { 30 | #[convert(try_from(default))] 31 | A(i32, u32), 32 | } 33 | 34 | assert_eq!( 35 | Lhs::A(Default::default(), Default::default()), 36 | Rhs::X(1, 2, 3).try_into().unwrap() 37 | ); 38 | } 39 | 40 | #[test] 41 | fn variant_is_named_default_to_field() { 42 | #[derive(Debug, PartialEq)] 43 | enum Rhs { 44 | A { z: i8, x: u32 }, 45 | } 46 | 47 | #[derive(Debug, PartialEq, StructuralConvert)] 48 | #[convert(try_from(Rhs))] 49 | enum Lhs { 50 | A { 51 | z: i32, 52 | x: u32, 53 | #[convert(try_from(default))] 54 | y: u8, 55 | }, 56 | } 57 | 58 | assert_eq!( 59 | Lhs::A { 60 | z: 1, 61 | x: 2, 62 | y: Default::default() 63 | }, 64 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /tests/try_from_enum_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn variant_is_unnamed() { 11 | #[derive(Debug, PartialEq)] 12 | enum Rhs { 13 | A(i8, Option), 14 | } 15 | 16 | #[derive(Debug, PartialEq, StructuralConvert)] 17 | #[convert(try_from(Rhs))] 18 | enum Lhs { 19 | A(i32, Option), 20 | } 21 | 22 | assert_eq!( 23 | Lhs::A(1, Some(W(2))), 24 | Rhs::A(1, Some(Q(2))).try_into().unwrap() 25 | ); 26 | } 27 | 28 | #[test] 29 | fn variant_is_named() { 30 | #[derive(Debug, PartialEq)] 31 | enum Rhs { 32 | A { z: i8, x: Option }, 33 | } 34 | 35 | #[derive(Debug, PartialEq, StructuralConvert)] 36 | #[convert(try_from(Rhs))] 37 | enum Lhs { 38 | A { z: i32, x: Option }, 39 | } 40 | 41 | assert_eq!( 42 | Lhs::A { 43 | z: 1, 44 | x: Some(W(2)) 45 | }, 46 | Rhs::A { 47 | z: 1, 48 | x: Some(Q(2)) 49 | } 50 | .try_into() 51 | .unwrap() 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /tests/try_from_enum_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | X, 8 | B, 9 | C, 10 | } 11 | 12 | #[derive(Debug, PartialEq, StructuralConvert)] 13 | #[convert(try_from(Rhs))] 14 | enum Lhs { 15 | #[convert(try_from(rename = "X"))] 16 | A, 17 | B, 18 | C, 19 | } 20 | 21 | assert_eq!(Lhs::A, Rhs::X.try_into().unwrap()); 22 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 23 | assert_eq!(Lhs::C, Rhs::C.try_into().unwrap()); 24 | } 25 | 26 | #[test] 27 | fn variant_is_unit_targeted() { 28 | #[derive(Debug, PartialEq)] 29 | enum Rhs1 { 30 | X, 31 | B, 32 | C, 33 | } 34 | 35 | #[derive(Debug, PartialEq)] 36 | enum Rhs2 { 37 | A, 38 | B, 39 | C, 40 | } 41 | 42 | #[derive(Debug, PartialEq, StructuralConvert)] 43 | #[convert(try_from(Rhs1), try_from(Rhs2))] 44 | enum Lhs { 45 | #[convert(try_from(Rhs1, rename = "X"), try_from(Rhs2))] 46 | A, 47 | B, 48 | C, 49 | } 50 | 51 | assert_eq!(Lhs::A, Rhs1::X.try_into().unwrap()); 52 | assert_eq!(Lhs::B, Rhs1::B.try_into().unwrap()); 53 | assert_eq!(Lhs::C, Rhs1::C.try_into().unwrap()); 54 | 55 | assert_eq!(Lhs::A, Rhs2::A.try_into().unwrap()); 56 | assert_eq!(Lhs::B, Rhs2::B.try_into().unwrap()); 57 | assert_eq!(Lhs::C, Rhs2::C.try_into().unwrap()); 58 | } 59 | 60 | #[test] 61 | fn variant_is_unnamed() { 62 | #[derive(Debug, PartialEq)] 63 | enum Rhs { 64 | X(i8, u32, u8), 65 | } 66 | 67 | #[derive(Debug, PartialEq, StructuralConvert)] 68 | #[convert(try_from(Rhs))] 69 | enum Lhs { 70 | #[convert(try_from(rename = "X"))] 71 | A(i32, u32), 72 | } 73 | 74 | assert_eq!(Lhs::A(1, 2), Rhs::X(1, 2, 3).try_into().unwrap()); 75 | } 76 | 77 | #[test] 78 | fn variant_is_named() { 79 | #[derive(Debug, PartialEq)] 80 | enum Rhs { 81 | X { z: i8, x: u32, y: u8 }, 82 | } 83 | 84 | #[derive(Debug, PartialEq, StructuralConvert)] 85 | #[convert(try_from(Rhs))] 86 | enum Lhs { 87 | #[convert(try_from(rename = "X"))] 88 | A { z: i32, x: u32 }, 89 | } 90 | 91 | assert_eq!( 92 | Lhs::A { z: 1, x: 2 }, 93 | Rhs::X { z: 1, x: 2, y: 3 }.try_into().unwrap() 94 | ); 95 | } 96 | 97 | #[test] 98 | fn variant_is_named_fields_named_not_targeted() { 99 | #[derive(Debug, PartialEq)] 100 | enum Rhs { 101 | A { z: i8, x: u32 }, 102 | } 103 | 104 | #[derive(Debug, PartialEq, StructuralConvert)] 105 | #[convert(try_from(Rhs))] 106 | enum Lhs { 107 | A { 108 | #[convert(try_from(rename = "z"))] 109 | a: i32, 110 | x: u32, 111 | }, 112 | } 113 | 114 | assert_eq!( 115 | Lhs::A { a: 1, x: 2 }, 116 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 117 | ); 118 | } 119 | 120 | #[test] 121 | fn variant_is_named_fields_named_targeted() { 122 | #[derive(Debug, PartialEq)] 123 | enum Rhs1 { 124 | A { z: i8, x: u32 }, 125 | } 126 | 127 | #[derive(Debug, PartialEq)] 128 | enum Rhs2 { 129 | A { a: i8, x: u32 }, 130 | } 131 | 132 | #[derive(Debug, PartialEq, StructuralConvert)] 133 | #[convert(try_from(Rhs1), try_from(Rhs2))] 134 | enum Lhs { 135 | A { 136 | #[convert(try_from(Rhs1::A, rename = "z"))] 137 | a: i32, 138 | x: u32, 139 | }, 140 | } 141 | 142 | assert_eq!( 143 | Lhs::A { a: 1, x: 2 }, 144 | Rhs1::A { z: 1, x: 2 }.try_into().unwrap() 145 | ); 146 | assert_eq!( 147 | Lhs::A { a: 1, x: 2 }, 148 | Rhs2::A { a: 1, x: 2 }.try_into().unwrap() 149 | ); 150 | } 151 | -------------------------------------------------------------------------------- /tests/try_from_enum_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | enum Rhs { 7 | A, 8 | B, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_from(Rhs))] 13 | enum Lhs { 14 | #[convert(try_from(skip))] 15 | _C, 16 | A, 17 | B, 18 | } 19 | 20 | assert_eq!(Lhs::A, Rhs::A.try_into().unwrap()); 21 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 22 | } 23 | 24 | #[test] 25 | fn variant_is_unit_targeted() { 26 | #[derive(Debug, PartialEq)] 27 | enum Rhs1 { 28 | A, 29 | B, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | enum Rhs2 { 34 | C, 35 | A, 36 | B, 37 | } 38 | 39 | #[derive(Debug, PartialEq, StructuralConvert)] 40 | #[convert(try_from(Rhs1), try_from(Rhs2))] 41 | enum Lhs { 42 | #[convert(try_from(Rhs1, skip), try_from(Rhs2))] 43 | C, 44 | A, 45 | B, 46 | } 47 | 48 | assert_eq!(Lhs::A, Rhs1::A.try_into().unwrap()); 49 | assert_eq!(Lhs::B, Rhs1::B.try_into().unwrap()); 50 | assert_eq!(Lhs::A, Rhs2::A.try_into().unwrap()); 51 | assert_eq!(Lhs::B, Rhs2::B.try_into().unwrap()); 52 | assert_eq!(Lhs::C, Rhs2::C.try_into().unwrap()); 53 | } 54 | 55 | #[test] 56 | fn variant_is_unnamed() { 57 | #[derive(Debug, PartialEq)] 58 | enum Rhs { 59 | A(i8, u32, u8), 60 | } 61 | 62 | #[derive(Debug, PartialEq, StructuralConvert)] 63 | #[convert(try_from(Rhs))] 64 | enum Lhs { 65 | A(i32, u32), 66 | } 67 | 68 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2, 3).try_into().unwrap()); 69 | } 70 | 71 | #[test] 72 | fn variant_is_named() { 73 | #[derive(Debug, PartialEq)] 74 | enum Rhs { 75 | A { z: i8, x: u32, y: u8 }, 76 | } 77 | 78 | #[derive(Debug, PartialEq, StructuralConvert)] 79 | #[convert(try_from(Rhs))] 80 | enum Lhs { 81 | A { z: i32, x: u32 }, 82 | } 83 | 84 | assert_eq!( 85 | Lhs::A { z: 1, x: 2 }, 86 | Rhs::A { z: 1, x: 2, y: 3 }.try_into().unwrap() 87 | ); 88 | } 89 | -------------------------------------------------------------------------------- /tests/try_from_struct.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn unit() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs; 7 | 8 | #[derive(Debug, PartialEq, StructuralConvert)] 9 | #[convert(try_from(Rhs))] 10 | struct Lhs; 11 | 12 | assert_eq!(Lhs, Rhs.try_into().unwrap()); 13 | } 14 | 15 | #[test] 16 | fn fields_unnamed() { 17 | #[derive(Debug, PartialEq)] 18 | struct Rhs(i8, u32); 19 | 20 | #[derive(Debug, PartialEq, StructuralConvert)] 21 | #[convert(try_from(Rhs))] 22 | struct Lhs(i32, u32); 23 | 24 | assert_eq!(Lhs(1, 2), Rhs(1, 2).try_into().unwrap()); 25 | } 26 | 27 | #[test] 28 | fn fields_named() { 29 | #[derive(Debug, PartialEq)] 30 | struct Rhs { 31 | z: i8, 32 | x: u32, 33 | } 34 | 35 | #[derive(Debug, PartialEq, StructuralConvert)] 36 | #[convert(try_from(Rhs))] 37 | struct Lhs { 38 | z: i32, 39 | x: u32, 40 | } 41 | 42 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.try_into().unwrap()); 43 | } 44 | -------------------------------------------------------------------------------- /tests/try_from_struct_as.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named() { 5 | #[derive(Debug, PartialEq)] 6 | pub struct Rhs { 7 | pub r#type: i32, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 11 | pub enum RhsEnum { 12 | Mobile = 0, 13 | Home = 1, 14 | } 15 | 16 | impl TryFrom for RhsEnum { 17 | type Error = &'static str; 18 | 19 | fn try_from(value: i32) -> Result { 20 | Ok(match value { 21 | 0 => RhsEnum::Mobile, 22 | 1 => RhsEnum::Home, 23 | _ => return Err(""), 24 | }) 25 | } 26 | } 27 | 28 | #[derive(Debug, PartialEq, StructuralConvert)] 29 | #[convert(try_from(Rhs))] 30 | pub struct Lhs { 31 | #[convert(try_from(as = "RhsEnum"))] 32 | pub r#type: LhsEnum, 33 | } 34 | 35 | #[derive(Debug, PartialEq, Eq, StructuralConvert, Default)] 36 | #[convert(try_from(RhsEnum))] 37 | pub enum LhsEnum { 38 | #[default] 39 | Mobile, 40 | Home, 41 | } 42 | 43 | assert_eq!( 44 | Lhs { 45 | r#type: LhsEnum::Home 46 | }, 47 | Rhs { r#type: 1 }.try_into().unwrap() 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /tests/try_from_struct_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_named_default_to_field() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs { 7 | z: i8, 8 | x: u32, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_from(Rhs))] 13 | struct Lhs { 14 | z: i32, 15 | x: u32, 16 | #[convert(try_from(default))] 17 | y: u8, 18 | } 19 | 20 | assert_eq!( 21 | Lhs { 22 | z: 1, 23 | x: 2, 24 | y: Default::default() 25 | }, 26 | Rhs { z: 1, x: 2 }.try_into().unwrap() 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /tests/try_from_struct_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn fields_unnamed() { 11 | #[derive(Debug, PartialEq)] 12 | struct Rhs(i8, Option); 13 | 14 | #[derive(Debug, PartialEq, StructuralConvert)] 15 | #[convert(try_from(Rhs))] 16 | struct Lhs(i32, Option); 17 | 18 | assert_eq!(Lhs(1, Some(W(2))), Rhs(1, Some(Q(2))).try_into().unwrap()); 19 | } 20 | 21 | #[test] 22 | fn fields_named() { 23 | #[derive(Debug, PartialEq)] 24 | struct Rhs { 25 | z: i8, 26 | x: Option, 27 | } 28 | 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(try_from(Rhs))] 31 | struct Lhs { 32 | z: i32, 33 | x: Option, 34 | } 35 | 36 | assert_eq!( 37 | Lhs { 38 | z: 1, 39 | x: Some(W(2)) 40 | }, 41 | Rhs { 42 | z: 1, 43 | x: Some(Q(2)) 44 | } 45 | .try_into() 46 | .unwrap() 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /tests/try_from_struct_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named_not_targeted() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs { 7 | z: i8, 8 | x: u32, 9 | } 10 | 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_from(Rhs))] 13 | struct Lhs { 14 | #[convert(try_from(rename = "z"))] 15 | a: i32, 16 | x: u32, 17 | } 18 | 19 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs { z: 1, x: 2 }.try_into().unwrap()); 20 | } 21 | 22 | #[test] 23 | fn fields_named_targeted() { 24 | #[derive(Debug, PartialEq)] 25 | struct Rhs1 { 26 | z: i8, 27 | x: u32, 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | struct Rhs2 { 32 | a: i8, 33 | x: u32, 34 | } 35 | 36 | #[derive(Debug, PartialEq, StructuralConvert)] 37 | #[convert(try_from(Rhs1), try_from(Rhs2))] 38 | struct Lhs { 39 | #[convert(try_from(Rhs1, rename = "z"))] 40 | a: i32, 41 | x: u32, 42 | } 43 | 44 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs1 { z: 1, x: 2 }.try_into().unwrap()); 45 | assert_eq!(Lhs { a: 1, x: 2 }, Rhs2 { a: 1, x: 2 }.try_into().unwrap()); 46 | } 47 | -------------------------------------------------------------------------------- /tests/try_from_struct_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_unnamed() { 5 | #[derive(Debug, PartialEq)] 6 | struct Rhs(i8, u32, u8); 7 | 8 | #[derive(Debug, PartialEq, StructuralConvert)] 9 | #[convert(try_from(Rhs))] 10 | struct Lhs(i32, u32); 11 | 12 | assert_eq!(Lhs(1, 2), Rhs(1, 2, 3).try_into().unwrap()); 13 | assert_eq!(Lhs(1, 2), Rhs(1, 2, 3).try_into().unwrap()); 14 | } 15 | 16 | #[test] 17 | fn fields_named() { 18 | #[derive(Debug, PartialEq)] 19 | struct Rhs { 20 | z: i8, 21 | x: u32, 22 | y: u8, 23 | } 24 | 25 | #[derive(Debug, PartialEq, StructuralConvert)] 26 | #[convert(try_from(Rhs))] 27 | struct Lhs { 28 | z: i32, 29 | x: u32, 30 | } 31 | 32 | assert_eq!( 33 | Lhs { z: 1, x: 2 }, 34 | Rhs { z: 1, x: 2, y: 3 }.try_into().unwrap() 35 | ); 36 | assert_eq!( 37 | Lhs { z: 1, x: 2 }, 38 | Rhs { z: 1, x: 2, y: 3 }.try_into().unwrap() 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /tests/try_into_enum.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs))] 7 | enum Rhs { 8 | A, 9 | B, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | enum Lhs { 14 | A, 15 | B, 16 | } 17 | 18 | assert_eq!(Lhs::A, Rhs::A.try_into().unwrap()); 19 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 20 | } 21 | 22 | #[test] 23 | fn variant_is_unnamed() { 24 | #[derive(Debug, PartialEq, StructuralConvert)] 25 | #[convert(try_into(Lhs))] 26 | enum Rhs { 27 | A(i8, u32), 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | enum Lhs { 32 | A(i32, u32), 33 | } 34 | 35 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2).try_into().unwrap()); 36 | } 37 | 38 | #[test] 39 | fn variant_is_named() { 40 | #[derive(Debug, PartialEq, StructuralConvert)] 41 | #[convert(try_into(Lhs))] 42 | enum Rhs { 43 | A { z: i8, x: u32 }, 44 | } 45 | 46 | #[derive(Debug, PartialEq)] 47 | enum Lhs { 48 | A { z: i32, x: u32 }, 49 | } 50 | 51 | assert_eq!( 52 | Lhs::A { z: 1, x: 2 }, 53 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 54 | ); 55 | } 56 | -------------------------------------------------------------------------------- /tests/try_into_enum_default.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_default() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs, default))] 7 | enum Rhs { 8 | #[convert(try_into(skip))] 9 | A, 10 | } 11 | 12 | #[derive(Debug, PartialEq, Default)] 13 | enum Lhs { 14 | #[default] 15 | X, 16 | } 17 | 18 | assert_eq!(Lhs::default(), Rhs::A.try_into().unwrap()); 19 | } 20 | 21 | #[test] 22 | fn variant_is_unnamed_default() { 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(try_into(Lhs, default))] 25 | enum Rhs { 26 | #[convert(try_into(skip))] 27 | A(i8, u32), 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | enum Lhs { 32 | X(i32, u32), 33 | } 34 | 35 | impl Default for Lhs { 36 | fn default() -> Self { 37 | Lhs::X(Default::default(), Default::default()) 38 | } 39 | } 40 | 41 | assert_eq!(Lhs::default(), Rhs::A(1, 2).try_into().unwrap()); 42 | } 43 | 44 | #[test] 45 | fn variant_is_named_default() { 46 | #[derive(Debug, PartialEq, StructuralConvert)] 47 | #[convert(try_into(Lhs, default))] 48 | enum Rhs { 49 | #[convert(try_into(skip))] 50 | A { z: i8, x: u32 }, 51 | } 52 | 53 | #[derive(Debug, PartialEq)] 54 | enum Lhs { 55 | X {}, 56 | } 57 | 58 | impl Default for Lhs { 59 | fn default() -> Self { 60 | Lhs::X {} 61 | } 62 | } 63 | 64 | assert_eq!(Lhs::default(), Rhs::A { z: 1, x: 2 }.try_into().unwrap()); 65 | } 66 | 67 | #[test] 68 | fn variant_is_named_default_to_field() { 69 | #[derive(Debug, PartialEq, StructuralConvert)] 70 | #[convert(try_into(Lhs, default_for_fields("y", "e")))] 71 | enum Rhs { 72 | A { z: i8, x: u32 }, 73 | } 74 | 75 | #[derive(Debug, PartialEq)] 76 | enum Lhs { 77 | A { z: i32, x: u32, y: u32, e: u32 }, 78 | } 79 | 80 | assert_eq!( 81 | Lhs::A { 82 | z: 1, 83 | x: 2, 84 | y: Default::default(), 85 | e: Default::default() 86 | }, 87 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /tests/try_into_enum_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn variant_is_unnamed() { 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_into(Lhs))] 13 | enum Rhs { 14 | A(i8, Option), 15 | } 16 | 17 | #[derive(Debug, PartialEq)] 18 | enum Lhs { 19 | A(i32, Option), 20 | } 21 | 22 | assert_eq!( 23 | Lhs::A(1, Some(W(2))), 24 | Rhs::A(1, Some(Q(2))).try_into().unwrap() 25 | ); 26 | } 27 | 28 | #[test] 29 | fn variant_is_named() { 30 | #[derive(Debug, PartialEq, StructuralConvert)] 31 | #[convert(try_into(Lhs))] 32 | enum Rhs { 33 | A { z: i8, x: Option }, 34 | } 35 | 36 | #[derive(Debug, PartialEq)] 37 | enum Lhs { 38 | A { z: i32, x: Option }, 39 | } 40 | 41 | assert_eq!( 42 | Lhs::A { 43 | z: 1, 44 | x: Some(W(2)) 45 | }, 46 | Rhs::A { 47 | z: 1, 48 | x: Some(Q(2)) 49 | } 50 | .try_into() 51 | .unwrap() 52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /tests/try_into_enum_rename.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs))] 7 | enum Rhs { 8 | #[convert(try_into(rename = "X"))] 9 | A, 10 | B, 11 | } 12 | 13 | #[derive(Debug, PartialEq)] 14 | enum Lhs { 15 | X, 16 | B, 17 | } 18 | 19 | assert_eq!(Lhs::X, Rhs::A.try_into().unwrap()); 20 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 21 | } 22 | 23 | #[test] 24 | fn variant_is_unit_targeted() { 25 | #[derive(Debug, PartialEq, StructuralConvert)] 26 | #[convert(try_into(Lhs1), try_into(Lhs2))] 27 | enum Rhs { 28 | #[convert(try_into(Lhs1, rename = "X"), try_into(Lhs2))] 29 | A, 30 | B, 31 | } 32 | 33 | #[derive(Debug, PartialEq)] 34 | enum Lhs1 { 35 | X, 36 | B, 37 | } 38 | 39 | #[derive(Debug, PartialEq)] 40 | enum Lhs2 { 41 | A, 42 | B, 43 | } 44 | 45 | assert_eq!(Lhs1::X, Rhs::A.try_into().unwrap()); 46 | assert_eq!(Lhs1::B, Rhs::B.try_into().unwrap()); 47 | 48 | assert_eq!(Lhs2::A, Rhs::A.try_into().unwrap()); 49 | assert_eq!(Lhs2::B, Rhs::B.try_into().unwrap()); 50 | } 51 | 52 | #[test] 53 | fn variant_is_unnamed() { 54 | #[derive(Debug, PartialEq, StructuralConvert)] 55 | #[convert(try_into(Lhs))] 56 | enum Rhs { 57 | #[convert(try_into(rename = "X"))] 58 | A(i8, u32), 59 | } 60 | 61 | #[derive(Debug, PartialEq)] 62 | enum Lhs { 63 | X(i32, u32), 64 | } 65 | 66 | assert_eq!(Lhs::X(1, 2), Rhs::A(1, 2).try_into().unwrap()); 67 | } 68 | 69 | #[test] 70 | fn variant_is_named() { 71 | #[derive(Debug, PartialEq, StructuralConvert)] 72 | #[convert(try_into(Lhs))] 73 | enum Rhs { 74 | #[convert(try_into(rename = "X"))] 75 | A { z: i8, x: u32 }, 76 | } 77 | 78 | #[derive(Debug, PartialEq)] 79 | enum Lhs { 80 | X { z: i32, x: u32 }, 81 | } 82 | 83 | assert_eq!( 84 | Lhs::X { z: 1, x: 2 }, 85 | Rhs::A { z: 1, x: 2 }.try_into().unwrap() 86 | ); 87 | } 88 | 89 | #[test] 90 | fn fields_named_not_targeted() { 91 | #[derive(Debug, PartialEq, StructuralConvert)] 92 | #[convert(try_into(Lhs))] 93 | enum Rhs { 94 | A { 95 | #[convert(try_into(rename = "z"))] 96 | a: i8, 97 | x: u32, 98 | }, 99 | } 100 | 101 | #[derive(Debug, PartialEq)] 102 | enum Lhs { 103 | A { z: i8, x: u32 }, 104 | } 105 | 106 | assert_eq!( 107 | Lhs::A { z: 1, x: 2 }, 108 | Rhs::A { a: 1, x: 2 }.try_into().unwrap() 109 | ); 110 | } 111 | 112 | #[test] 113 | fn fields_named_targeted() { 114 | #[derive(Debug, PartialEq, StructuralConvert)] 115 | #[convert(try_into(Lhs1), try_into(Lhs2))] 116 | enum Rhs { 117 | A { 118 | #[convert(try_into(Lhs1::A, rename = "z"))] 119 | a: i8, 120 | x: u32, 121 | }, 122 | } 123 | 124 | #[derive(Debug, PartialEq)] 125 | enum Lhs1 { 126 | A { z: i8, x: u32 }, 127 | } 128 | 129 | #[derive(Debug, PartialEq)] 130 | enum Lhs2 { 131 | A { a: i8, x: u32 }, 132 | } 133 | 134 | assert_eq!( 135 | Lhs1::A { z: 1, x: 2 }, 136 | Rhs::A { a: 1, x: 2 }.try_into().unwrap() 137 | ); 138 | assert_eq!( 139 | Lhs2::A { a: 1, x: 2 }, 140 | Rhs::A { a: 1, x: 2 }.try_into().unwrap() 141 | ); 142 | } 143 | -------------------------------------------------------------------------------- /tests/try_into_enum_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn variant_is_unit_non_targeted() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs))] 7 | enum Rhs { 8 | A, 9 | B, 10 | } 11 | 12 | #[derive(Debug, PartialEq)] 13 | enum Lhs { 14 | A, 15 | B, 16 | _C, 17 | } 18 | 19 | assert_eq!(Lhs::A, Rhs::A.try_into().unwrap()); 20 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 21 | } 22 | 23 | #[test] 24 | fn variant_is_unit_targeted() { 25 | #[derive(Debug, PartialEq, StructuralConvert)] 26 | #[convert(try_into(Lhs))] 27 | enum Rhs { 28 | A, 29 | B, 30 | } 31 | 32 | #[derive(Debug, PartialEq)] 33 | enum Lhs { 34 | A, 35 | B, 36 | _C, 37 | } 38 | 39 | assert_eq!(Lhs::A, Rhs::A.try_into().unwrap()); 40 | assert_eq!(Lhs::B, Rhs::B.try_into().unwrap()); 41 | } 42 | 43 | #[test] 44 | fn variant_is_unnamed() { 45 | #[derive(Debug, PartialEq, StructuralConvert)] 46 | #[convert(try_into(Lhs))] 47 | enum Rhs { 48 | #[convert(try_into(Lhs, skip_after = 2))] 49 | A(i8, u32, u8), 50 | } 51 | 52 | #[derive(Debug, PartialEq)] 53 | enum Lhs { 54 | A(i32, u32), 55 | } 56 | 57 | assert_eq!(Lhs::A(1, 2), Rhs::A(1, 2, 3).try_into().unwrap()); 58 | } 59 | 60 | #[test] 61 | fn variant_is_named() { 62 | #[derive(Debug, PartialEq, StructuralConvert)] 63 | #[convert(try_into(Lhs))] 64 | enum Rhs { 65 | A { 66 | z: i8, 67 | x: u32, 68 | 69 | #[convert(try_into(Lhs::A, skip))] 70 | y: u8, 71 | }, 72 | } 73 | 74 | #[derive(Debug, PartialEq)] 75 | enum Lhs { 76 | A { z: i32, x: u32 }, 77 | } 78 | 79 | assert_eq!( 80 | Lhs::A { z: 1, x: 2 }, 81 | Rhs::A { z: 1, x: 2, y: 3 }.try_into().unwrap() 82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /tests/try_into_struct.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn unit() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs))] 7 | struct Rhs; 8 | 9 | #[derive(Debug, PartialEq)] 10 | struct Lhs; 11 | 12 | assert_eq!(Lhs, Rhs.try_into().unwrap()); 13 | } 14 | 15 | #[test] 16 | fn fields_unnamed() { 17 | #[derive(Debug, PartialEq, StructuralConvert)] 18 | #[convert(try_into(Lhs))] 19 | struct Rhs(i8, u32); 20 | 21 | #[derive(Debug, PartialEq)] 22 | struct Lhs(i32, u32); 23 | 24 | assert_eq!(Lhs(1, 2), Rhs(1, 2).try_into().unwrap()); 25 | } 26 | 27 | #[test] 28 | fn fields_named() { 29 | #[derive(Debug, PartialEq, StructuralConvert)] 30 | #[convert(try_into(Lhs))] 31 | struct Rhs { 32 | z: i8, 33 | x: u32, 34 | } 35 | 36 | #[derive(Debug, PartialEq)] 37 | struct Lhs { 38 | z: i32, 39 | x: u32, 40 | } 41 | 42 | assert_eq!(Lhs { z: 1, x: 2 }, Rhs { z: 1, x: 2 }.try_into().unwrap()); 43 | } 44 | -------------------------------------------------------------------------------- /tests/try_into_struct_as.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_named_i32() { 5 | #[derive(Debug, PartialEq)] 6 | pub struct Rhs { 7 | pub r#type: i32, 8 | } 9 | 10 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] 11 | pub enum RhsEnum { 12 | Mobile = 0, 13 | Home = 1, 14 | Work = 2, 15 | } 16 | 17 | impl From for i32 { 18 | fn from(value: RhsEnum) -> Self { 19 | value as i32 20 | } 21 | } 22 | 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(try_into(Rhs))] 25 | pub struct Lhs { 26 | #[convert(try_into(as = "RhsEnum"))] 27 | pub r#type: LhsEnum, 28 | } 29 | 30 | #[derive(Debug, PartialEq, Eq, StructuralConvert)] 31 | #[convert(try_into(RhsEnum))] 32 | pub enum LhsEnum { 33 | Mobile, 34 | Home, 35 | Work, 36 | } 37 | 38 | assert_eq!( 39 | Rhs { r#type: 1 }, 40 | Lhs { 41 | r#type: LhsEnum::Home 42 | } 43 | .try_into() 44 | .unwrap(), 45 | ); 46 | } 47 | 48 | #[test] 49 | fn fields_named_option() { 50 | #[derive(Debug, PartialEq)] 51 | struct Q(u32); 52 | #[derive(Debug, PartialEq, StructuralConvert)] 53 | #[convert(from(Q))] 54 | struct W(u32); 55 | 56 | #[derive(Debug, PartialEq, StructuralConvert)] 57 | #[convert(try_into(Lhs))] 58 | struct Rhs { 59 | z: i8, 60 | #[convert(try_into(Lhs, as = "Option::"))] 61 | x: Q, 62 | } 63 | 64 | #[derive(Debug, PartialEq)] 65 | struct Lhs { 66 | z: i32, 67 | x: Option, 68 | } 69 | 70 | assert_eq!( 71 | Lhs { 72 | z: 1, 73 | x: Some(W(2)) 74 | }, 75 | Rhs { z: 1, x: Q(2) }.try_into().unwrap() 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /tests/try_into_struct_generic_types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use structural_convert::StructuralConvert; 4 | 5 | #[test] 6 | fn unnamed_fields_option() { 7 | #[derive(Debug, PartialEq, StructuralConvert)] 8 | #[convert(try_into(Lhs))] 9 | struct Rhs(Option); 10 | 11 | #[derive(Debug, PartialEq)] 12 | struct Lhs(Option); 13 | 14 | assert_eq!(Lhs(Some(1)), Rhs(Some(1)).try_into().unwrap()); 15 | } 16 | 17 | #[test] 18 | fn unnamed_fields_vec() { 19 | #[derive(Debug, PartialEq, StructuralConvert)] 20 | #[convert(try_into(Lhs))] 21 | struct Rhs(Vec); 22 | 23 | #[derive(Debug, PartialEq)] 24 | struct Lhs(Vec); 25 | 26 | assert_eq!(Lhs(vec![1]), Rhs(vec![1]).try_into().unwrap()); 27 | } 28 | 29 | #[test] 30 | fn unnamed_fields_tuple() { 31 | #[derive(Debug, PartialEq, StructuralConvert)] 32 | #[convert(try_into(Lhs))] 33 | struct Rhs((i8, i8)); 34 | 35 | #[derive(Debug, PartialEq)] 36 | struct Lhs((i32, i8)); 37 | 38 | assert_eq!(Lhs((1, 2)), Rhs((1, 2)).try_into().unwrap()); 39 | } 40 | 41 | #[test] 42 | fn unnamed_fields_vec_tuples() { 43 | #[derive(Debug, PartialEq, StructuralConvert)] 44 | #[convert(try_into(Lhs))] 45 | struct Rhs(Vec<(i8, i8)>); 46 | 47 | #[derive(Debug, PartialEq)] 48 | struct Lhs(Vec<(i32, i8)>); 49 | 50 | assert_eq!(Lhs(vec![(1, 2)]), Rhs(vec![(1, 2)]).try_into().unwrap()); 51 | } 52 | 53 | #[test] 54 | fn unnamed_fields_option_vec() { 55 | #[derive(Debug, PartialEq, StructuralConvert)] 56 | #[convert(try_into(Lhs))] 57 | struct Rhs(Option>); 58 | 59 | #[derive(Debug, PartialEq)] 60 | struct Lhs(Option>); 61 | 62 | assert_eq!(Lhs(Some(vec![1])), Rhs(Some(vec![1])).try_into().unwrap()); 63 | } 64 | 65 | #[test] 66 | fn unnamed_fields_vec_option() { 67 | #[derive(Debug, PartialEq, StructuralConvert)] 68 | #[convert(try_into(Lhs))] 69 | struct Rhs(Vec>); 70 | 71 | #[derive(Debug, PartialEq)] 72 | struct Lhs(Vec>); 73 | 74 | assert_eq!(Lhs(vec![Some(1)]), Rhs(vec![Some(1)]).try_into().unwrap()); 75 | } 76 | 77 | #[test] 78 | fn unnamed_fields_vec_option_tuple() { 79 | #[derive(Debug, PartialEq, StructuralConvert)] 80 | #[convert(try_into(Lhs))] 81 | struct Rhs(Vec>); 82 | 83 | #[derive(Debug, PartialEq)] 84 | struct Lhs(Vec>); 85 | 86 | assert_eq!( 87 | Lhs(vec![Some((1, 2))]), 88 | Rhs(vec![Some((1, 2))]).try_into().unwrap() 89 | ); 90 | } 91 | #[test] 92 | fn unnamed_fields_vec_tuple_option() { 93 | #[derive(Debug, PartialEq, StructuralConvert)] 94 | #[convert(try_into(Lhs))] 95 | struct Rhs(Vec<(Option, Option)>); 96 | 97 | #[derive(Debug, PartialEq)] 98 | struct Lhs(Vec<(Option, Option)>); 99 | 100 | assert_eq!( 101 | Lhs(vec![(Some(1), Some(1))]), 102 | Rhs(vec![(Some(1), Some(1))]).try_into().unwrap() 103 | ); 104 | } 105 | #[test] 106 | fn unnamed_fields_tuple_vec_option() { 107 | #[derive(Debug, PartialEq, StructuralConvert)] 108 | #[convert(try_into(Lhs))] 109 | struct Rhs((Vec>, Vec>)); 110 | 111 | #[derive(Debug, PartialEq)] 112 | struct Lhs((Vec>, Vec>)); 113 | 114 | assert_eq!( 115 | Lhs((vec![Some(1)], vec![Some(1)])), 116 | Rhs((vec![Some(1)], vec![Some(1)])).try_into().unwrap() 117 | ); 118 | } 119 | #[test] 120 | fn unnamed_fields_result() { 121 | #[derive(Debug, PartialEq, StructuralConvert)] 122 | #[convert(try_into(Lhs))] 123 | struct Rhs(Result); 124 | 125 | #[derive(Debug, PartialEq)] 126 | struct Lhs(Result); 127 | 128 | assert_eq!(Lhs(Ok(1)), Rhs(Ok(1)).try_into().unwrap()); 129 | assert_eq!(Lhs(Err(1)), Rhs(Err(1)).try_into().unwrap()); 130 | } 131 | #[test] 132 | fn unnamed_fields_hash_map() { 133 | #[derive(Debug, PartialEq, StructuralConvert)] 134 | #[convert(try_into(Lhs))] 135 | struct Rhs(HashMap); 136 | 137 | #[derive(Debug, PartialEq)] 138 | struct Lhs(HashMap); 139 | 140 | let mut lhs = HashMap::new(); 141 | lhs.insert(1, 2); 142 | 143 | let mut rhs = HashMap::new(); 144 | rhs.insert(1, 2); 145 | 146 | assert_eq!(Lhs(lhs), Rhs(rhs).try_into().unwrap()); 147 | } 148 | #[test] 149 | fn named_fields_option() { 150 | #[derive(Debug, PartialEq, StructuralConvert)] 151 | #[convert(try_into(Lhs))] 152 | struct Rhs { 153 | item: Option, 154 | } 155 | 156 | #[derive(Debug, PartialEq)] 157 | struct Lhs { 158 | item: Option, 159 | } 160 | 161 | assert_eq!( 162 | Lhs { item: Some(1) }, 163 | Rhs { item: Some(1) }.try_into().unwrap() 164 | ); 165 | } 166 | #[test] 167 | fn named_fields_vec() { 168 | #[derive(Debug, PartialEq, StructuralConvert)] 169 | #[convert(try_into(Lhs))] 170 | struct Rhs { 171 | item: Vec, 172 | } 173 | 174 | #[derive(Debug, PartialEq)] 175 | struct Lhs { 176 | item: Vec, 177 | } 178 | 179 | assert_eq!( 180 | Lhs { item: vec![1] }, 181 | Rhs { item: vec![1] }.try_into().unwrap() 182 | ); 183 | } 184 | #[test] 185 | fn named_fields_tuple() { 186 | #[derive(Debug, PartialEq, StructuralConvert)] 187 | #[convert(try_into(Lhs))] 188 | struct Rhs { 189 | item: (i8, i8), 190 | } 191 | 192 | #[derive(Debug, PartialEq)] 193 | struct Lhs { 194 | item: (i32, i8), 195 | } 196 | 197 | assert_eq!( 198 | Lhs { item: (1, 2) }, 199 | Rhs { item: (1, 2) }.try_into().unwrap() 200 | ); 201 | } 202 | #[test] 203 | fn named_fields_vec_tuples() { 204 | #[derive(Debug, PartialEq, StructuralConvert)] 205 | #[convert(try_into(Lhs))] 206 | struct Rhs { 207 | item: Vec<(i8, i8)>, 208 | } 209 | 210 | #[derive(Debug, PartialEq)] 211 | struct Lhs { 212 | item: Vec<(i32, i8)>, 213 | } 214 | 215 | assert_eq!( 216 | Lhs { item: vec![(1, 2)] }, 217 | Rhs { item: vec![(1, 2)] }.try_into().unwrap() 218 | ); 219 | } 220 | #[test] 221 | fn named_fields_option_vec() { 222 | #[derive(Debug, PartialEq, StructuralConvert)] 223 | #[convert(try_into(Lhs))] 224 | struct Rhs { 225 | item: Option>, 226 | } 227 | 228 | #[derive(Debug, PartialEq)] 229 | struct Lhs { 230 | item: Option>, 231 | } 232 | 233 | assert_eq!( 234 | Lhs { 235 | item: Some(vec![1]) 236 | }, 237 | Rhs { 238 | item: Some(vec![1]) 239 | } 240 | .try_into() 241 | .unwrap() 242 | ); 243 | } 244 | #[test] 245 | fn named_fields_vec_option() { 246 | #[derive(Debug, PartialEq, StructuralConvert)] 247 | #[convert(try_into(Lhs))] 248 | struct Rhs { 249 | item: Vec>, 250 | } 251 | 252 | #[derive(Debug, PartialEq)] 253 | struct Lhs { 254 | item: Vec>, 255 | } 256 | 257 | assert_eq!( 258 | Lhs { 259 | item: vec![Some(1)] 260 | }, 261 | Rhs { 262 | item: vec![Some(1)] 263 | } 264 | .try_into() 265 | .unwrap() 266 | ); 267 | } 268 | #[test] 269 | fn named_fields_vec_option_tuple() { 270 | #[derive(Debug, PartialEq, StructuralConvert)] 271 | #[convert(try_into(Lhs))] 272 | struct Rhs { 273 | item: Vec>, 274 | } 275 | 276 | #[derive(Debug, PartialEq)] 277 | struct Lhs { 278 | item: Vec>, 279 | } 280 | 281 | assert_eq!( 282 | Lhs { 283 | item: vec![Some((1, 2))] 284 | }, 285 | Rhs { 286 | item: vec![Some((1, 2))] 287 | } 288 | .try_into() 289 | .unwrap() 290 | ) 291 | } 292 | #[test] 293 | fn named_fields_vec_tuple_option() { 294 | #[derive(Debug, PartialEq, StructuralConvert)] 295 | #[convert(try_into(Lhs))] 296 | struct Rhs { 297 | item: Vec<(Option, Option)>, 298 | } 299 | 300 | #[derive(Debug, PartialEq)] 301 | struct Lhs { 302 | item: Vec<(Option, Option)>, 303 | } 304 | 305 | assert_eq!( 306 | Lhs { 307 | item: vec![(Some(1), Some(1))] 308 | }, 309 | Rhs { 310 | item: vec![(Some(1), Some(1))] 311 | } 312 | .try_into() 313 | .unwrap() 314 | ) 315 | } 316 | #[test] 317 | fn named_fields_tuple_vec_option() { 318 | #[derive(Debug, PartialEq, StructuralConvert)] 319 | #[convert(try_into(Lhs))] 320 | struct Rhs { 321 | item: (Vec>, Vec>), 322 | } 323 | 324 | #[derive(Debug, PartialEq)] 325 | struct Lhs { 326 | item: (Vec>, Vec>), 327 | } 328 | 329 | assert_eq!( 330 | Lhs { 331 | item: (vec![Some(1)], vec![Some(1)]) 332 | }, 333 | Rhs { 334 | item: (vec![Some(1)], vec![Some(1)]) 335 | } 336 | .try_into() 337 | .unwrap() 338 | ) 339 | } 340 | #[test] 341 | fn named_fields_result() { 342 | #[derive(Debug, PartialEq, StructuralConvert)] 343 | #[convert(try_into(Lhs))] 344 | struct Rhs { 345 | item: Result, 346 | } 347 | 348 | #[derive(Debug, PartialEq)] 349 | struct Lhs { 350 | item: Result, 351 | } 352 | 353 | assert_eq!(Lhs { item: Ok(1) }, Rhs { item: Ok(1) }.try_into().unwrap()); 354 | assert_eq!( 355 | Lhs { item: Err(1) }, 356 | Rhs { item: Err(1) }.try_into().unwrap() 357 | ); 358 | } 359 | #[test] 360 | fn named_fields_hash_map() { 361 | #[derive(Debug, PartialEq, StructuralConvert)] 362 | #[convert(try_into(Lhs))] 363 | struct Rhs { 364 | item: HashMap, 365 | } 366 | 367 | #[derive(Debug, PartialEq)] 368 | struct Lhs { 369 | item: HashMap, 370 | } 371 | 372 | let mut lhs = HashMap::new(); 373 | lhs.insert(1, 2); 374 | 375 | let mut rhs = HashMap::new(); 376 | rhs.insert(1, 2); 377 | 378 | assert_eq!(Lhs { item: lhs }, Rhs { item: rhs }.try_into().unwrap()); 379 | } 380 | -------------------------------------------------------------------------------- /tests/try_into_struct_option.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[derive(Debug, PartialEq)] 4 | struct Q(u32); 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_from(Q))] 7 | struct W(u32); 8 | 9 | #[test] 10 | fn fields_unnamed() { 11 | #[derive(Debug, PartialEq, StructuralConvert)] 12 | #[convert(try_into(Lhs))] 13 | struct Rhs(i8, Option); 14 | 15 | #[derive(Debug, PartialEq)] 16 | struct Lhs(i32, Option); 17 | 18 | assert_eq!(Lhs(1, Some(W(2))), Rhs(1, Some(Q(2))).try_into().unwrap()); 19 | } 20 | 21 | #[test] 22 | fn fields_named() { 23 | #[derive(Debug, PartialEq, StructuralConvert)] 24 | #[convert(try_into(Lhs))] 25 | struct Rhs { 26 | z: i8, 27 | x: Option, 28 | } 29 | 30 | #[derive(Debug, PartialEq)] 31 | struct Lhs { 32 | z: i32, 33 | x: Option, 34 | } 35 | 36 | assert_eq!( 37 | Lhs { 38 | z: 1, 39 | x: Some(W(2)) 40 | }, 41 | Rhs { 42 | z: 1, 43 | x: Some(Q(2)) 44 | } 45 | .try_into() 46 | .unwrap() 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /tests/try_into_struct_skip.rs: -------------------------------------------------------------------------------- 1 | use structural_convert::StructuralConvert; 2 | 3 | #[test] 4 | fn fields_unnamed() { 5 | #[derive(Debug, PartialEq, StructuralConvert)] 6 | #[convert(try_into(Lhs, skip_after = 2))] 7 | struct Rhs(i8, u32, u8); 8 | 9 | #[derive(Debug, PartialEq)] 10 | struct Lhs(i32, u32); 11 | 12 | assert_eq!(Lhs(1, 2), Rhs(1, 2, 3).try_into().unwrap()); 13 | } 14 | 15 | #[test] 16 | fn fields_named() { 17 | #[derive(Debug, PartialEq, StructuralConvert)] 18 | #[convert(try_into(Lhs))] 19 | struct Rhs { 20 | z: i8, 21 | x: u32, 22 | #[convert(try_into(Lhs, skip))] 23 | y: u8, 24 | } 25 | 26 | #[derive(Debug, PartialEq)] 27 | struct Lhs { 28 | z: i32, 29 | x: u32, 30 | } 31 | 32 | assert_eq!( 33 | Lhs { z: 1, x: 2 }, 34 | Rhs { z: 1, x: 2, y: 3 }.try_into().unwrap() 35 | ); 36 | } 37 | --------------------------------------------------------------------------------