just_convert/
lib.rs

1//! just-convert make easier to convert between structs.
2//!
3//! This crate provides JustConvert derive macro.
4//!
5//! Example of use:
6//!
7//! ```rust
8//! # use just_convert::JustConvert;
9//! #
10//! // Allow convert A struct into B struct
11//! #[derive(JustConvert)]
12//! #[convert(into(B))]
13//! struct A {
14//!     // field can be renamed
15//!     #[convert(rename = bid)]
16//!     id: i64,
17//!
18//!     // field can execute any expression
19//!     #[convert(map = ".to_string()")]
20//!     num: i64,
21//!
22//!     // unwrap Option value for B::name
23//!     #[convert(unwrap)]
24//!     name: Option<String>,
25//! }
26//!
27//! struct B {
28//!     bid: i64,
29//!     num: String,
30//!     name: String,
31//! }
32//! ```
33//!
34//! See more [examples](https://github.com/vettich/just-convert-rs/tree/main/examples)
35
36use std::collections::HashMap;
37
38use parse::parse_params;
39use proc_macro::TokenStream;
40use syn::{parse_macro_input, DeriveInput, Ident, Path};
41
42mod build;
43mod map;
44mod parse;
45
46#[proc_macro_derive(JustConvert, attributes(convert))]
47pub fn just_convert_derive(input: TokenStream) -> TokenStream {
48    let input = parse_macro_input!(input as DeriveInput);
49    build_impl(input).into()
50}
51
52fn build_impl(input: DeriveInput) -> proc_macro2::TokenStream {
53    let params = match parse_params(&input) {
54        Ok(p) => p,
55        Err(err) => return err.to_compile_error(),
56    };
57
58    params
59        .build()
60        .unwrap_or_else(syn::Error::into_compile_error)
61}
62
63#[derive(Debug)]
64struct Params {
65    name: Ident,
66    from: Vec<PathParams>,
67    into: Vec<PathParams>,
68    fields: Fields,
69}
70
71#[derive(Debug)]
72struct PathParams {
73    path: Path,
74    default: bool,
75    wrap_option: bool,
76}
77
78#[derive(Debug, Clone)]
79struct FieldParams {
80    map: FieldValue<proc_macro2::Literal>,
81    rename: FieldValue<Ident>,
82    wrap: FieldValue<bool>,
83    unwrap: FieldValue<bool>,
84    skip: FieldValue<bool>,
85    a_type: AdditionalType,
86}
87
88impl FieldParams {
89    fn new() -> Self {
90        Self {
91            map: FieldValue::new(),
92            rename: FieldValue::new(),
93            wrap: FieldValue::new(),
94            unwrap: FieldValue::new(),
95            skip: FieldValue::new(),
96            a_type: AdditionalType::None,
97        }
98    }
99}
100
101#[derive(Debug, Default, Clone)]
102struct FieldValue<T> {
103    common: Option<T>,
104    common_from: Option<T>,
105    common_into: Option<T>,
106    from: HashMap<Path, T>,
107    into: HashMap<Path, T>,
108}
109
110impl<T> FieldValue<T> {
111    fn new() -> Self {
112        Self {
113            common: None,
114            common_from: None,
115            common_into: None,
116            from: [].into(),
117            into: [].into(),
118        }
119    }
120
121    fn set_from(&mut self, path: Option<Path>, value: T) {
122        if let Some(path) = path {
123            self.from.insert(path, value);
124        } else {
125            self.common_from = Some(value);
126        }
127    }
128
129    fn set_into(&mut self, path: Option<Path>, value: T) {
130        if let Some(path) = path {
131            self.into.insert(path, value);
132        } else {
133            self.common_into = Some(value);
134        }
135    }
136}
137
138#[derive(Debug, Default, Clone, Copy)]
139enum AdditionalType {
140    #[default]
141    None,
142    /// Option<T>
143    Option,
144    /// Option<Vec<T>>
145    OptionVec,
146    /// Vec<T>
147    Vec,
148    /// Vec<Option<T>>
149    VecOption,
150}
151
152impl AdditionalType {
153    fn is_option(self) -> bool {
154        matches!(self, Self::Option)
155    }
156
157    fn is_option_vec(self) -> bool {
158        matches!(self, Self::OptionVec)
159    }
160
161    fn is_vec(self) -> bool {
162        matches!(self, Self::Vec)
163    }
164
165    fn is_vec_option(self) -> bool {
166        matches!(self, Self::VecOption)
167    }
168}
169
170type Fields = HashMap<Ident, FieldParams>;