1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! DRF-style serializer layer — typed JSON output from model instances.
//!
//! A serializer is a Rust struct that maps a [`Model`] instance to a
//! JSON-ready shape, with per-field control over what is included,
//! renamed, or excluded.
//!
//! ## Quick start
//!
//! ```ignore
//! use rustango::Serializer;
//! use rustango::serializer::ModelSerializer;
//!
//! #[derive(Serializer, serde::Deserialize, Default)]
//! #[serializer(model = Post)]
//! pub struct PostSerializer {
//! pub id: i64,
//! pub title: String,
//! #[serializer(read_only)]
//! pub created_at: chrono::DateTime<chrono::Utc>,
//! #[serializer(write_only)]
//! pub secret: String,
//! #[serializer(source = "body")]
//! pub content: String,
//! #[serializer(skip)]
//! pub tag_ids: Vec<i64>, // set manually: s.tag_ids = post.tags_m2m().all(&pool).await?
//! }
//!
//! // Serialize:
//! let s = PostSerializer::from_model(&post);
//! let json = s.to_value();
//!
//! // Serialize many:
//! let json_array = PostSerializer::many_to_value(&posts);
//! ```
//!
//! ## Field attributes
//!
//! | Attribute | Effect on `from_model` | Effect on JSON output | Effect on `writable_fields` |
//! |---|---|---|---|
//! | *(none)* | mapped from model | included | yes |
//! | `read_only` | mapped from model | included | no |
//! | `write_only` | `Default::default()` | excluded | yes |
//! | `source = "x"` | mapped from `model.x` | included | yes |
//! | `skip` | `Default::default()` | included | no |
//!
//! ## Nested serializers
//!
//! For v0.20.x, nested objects require `#[serializer(skip)]` — set the field
//! manually after calling `from_model`:
//!
//! ```ignore
//! let mut s = PostSerializer::from_model(&post);
//! s.author = AuthorSerializer::from_model(post.author.value().expect("loaded"));
//! ```
//!
//! Full automatic nested support (`#[serializer(nested)]` with FK loading) is
//! planned for a future slice.
//!
//! ## Validation
//!
//! Add cross-field validation as an inherent method on the serializer struct:
//!
//! ```ignore
//! impl PostSerializer {
//! pub fn validate(&self) -> Result<(), rustango::forms::FormErrors> {
//! let mut errors = rustango::forms::FormErrors::default();
//! if self.title.is_empty() {
//! errors.add("title", "title cannot be empty");
//! }
//! if errors.is_empty() { Ok(()) } else { Err(errors) }
//! }
//! }
//! ```
use Value;
/// Core serializer trait. Implemented by `#[derive(Serializer)]` structs.
///
/// # Required implementations
///
/// The derive macro generates:
/// - `from_model` — maps a model instance to the serializer struct
/// - `writable_fields` — field names accepted on create/update (excludes `read_only` and `skip`)
///
/// # Default implementations
///
/// - `to_value` — calls `serde::Serialize` (which the macro also emits, skipping `write_only` fields)
/// - `many` / `many_to_value` — batch `from_model` calls
/// - `validate` — no-op; override to add cross-field validation