1use std::collections::HashMap;
2
3use serde_json::Value;
4
5use crate::{AttributeError, Attributes, Conversion, Errors, ModelNaming, Serialization};
6
7pub trait Model:
9 Attributes + ModelNaming + Conversion + Serialization + Sized + Send + Sync
10{
11 fn new() -> Self;
13
14 fn initialize(attrs: HashMap<String, Value>) -> Result<Self, AttributeError> {
16 let mut model = Self::new();
17 model.assign_attributes(attrs)?;
18 Ok(model)
19 }
20
21 fn is_valid(&self) -> bool {
23 true
24 }
25
26 fn is_invalid(&self) -> bool {
28 !self.is_valid()
29 }
30
31 fn errors(&self) -> &Errors;
33
34 fn errors_mut(&mut self) -> &mut Errors;
36
37 fn validate(&mut self) -> bool {
39 self.errors_mut().clear();
40 true
41 }
42}
43
44#[cfg(test)]
45mod tests {
46 use std::collections::HashMap;
47
48 use serde_json::{Value, json};
49
50 use super::Model;
51 use crate::{
52 AttributeError, Attributes, Conversion, ErrorType, Errors, ModelNaming, Serialization,
53 };
54
55 #[derive(Debug, Default, Clone)]
56 struct TestUser {
57 id: Option<u64>,
58 name: String,
59 errors: Errors,
60 }
61
62 impl Attributes for TestUser {
63 fn attribute_names() -> &'static [&'static str] {
64 &["id", "name"]
65 }
66
67 fn read_attribute(&self, name: &str) -> Option<Value> {
68 match name {
69 "id" => Some(self.id.map_or(Value::Null, Value::from)),
70 "name" => Some(Value::String(self.name.clone())),
71 _ => None,
72 }
73 }
74
75 fn write_attribute(&mut self, name: &str, value: Value) -> Result<(), AttributeError> {
76 match (name, value) {
77 ("id", Value::Null) => {
78 self.id = None;
79 Ok(())
80 }
81 ("id", Value::Number(number)) => {
82 let id = number
83 .as_u64()
84 .ok_or_else(|| AttributeError::TypeMismatch {
85 attribute: "id".to_string(),
86 expected: "u64".to_string(),
87 actual: "number".to_string(),
88 })?;
89 self.id = Some(id);
90 Ok(())
91 }
92 ("name", Value::String(name)) => {
93 self.name = name;
94 Ok(())
95 }
96 ("id", other) => Err(AttributeError::TypeMismatch {
97 attribute: "id".to_string(),
98 expected: "u64".to_string(),
99 actual: other.to_string(),
100 }),
101 ("name", other) => Err(AttributeError::TypeMismatch {
102 attribute: "name".to_string(),
103 expected: "string".to_string(),
104 actual: other.to_string(),
105 }),
106 (unknown, _) => Err(AttributeError::UnknownAttribute(unknown.to_string())),
107 }
108 }
109
110 fn assign_attributes(
111 &mut self,
112 attrs: HashMap<String, Value>,
113 ) -> Result<(), AttributeError> {
114 for (name, value) in attrs {
115 self.write_attribute(&name, value)?;
116 }
117 Ok(())
118 }
119
120 fn attributes(&self) -> HashMap<String, Value> {
121 let mut attributes = HashMap::new();
122 attributes.insert("id".to_string(), self.id.map_or(Value::Null, Value::from));
123 attributes.insert("name".to_string(), Value::String(self.name.clone()));
124 attributes
125 }
126 }
127
128 impl ModelNaming for TestUser {}
129 impl Conversion for TestUser {}
130
131 impl Model for TestUser {
132 fn new() -> Self {
133 Self {
134 id: None,
135 name: String::new(),
136 errors: Errors::new(),
137 }
138 }
139
140 fn errors(&self) -> &Errors {
141 &self.errors
142 }
143
144 fn errors_mut(&mut self) -> &mut Errors {
145 &mut self.errors
146 }
147
148 fn is_valid(&self) -> bool {
149 self.errors.is_empty()
150 }
151
152 fn validate(&mut self) -> bool {
153 self.errors.clear();
154 if self.name.trim().is_empty() {
155 self.errors.add("name", ErrorType::Blank, "can't be blank");
156 }
157 self.errors.is_empty()
158 }
159 }
160
161 #[test]
162 fn initialize_assigns_attributes_to_new_model() {
163 let model = TestUser::initialize(HashMap::from([
164 ("id".to_string(), json!(7)),
165 ("name".to_string(), json!("Alice")),
166 ]))
167 .expect("test data should initialize");
168
169 assert_eq!(model.id, Some(7));
170 assert_eq!(model.name, "Alice");
171 }
172
173 #[test]
174 fn validity_defaults_follow_current_errors() {
175 let mut model = TestUser::new();
176
177 assert!(model.is_valid());
178 model
179 .errors_mut()
180 .add("name", ErrorType::Blank, "can't be blank");
181 assert!(model.is_invalid());
182 }
183
184 #[test]
185 fn validate_populates_errors() {
186 let mut model = TestUser::new();
187
188 assert!(!model.validate());
189 assert!(model.is_invalid());
190
191 model.name = "Alice".to_string();
192 assert!(model.validate());
193 assert!(model.is_valid());
194 }
195
196 #[test]
197 fn model_trait_composes_conversion_and_serialization_defaults() {
198 let model = TestUser::initialize(HashMap::from([
199 ("id".to_string(), json!(12)),
200 ("name".to_string(), json!("Alice")),
201 ]))
202 .expect("test data should initialize");
203
204 assert_eq!(model.to_param(), Some("12".to_string()));
205 assert_eq!(model.to_partial_path(), "test_users/test_user");
206 assert_eq!(
207 serde_json::from_str::<Value>(&model.to_json(None)).expect("model JSON should parse"),
208 json!({"id": 12, "name": "Alice"})
209 );
210 }
211 #[derive(Debug, Default, Clone)]
212 struct MinimalModel {
213 errors: Errors,
214 }
215
216 impl Attributes for MinimalModel {
217 fn attribute_names() -> &'static [&'static str] {
218 &[]
219 }
220
221 fn read_attribute(&self, _name: &str) -> Option<Value> {
222 None
223 }
224
225 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
226 Err(AttributeError::UnknownAttribute(name.to_string()))
227 }
228
229 fn attributes(&self) -> HashMap<String, Value> {
230 HashMap::new()
231 }
232 }
233
234 impl ModelNaming for MinimalModel {}
235 impl Conversion for MinimalModel {}
236
237 impl Model for MinimalModel {
238 fn new() -> Self {
239 Self {
240 errors: Errors::new(),
241 }
242 }
243
244 fn errors(&self) -> &Errors {
245 &self.errors
246 }
247
248 fn errors_mut(&mut self) -> &mut Errors {
249 &mut self.errors
250 }
251 }
252
253 #[test]
254 fn initialize_propagates_attribute_errors() {
255 let result = TestUser::initialize(HashMap::from([(
256 "email".to_string(),
257 json!("alice@example.com"),
258 )]));
259
260 assert!(matches!(
261 result,
262 Err(AttributeError::UnknownAttribute(attribute)) if attribute == "email"
263 ));
264 }
265
266 #[test]
267 fn default_is_valid_returns_true_without_validation_override() {
268 let mut model = MinimalModel::new();
269 model
270 .errors_mut()
271 .add("name", ErrorType::Blank, "can't be blank");
272
273 assert!(model.is_valid());
274 assert!(!model.is_invalid());
275 }
276
277 #[test]
278 fn default_validate_clears_existing_errors_and_returns_true() {
279 let mut model = MinimalModel::new();
280 model
281 .errors_mut()
282 .add("name", ErrorType::Blank, "can't be blank");
283
284 assert!(model.validate());
285 assert!(model.errors().is_empty());
286 }
287
288 #[test]
289 fn custom_model_can_override_validity_checks() {
290 let mut model = TestUser::new();
291 assert!(model.is_valid());
292
293 model.errors.add("name", ErrorType::Blank, "can't be blank");
294 assert!(model.is_invalid());
295 }
296}