model_views/lib.rs
1//! Type-safe view types for different access modes on data models.
2//!
3//! This crate provides a trait-based system for generating specialized view types
4//! for different operations on data models (Get/Read, Create, Patch/Update). Each
5//! view type only includes the fields relevant to that operation, enforcing API
6//! contracts at compile time.
7//!
8//! # Core Concepts
9//!
10//! ## View Modes
11//!
12//! The crate defines three access modes for models:
13//!
14//! - **[`ViewModeGet`]**: For read operations, retrieving existing data
15//! - **[`ViewModeCreate`]**: For create operations, accepting input to create new entities
16//! - **[`ViewModePatch`]**: For update operations, allowing partial modifications
17//!
18//! ## The View Trait
19//!
20//! The [`View`] trait associates a type with its representation in a specific mode:
21//!
22//! ```rust,ignore
23//! trait View<M: ViewMode> {
24//! type Type;
25//! }
26//! ```
27//!
28//! This allows the same model to have different representations depending on the operation.
29//!
30//! ## Patch Type
31//!
32//! The [`Patch<T>`] enum is central to update operations, providing an explicit way to
33//! distinguish between "don't update this field" and "update this field to a value":
34//!
35//! - `Patch::Ignore`: Leave the field unchanged
36//! - `Patch::Update(value)`: Update the field to the given value
37//!
38//! This is clearer than using `Option<T>` for updates, especially when dealing with
39//! optional fields.
40//!
41//! # Usage
42//!
43//! ## Basic Example
44//!
45//! ```rust
46//! use model_views::{Views, Patch};
47//!
48//! #[derive(Views)]
49//! #[views(serde)]
50//! struct User {
51//! // ID is returned when reading, but can't be set during create/update
52//! #[views(get = "required", create = "forbidden", patch = "forbidden")]
53//! id: u64,
54//!
55//! // Name is always required for all operations
56//! #[views(get = "required", create = "required", patch = "required")]
57//! name: String,
58//!
59//! // Email is optional everywhere
60//! #[views(get = "optional", create = "optional", patch = "optional")]
61//! email: String,
62//! }
63//!
64//! // This generates three types:
65//!
66//! // UserGet - for reading user data
67//! let user_get = UserGet {
68//! id: 1,
69//! name: "Alice".to_string(),
70//! email: Some("alice@example.com".to_string()),
71//! };
72//!
73//! // UserCreate - for creating new users
74//! let user_create = UserCreate {
75//! name: "Bob".to_string(),
76//! email: Some("bob@example.com".to_string()),
77//! };
78//!
79//! // UserPatch - for updating users
80//! let user_patch = UserPatch {
81//! name: Patch::Update("Charlie".to_string()), // Update the name
82//! email: Patch::Ignore, // Don't change email
83//! };
84//! ```
85//!
86//! ## Nested Models
87//!
88//! Views work seamlessly with nested structures:
89//!
90//! ```rust
91//! # use model_views::{Views, Patch};
92//! # #[derive(Views)]
93//! # #[views(serde)]
94//! # struct User {
95//! # // ID is returned when reading, but can't be set during create/update
96//! # #[views(get = "required", create = "forbidden", patch = "forbidden")]
97//! # id: u64,
98//! #
99//! # // Name is always required for all operations
100//! # #[views(get = "required", create = "required", patch = "required")]
101//! # name: String,
102//! #
103//! # // Email is optional everywhere
104//! # #[views(get = "optional", create = "optional", patch = "optional")]
105//! # email: String,
106//! # }
107//! #[derive(Views)]
108//! struct Post {
109//! #[views(get = "required", create = "forbidden", patch = "forbidden")]
110//! id: u64,
111//!
112//! #[views(get = "required")]
113//! title: String,
114//!
115//! // Nested models are automatically handled
116//! #[views(get = "required", create = "forbidden", patch = "optional")]
117//! author: User,
118//! }
119//!
120//! // PostPatch will have: author: Patch<Option<UserPatch>>
121//! let post_patch = PostPatch {
122//! title: Patch::Update("New Title".to_string()),
123//! author: Patch::Update(Some(UserPatch {
124//! name: Patch::Update("New Author Name".to_string()),
125//! email: Patch::Ignore,
126//! })),
127//! };
128//! ```
129//!
130//! ## Field Policies
131//!
132//! Each field can be configured independently for each view mode:
133//!
134//! - `get = "required"`: Field is always present in Get view
135//! - `get = "optional"`: Field is `Option<T>` in Get view
136//! - `get = "forbidden"`: Field is excluded from Get view
137//!
138//! - `create = "required"`: Field must be provided when creating
139//! - `create = "optional"`: Field is `Option<T>` in Create view
140//! - `create = "forbidden"`: Field cannot be set during creation
141//!
142//! - `patch = "patch"`: Field is `Patch<T>` in Patch view
143//! - `patch = "optional"`: Field is `Patch<Option<T>>` in Patch view
144//! - `patch = "forbidden"`: Field cannot be modified via patches
145//!
146//! # Features
147//!
148//! - **`derive`** (default): Enables the `#[derive(Views)]` procedural macro
149//! - **`serde`**: Adds `Serialize`/`Deserialize` support for `Patch<T>`
150//! - **`uuid`**: Implements `View` for `uuid::Uuid`
151//! - **`chrono`**: Implements `View` for `chrono::DateTime<Utc>`
152//!
153//! # Benefits
154//!
155//! - **Type Safety**: Different operations use different types, catching errors at compile time
156//! - **API Clarity**: View types clearly document which fields are required/optional for each operation
157//! - **Reduced Boilerplate**: Automatically generates DTOs (Data Transfer Objects) from models
158//! - **Explicit Updates**: `Patch<T>` makes update intent clear, avoiding ambiguity with `Option<T>`
159
160#![forbid(unsafe_code)]
161
162mod patch;
163
164pub use patch::*;
165
166#[cfg(feature = "derive")]
167pub use model_views_derive::Views;
168
169pub trait View<M: ViewMode> {
170 type Type;
171}
172
173/// Access mode for a model.
174pub trait ViewMode {}
175
176/// Read access for a model.
177pub struct ViewModeGet;
178impl ViewMode for ViewModeGet {}
179
180/// Create access for a model.
181pub struct ViewModeCreate;
182impl ViewMode for ViewModeCreate {}
183
184/// Update/Write access for a model.
185pub struct ViewModePatch;
186impl ViewMode for ViewModePatch {}
187
188// Trivials just map to themselves for any mode
189macro_rules! trivial_view {
190 ($($t:ty),* $(,)?) => {$(
191 impl<M: $crate::ViewMode> $crate::View<M> for $t { type Type = $t; }
192 )*}
193}
194
195trivial_view!(
196 bool,
197 i8,
198 u8,
199 i16,
200 u16,
201 i32,
202 u32,
203 i64,
204 u64,
205 i128,
206 u128,
207 f32,
208 f64,
209 String,
210 &'static str
211);
212
213#[cfg(feature = "uuid")]
214trivial_view!(uuid::Uuid);
215
216#[cfg(feature = "chrono")]
217trivial_view!(chrono::DateTime<chrono::Utc>);