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>);