Skip to main content

from_similar/
lib.rs

1#![deny(unsafe_code)]
2#![deny(missing_docs)]
3#![allow(clippy::tabs_in_doc_comments)]
4
5//! `FromSimilar` automatically implements [`From`] between two structs that are "similar".
6//!
7//! Specifically for structs where the *fields have the same names*.
8//! Or tuple structs with similar positional arguments.
9//!
10//! This macro is mainly useful to generate predicable `From` implementations for:
11//! - Structs that are *mostly* identical, except for a few attributes like `#[serde]` exceptions for serializing BSON.
12//! - Structs with a subset of fields from the more complete one.
13//!
14//! ### Struct attributes
15//!
16//! `#[from(InputType)]` a **required attribute** to specify the input type.<br>
17//! Will generate `impl From<InputType> for T`.
18//!
19//! `#[from(.., bidirectional = true)]` optional attribute to implement both directions.<br>
20//! Will generate `impl From<InputType> for T` and `impl From<T> for InputType`.
21//!
22//! ### Field attributes
23//!
24//! `#[use_into]` is an optional *field attribute* to use `.into()` when converting this field.
25//!
26//! `#[use_into_option]` for `Option<T>` types that need a `.map(Into::into)` when converting this field.
27//!
28//! `#[use_into_collection]` for `impl IntoIterator<T>` types that should map each item and collect.
29//!
30//! ## Example with database models
31//!
32//! A bidirectional FromSimilar that can be used for MongoDB.
33//!
34//! ```rust
35//! use from_similar::FromSimilar;
36//!
37//! #[derive(Default)]
38//! struct NormalModel {
39//!     id: String,
40//!     date: chrono::DateTime<chrono::Utc>,
41//!     date_option: Option<chrono::DateTime<chrono::Utc>>,
42//!     date_list: Vec<chrono::DateTime<chrono::Utc>>,
43//! }
44//!
45//! #[derive(FromSimilar, serde::Serialize, serde::Deserialize)]
46//! #[from(NormalModel, bidirectional = true)]
47//! struct DatabaseModel {
48//!     #[serde(rename = "_id")]
49//!     id: String,
50//!
51//!     #[use_into]
52//!     date: bson::DateTime,
53//!
54//!     #[use_into_option]
55//!     date_option: Option<bson::DateTime>,
56//!
57//!     #[use_into_collection]
58//!     date_list: Vec<bson::DateTime>,
59//! }
60//!
61//! let normal = NormalModel::default();
62//! let db: DatabaseModel = normal.into();
63//! let _: NormalModel = db.into();
64//! ```
65//!
66//! ### Example with views
67//!
68//! Note: `#[from(.., bidirectional = true)]` would break here, because it's a lossy conversion.
69//!
70//! ```rust
71//! use from_similar::FromSimilar;
72//!
73//! #[derive(Default)]
74//! struct FullModel {
75//!     id: String,
76//!     pretty_name: String,
77//!     secret: String,
78//! }
79//!
80//! #[derive(FromSimilar)]
81//! #[from(FullModel)]
82//! struct PublicView {
83//!     id: String,
84//!     pretty_name: String,
85//!     // ... omits `secret` field
86//! }
87//!
88//! let full = FullModel::default();
89//! let _: PublicView = full.into();
90//! ```
91//!
92//! ### Example with tuple struct
93//!
94//! ```rust
95//! use from_similar::FromSimilar;
96//!
97//! #[derive(Default)]
98//! struct Data(pub String, pub usize);
99//!
100//! #[derive(FromSimilar)]
101//! #[from(Data)]
102//! struct SealedData(#[use_into] std::sync::Arc<str>, usize);
103//!
104//! let mut data = Data::default();
105//! data.0 += "Pushing ";
106//! data.0 += "some text";
107//! data.1 = 42;
108//! let data: SealedData = data.into();
109//! ```
110
111extern crate proc_macro;
112use proc_macro::TokenStream;
113use syn::{parse_macro_input, DeriveInput};
114
115mod from_similar;
116
117/// [`FromSimilar`] derive macro.
118///
119/// Typical usage means adding the Derive macro, setting the source with
120/// `#[from(SourceType)]` and using field macros as needed.
121///
122/// - `#[use_into]` for types that need direct `into` calls.
123/// - `#[use_into_option]` for `Option<T>` types that need a map-into.
124/// - `#[use_into_collection]` for `impl IntoIterator<T>` types that should map each item and collect.
125#[proc_macro_derive(
126	FromSimilar,
127	attributes(from, use_into, use_into_option, use_into_collection)
128)]
129pub fn from_similar(input: TokenStream) -> TokenStream {
130	let ty = parse_macro_input!(input as DeriveInput);
131	from_similar::expand_from_similar(ty)
132		.unwrap_or_else(syn::Error::into_compile_error)
133		.into()
134}