structural_typing/
lib.rs

1//! # Structural Typing for Rust
2//!
3//! Define a struct once and use it with different field combinations, tracked at compile time.
4//!
5//! ## Installation
6//!
7//! ```bash
8//! cargo add structural-typing
9//! ```
10//!
11//! If using derives on `#[structural]` structs, also add:
12//! ```bash
13//! cargo add derive-where
14//! ```
15//!
16//! ## Quick Example
17//!
18//! ```
19//! use structural_typing::{structural, presence::Present, select};
20//!
21//! #[structural]
22//! struct User { id: u32, name: String, email: String }
23//!
24//! type Create = select!(user: name, email);
25//!
26//! fn create_user(data: User<Create>) -> User {
27//!     data.id(generate_id())
28//! }
29//!
30//! fn update_user<F: user::Fields<id = Present>>(data: User<F>) {
31//!     if let Some(name) = data.get_name() {
32//!         update_in_db(data.id, name);
33//!     }
34//! }
35//! # fn generate_id() -> u32 { 42 }
36//! # fn update_in_db(_id: u32, _name: &String) {}
37//! ```
38//!
39//! ## Core Concepts
40//!
41//! - **Field States**: `Present`, `Optional`, `Absent`
42//! - **Builder API**: `.field(value)` infers presence from type
43//! - **Type Selection**: `select!` macro or type aliases
44//! - **Bounded Impls**: Require specific fields via trait bounds
45//! - **Merge and Extract**: Combine partial structs or extract field subsets
46//!
47//! For serde integration and other details, see the [`structural`] macro documentation.
48//!
49//! See the [examples](https://github.com/Crazytieguy/structural-typing-rs/tree/master/examples) directory for comprehensive usage.
50//!
51//! ## Constraints
52//!
53//! - Named structs only (not tuple structs or enums)
54//! - No generic parameters
55//! - At least one field required
56
57#![deny(missing_docs)]
58#![warn(clippy::pedantic)]
59
60/// Runtime field access trait for checking presence and getting values.
61pub mod access;
62/// Type-level presence markers and traits for field state tracking.
63pub mod presence;
64/// Traits for splitting structs into selected fields and remainder.
65pub mod extract;
66
67pub use structural_typing_macros::structural;
68
69/// Construct a `FieldSet` by selecting fields from a module.
70///
71/// # Syntax
72/// - `select!(module: field)` - field is Present
73/// - `select!(module: ?field)` - field is Optional
74/// - `select!(module: ?all)` - all fields Optional
75/// - Multiple fields compose: `select!(module: field1, ?field2)` expands to `module::with::field1::Present<module::with::field2::Optional>`
76///
77/// # Examples
78/// ```ignore
79/// type Create = select!(user: name, email);
80/// type Update = select!(user: ?name, ?email);
81/// fn create_user(user: User<Create>) {...}
82/// ```
83#[macro_export]
84macro_rules! select {
85    // Error: invalid + prefix
86    ($($module:ident)::+ : + $field:ident $($rest:tt)*) => {
87        compile_error!("Invalid prefix '+' in select!. Use 'field' for Present or '?field' for Optional")
88    };
89
90    // Error: invalid - prefix
91    ($($module:ident)::+ : - $field:ident $($rest:tt)*) => {
92        compile_error!("Invalid prefix '-' in select!. Use 'field' for Present or '?field' for Optional")
93    };
94
95    // Error: empty field list
96    ($($module:ident)::+ :) => {
97        compile_error!("select! requires at least one field")
98    };
99
100    // Single field: Present
101    ($($module:ident)::+ : $field:ident) => {
102        $($module)::+::with::$field::Present
103    };
104
105    // Single field: Present with trailing comma
106    ($($module:ident)::+ : $field:ident,) => {
107        $($module)::+::with::$field::Present
108    };
109
110    // Single field: Optional
111    ($($module:ident)::+ : ? $field:ident) => {
112        $($module)::+::with::$field::Optional
113    };
114
115    // Single field: Optional with trailing comma
116    ($($module:ident)::+ : ? $field:ident,) => {
117        $($module)::+::with::$field::Optional
118    };
119
120    // Multiple fields: Present + rest
121    ($($module:ident)::+ : $field:ident, $($rest:tt)+) => {
122        $($module)::+::with::$field::Present<select!($($module)::+ : $($rest)+)>
123    };
124
125    // Multiple fields: Optional + rest
126    ($($module:ident)::+ : ? $field:ident, $($rest:tt)+) => {
127        $($module)::+::with::$field::Optional<select!($($module)::+ : $($rest)+)>
128    };
129}