reinhardt_rest/serializers/relations.rs
1//! Relation fields - Django REST Framework inspired relationship serialization
2//!
3//! This module provides field types for representing relationships between models
4//! in serialized output, supporting various representation strategies.
5
6use serde::{Deserialize, Serialize};
7use std::marker::PhantomData;
8
9/// RelationField - Base trait for relationship representation
10///
11/// Defines the common interface for all relationship field types.
12/// Different field types represent the same relationship in different ways.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct RelationField<T> {
15 _phantom: PhantomData<T>,
16}
17
18impl<T> RelationField<T> {
19 /// Create a new RelationField
20 pub fn new() -> Self {
21 Self {
22 _phantom: PhantomData,
23 }
24 }
25}
26
27impl<T> Default for RelationField<T> {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33/// PrimaryKeyRelatedField - Represent relationships by primary key
34///
35/// This is the most common and efficient way to represent relationships,
36/// storing only the primary key of the related model.
37///
38/// # Examples
39///
40/// ```
41/// # use reinhardt_rest::serializers::PrimaryKeyRelatedField;
42/// # use serde::{Serialize, Deserialize};
43/// #
44/// # #[derive(Debug, Clone, Serialize, Deserialize)]
45/// # struct Author {
46/// # id: Option<i64>,
47/// # name: String,
48/// # }
49/// #
50/// # #[derive(Debug, Clone, Serialize, Deserialize)]
51/// # struct Post {
52/// # id: Option<i64>,
53/// # title: String,
54/// # author: PrimaryKeyRelatedField<Author>,
55/// # }
56/// // In JSON, the author field will be represented as just the ID:
57/// // {"id": 1, "title": "My Post", "author": 42}
58/// ```
59pub type PrimaryKeyRelatedField<T> = RelationField<T>;
60
61/// SlugRelatedField - Represent relationships by a slug field
62///
63/// Uses a unique text field (slug) instead of the primary key to represent
64/// the relationship. Useful for human-readable URLs and references.
65///
66/// # Examples
67///
68/// ```
69/// # use reinhardt_rest::serializers::SlugRelatedField;
70/// # use serde::{Serialize, Deserialize};
71/// #
72/// # #[derive(Debug, Clone, Serialize, Deserialize)]
73/// # struct Category {
74/// # id: Option<i64>,
75/// # slug: String,
76/// # name: String,
77/// # }
78/// #
79/// # #[derive(Debug, Clone, Serialize, Deserialize)]
80/// # struct Product {
81/// # id: Option<i64>,
82/// # name: String,
83/// # category: SlugRelatedField<Category>,
84/// # }
85/// // In JSON, the category field will be represented as a slug:
86/// // {"id": 1, "name": "Widget", "category": "electronics"}
87/// ```
88pub type SlugRelatedField<T> = RelationField<T>;
89
90/// StringRelatedField - Represent relationships by string representation
91///
92/// Uses the string representation of the related model (typically from
93/// Display trait or a custom method). Read-only field type.
94///
95/// # Examples
96///
97/// ```
98/// # use reinhardt_rest::serializers::StringRelatedField;
99/// # use serde::{Serialize, Deserialize};
100/// #
101/// # #[derive(Debug, Clone, Serialize, Deserialize)]
102/// # struct User {
103/// # id: Option<i64>,
104/// # username: String,
105/// # }
106/// #
107/// # #[derive(Debug, Clone, Serialize, Deserialize)]
108/// # struct Comment {
109/// # id: Option<i64>,
110/// # text: String,
111/// # author: StringRelatedField<User>,
112/// # }
113/// // In JSON, the author field will be represented as a string:
114/// // {"id": 1, "text": "Great post!", "author": "john_doe"}
115/// ```
116pub type StringRelatedField<T> = RelationField<T>;
117
118/// HyperlinkedRelatedField - Represent relationships by URL
119///
120/// Stores a URL that points to the detail view of the related model,
121/// following HATEOAS principles.
122///
123/// # Examples
124///
125/// ```
126/// # use reinhardt_rest::serializers::HyperlinkedRelatedField;
127/// # use serde::{Serialize, Deserialize};
128/// #
129/// # #[derive(Debug, Clone, Serialize, Deserialize)]
130/// # struct Author {
131/// # id: Option<i64>,
132/// # name: String,
133/// # }
134/// #
135/// # #[derive(Debug, Clone, Serialize, Deserialize)]
136/// # struct Book {
137/// # id: Option<i64>,
138/// # title: String,
139/// # author: HyperlinkedRelatedField<Author>,
140/// # }
141/// // In JSON, the author field will be represented as a URL:
142/// // {"id": 1, "title": "My Book", "author": "/api/authors/42/"}
143/// ```
144pub type HyperlinkedRelatedField<T> = RelationField<T>;
145
146/// ManyRelatedField - Represent many-to-many or reverse relationships
147///
148/// Wrapper for collections of related objects, can use any of the above
149/// field types for the individual items.
150///
151/// # Examples
152///
153/// ```
154/// # use reinhardt_rest::serializers::ManyRelatedField;
155/// # use serde::{Serialize, Deserialize};
156/// #
157/// # #[derive(Debug, Clone, Serialize, Deserialize)]
158/// # struct Tag {
159/// # id: Option<i64>,
160/// # name: String,
161/// # }
162/// #
163/// # #[derive(Debug, Clone, Serialize, Deserialize)]
164/// # struct Article {
165/// # id: Option<i64>,
166/// # title: String,
167/// # tags: ManyRelatedField<Tag>,
168/// # }
169/// // In JSON, the tags field will be an array:
170/// // {"id": 1, "title": "My Article", "tags": [1, 2, 3]}
171/// // or with SlugRelatedField:
172/// // {"id": 1, "title": "My Article", "tags": ["rust", "programming", "web"]}
173/// ```
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct ManyRelatedField<T> {
176 _phantom: PhantomData<T>,
177}
178
179impl<T> ManyRelatedField<T> {
180 /// Create a new ManyRelatedField
181 pub fn new() -> Self {
182 Self {
183 _phantom: PhantomData,
184 }
185 }
186}
187
188impl<T> Default for ManyRelatedField<T> {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194/// IdentityField - Returns the entire related object
195///
196/// Used internally by NestedSerializer to represent the entire related
197/// object instead of just a reference.
198///
199/// # Examples
200///
201/// ```
202/// # use reinhardt_rest::serializers::IdentityField;
203/// # use serde::{Serialize, Deserialize};
204/// #
205/// # #[derive(Debug, Clone, Serialize, Deserialize)]
206/// # struct Profile {
207/// # id: Option<i64>,
208/// # bio: String,
209/// # }
210/// #
211/// # #[derive(Debug, Clone, Serialize, Deserialize)]
212/// # struct User {
213/// # id: Option<i64>,
214/// # username: String,
215/// # profile: IdentityField<Profile>,
216/// # }
217/// // In JSON, the profile field will be the entire object:
218/// // {"id": 1, "username": "alice", "profile": {"id": 1, "bio": "Developer"}}
219/// ```
220pub type IdentityField<T> = RelationField<T>;
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
227 struct TestRelated {
228 id: Option<i64>,
229 name: String,
230 }
231
232 #[test]
233 fn test_relation_field_serialization() {
234 let field = RelationField::<TestRelated>::new();
235 let json = serde_json::to_string(&field).unwrap();
236 assert!(!json.is_empty());
237 }
238
239 #[test]
240 fn test_many_related_field_serialization() {
241 let field = ManyRelatedField::<TestRelated>::new();
242 let json = serde_json::to_string(&field).unwrap();
243 assert!(!json.is_empty());
244 }
245}