microtype/lib.rs
1#![warn(clippy::all)]
2#![warn(rustdoc::all)]
3#![no_std]
4
5//! A library to generate "microtypes" (A.K.A. "newtypes"). Opinionated in favour of ergonomics
6//! over maximum flexibility.
7//!
8//! A microtype is a thin wrapper around an underlying type, that helps disambiguate similar uses
9//! of the same type
10//!
11//! For example, consider the following code from an imaginary e-commerce web backend:
12//! ```
13//! # #[macro_use]
14//! # extern crate microtype;
15//! # fn retrieve_user_id() -> String { "".into() }
16//! # fn retrieve_order_id() -> String { "".into() }
17//! fn handle_order(user_id: String, order_id: String) {
18//! // ...
19//! }
20//!
21//! fn main() {
22//! let user_id = retrieve_user_id();
23//! let order_id = retrieve_order_id();
24//!
25//! handle_order(order_id, user_id);
26//! }
27//! ```
28//!
29//! This code compiles, but has a bug: the `order_id` and `user_id` are used in the wrong order.
30//! This example is fairly trivial and easy to spot, but the larger a project gets, the harder it
31//! becomes to detect these issues. This becomes especially troublesome if you want to refactor. For
32//! example, if you wanted to swap the order of the arguments, you'd have to make sure you visited
33//! all the calls to this function and swapped the arguments manually. Luckily, we can get the
34//! compiler to help with this.
35//!
36//! Microtypes solve this problem. They wrap some inner type, and allow the compiler to distinguish
37//! between different uses of the same underlying type. For example, we could rewrite the earlier
38//! example as:
39//!
40//! ```compile_fail
41//! # #[macro_use]
42//! # extern crate microtype;
43//! # fn retrieve_user_id() -> String { "".into() }
44//! # fn retrieve_order_id() -> String { "".into() }
45//! // Generate wrappers around String called UserId and OrderId
46//! microtype! {
47//! String {
48//! UserId,
49//! OrderId,
50//! }
51//! }
52//!
53//! fn handle_order(user_id: UserId, order_id: OrderId) {
54//! // ...
55//! }
56//!
57//! fn main() {
58//! let user_id: OrderId = retrieve_user_id();
59//! let order_id: UserId = retrieve_order_id();
60//!
61//! handle_order(order_id, user_id); // Error: incompatible types
62//! }
63//! ```
64//! Excellent, a run-time error has been turned into a compile time error.
65//!
66//! ## Basic usage
67//!
68//! To declare microtypes, use the ['microtype::microtype'] macro:
69//! ```
70//! # #[macro_use]
71//! # extern crate microtype;
72//! # use microtype::Microtype;
73//! microtype! {
74//! // these attributes apply to all microtypes defined in this block
75//! #[derive(Debug, Clone)]
76//! String {
77//! #[derive(PartialEq)]
78//! UserId, // implements Debug, Clone and PartialEq
79//!
80//! Username, // only implements Debug and Clone
81//! }
82//!
83//! // multiple inner types can be used in a single macro
84//! i64 {
85//! Timestamp
86//! }
87//!
88//! // use the `#[secret]` attribute to mark a type as "secret"
89//! #[secret]
90//! String {
91//! Password
92//! }
93//!
94//! // use `#[secret(serialize)]` to make a secret type implement serde::Serialize
95//! String {
96//! SessionToken
97//! }
98//! }
99//!
100//! fn main() {
101//! let user_id = UserId::new("id".into()); // create new UserId
102//! let string = user_id.into_inner(); // consume UserId, return inner String
103//! let username = Username::new(string); // create new Username
104//!
105//! // sometimes you need to explicitly change the type of a value:
106//! let user_id: UserId = username.convert();
107//!
108//! // microtypes also optionally implement Deref
109//! let length = user_id.len();
110//! assert_eq!(length, 2);
111//! }
112//! ```
113//!
114//!
115//! ## Secrets
116//!
117//! Some types may be considered "sensitive" (for example: passwords, session tokens, etc).
118//! For this purpose, microtypes can be marked as `#[secret]`:
119//! ```
120//! # use microtype::microtype;
121//! # use microtype::SecretMicrotype;
122//! # use microtype::secrecy::ExposeSecret;
123//! microtype! {
124//! #[secret]
125//! String {
126//! Password
127//! }
128//! }
129//!
130//! fn main() {
131//! let password = Password::new("secret password".to_string());
132//! assert_eq!(password.expose_secret(), "secret password");
133//! }
134//! ```
135//! Secret microtypes don't implement [`Microtype`], instead they implement
136//! [`SecretMicrotype`], which has a much more restrictive API:
137//! - Mutable and owned access to the inner data is not possible, it is only possible to get a
138//! shared reference to the inner data via [`secrecy::ExposeSecret::expose_secret`], which makes
139//! accesses easier to audit.
140//! - They `#[derive(Debug, Clone)]` (and optionally `Serialize` and `Deserialize`) but do not support adding extra derive macros.
141//!
142//! Internally, they wrap the contained data in [`secrecy::Secret`], which provides some nice
143//! safety features. In particular:
144//! - The debug representation is redacted. This is can prevent against accidentally leaking
145//! data to logs, but it still *has* a `Debug` implementation (so you can still
146//! `#[derive(Debug)]` on structs which contain secret data)
147//! - Data is zeroized after use, meaning the underlying data is overwritten with 0s, which
148//! ensures sensitive data exists in memory only for as long as is needed. (Caveat: not all types
149//! have perfect zeroize implementations. Notably `Vec` (and `String`) will not be able to zeroize
150//! previous allocations)
151//! - when using `serde`, secret microtypes do not implement `Serialize`, to avoid accidentally
152//! leaking secret data
153//!
154//! ## Serializable Secrets
155//!
156//! The fact that secret microtypes do not implement `Serialize` can be overly restrictive
157//! sometimes. There are many types (e.g. session tokens) which are sensitive enough to warrant
158//! redacting their debug implementation, but also need to be serialized. For types like this, you
159//! can use `#[secret(serialize)]` to make the type implement `Serialize`.
160//!
161//! ```ignore
162//! # use serde::Serialize;
163//! # use microtype::microtype;
164//! microtype! {
165//! #[secret(serialize)]
166//! String {
167//! SessionToken
168//! }
169//! }
170//!
171//! #[derive(Serialize)]
172//! struct LoginResponse {
173//! token: SessionToken
174//! }
175//! ```
176//!
177//! ## Type Hints
178//!
179//! Proc-macros are run before type information is available, so can only use the text of the
180//! invocation. Given that, a proc-macro can't distinguish between the `String` type provided by
181//! the standard library and a custom `struct String;`. You can use a type hint to mark a microtype
182//! as wrapping a well-known type, to generate more helpful implementations for you:
183//!
184//! - If the wrapped type is a `String`, you can use `#[string]` to provide a few extra
185//! implementations (e.g. `FromStr`, `From<&str>`, `Display`)
186//! - If the wrapped type is an integer type, you can use `#[int]` to provide other extra
187//! implementations: various `fmt` traits (e.g. `UpperHex`, etc), as well as arithmetic traits
188//! (`Add`, `AddAssign`, etc). These are incomplete, please open a PR/issue if there are implementations
189//! you rely on that are missing
190//!
191//! For example:
192//! ```
193//! # use microtype::*;
194//! microtype! {
195//! #[string]
196//! String {
197//! Email
198//! }
199//!
200//! #[int]
201//! i32 {
202//! Num
203//! }
204//! }
205//!
206//! fn main() {
207//! let email = Email::from("email");
208//! let num = Num::from(123);
209//!
210//! println!("{email}");
211//! println!("display: {num}, hex: {num:x}");
212//! }
213//! ```
214//!
215//!
216//! ## Feature flags
217//!
218//! The following feature flags are provided, to help customize the behaviour of the types creates:
219//! - `serde` - when enabled, any type created will derive `Serialize` and `Deserialize`, and will
220//! be `#[serde(transparent)]`
221//! - `deref_impls` - some people argue that implementing `Deref` and `DerefMut` on a non-pointer container is
222//! unidiomatic. Others prefer the ergonomics of being able to call associated functions more
223//! easily. If `deref_impls` is enabled, microtypes will deref to their inner types
224//! - `test_impls` - makes secret microtypes easier to work with in test environments by:
225//! - making their `Debug` implmentation print their actual value instead of `"REDACTED"`
226//! - making them derive `PartialEq`
227//! - `secret` - enables secret microtypes, discussed below:
228//! - `diesel` - if enabled, any attribtes of the form `#[diesel(sql_type = ...)]` will be
229//! captured, and `FromSql` and `ToSql` implementations will be generated. Note, you will
230//! generally also want to `#[derive(AsExpression, FromSqlRow)]`
231
232/* TRAIT DEFS */
233
234/// A trait implemented by microtypes
235///
236/// Provides some useful common functions for working with microtypes
237pub trait Microtype {
238 /// The type of the wrapped value
239 ///
240 /// For example, the inner type of an `EmailAddress` could be a `String`
241 type Inner;
242
243 /// Create a microtype from the inner value
244 fn new(inner: Self::Inner) -> Self;
245
246 /// Consume this microtype and return the value it contains
247 fn into_inner(self) -> Self::Inner;
248
249 /// Get a shared reference to the inner value
250 fn inner(&self) -> &Self::Inner;
251
252 /// Get a mutable reference to the inner value
253 fn inner_mut(&mut self) -> &mut Self::Inner;
254
255 /// Explicitly convert from one microtype to another.
256 ///
257 /// This exists as an alternative to `From`/`Into` implementations between different
258 /// microtypes to make conversions explicit
259 fn convert<T: Microtype<Inner = Self::Inner>>(self) -> T;
260}
261
262/// A trait implemented by secret microtypes
263///
264/// Due to their nature, secret microtypes are more restrictive than regular microtypes:
265/// - `inner`, `inner_mut` and `into_inner` are removed, since they can allow accidental use of
266/// the contained secret.
267/// - `SecretMicrotype` requires `ExposeSecret<Self::Inner>`; to use the contained data, use
268/// `.expose_secret()`
269///
270/// The wrapped type must also implement [`secrecy::Zeroize`]
271#[cfg(feature = "secret")]
272pub trait SecretMicrotype: secrecy::ExposeSecret<Self::Inner> {
273 /// The type of the wrapped value
274 /// For example, the inner type of a `Password` could be a `String`
275 type Inner: secrecy::Zeroize;
276
277 /// Create a microtype from the inner value
278 ///
279 /// Note that it is not possible to retrieve the owned value, it can only be read via shared
280 /// reference obtained via `expose_secret()`
281 fn new(inner: Self::Inner) -> Self;
282}
283
284pub use microtype_macro::microtype;
285#[cfg(feature = "secret")]
286pub use secrecy;
287
288#[cfg(test)]
289#[test]
290fn ui() {
291 let t = trybuild::TestCases::new();
292 t.compile_fail("tests/ui/fail/*.rs");
293 t.pass("tests/ui/pass/*.rs");
294
295 #[cfg(feature = "serde")]
296 t.pass("tests/ui/pass/serde/*.rs");
297 #[cfg(feature = "serde")]
298 t.compile_fail("tests/ui/fail/serde/*.rs");
299}