1use indexmap::IndexMap;
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4
5use crate::Attributes;
6
7pub type SerializationMap = IndexMap<String, Value>;
8
9#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
11pub struct SerializationOptions {
12 pub only: Option<Vec<String>>,
14 pub except: Option<Vec<String>>,
16 pub methods: Option<Vec<String>>,
18 pub include: Option<Vec<SerializationInclude>>,
20 pub root: Option<String>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub enum SerializationInclude {
26 Association(String),
27 Nested {
28 association: String,
29 options: SerializationOptions,
30 },
31}
32
33impl SerializationInclude {
34 pub fn named(name: impl Into<String>) -> Self {
35 Self::Association(name.into())
36 }
37
38 pub fn with_options(name: impl Into<String>, options: SerializationOptions) -> Self {
39 Self::Nested {
40 association: name.into(),
41 options,
42 }
43 }
44
45 fn association(&self) -> &str {
46 match self {
47 Self::Association(association) => association,
48 Self::Nested { association, .. } => association,
49 }
50 }
51
52 fn options(&self) -> Option<SerializationOptions> {
53 match self {
54 Self::Association(_) => None,
55 Self::Nested { options, .. } => Some(options.clone()),
56 }
57 }
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61struct SerializationAssociationRequest {
62 association: String,
63 options: SerializationOptions,
64}
65
66const ASSOCIATION_REQUEST_PREFIX: &str = "__rustrails_association__=";
67
68pub fn association_serialization_key(
69 association: impl Into<String>,
70 options: Option<SerializationOptions>,
71) -> String {
72 let request = SerializationAssociationRequest {
73 association: association.into(),
74 options: options.unwrap_or_default(),
75 };
76
77 format!(
78 "{ASSOCIATION_REQUEST_PREFIX}{}",
79 serde_json::to_string(&request).expect("association request should serialize")
80 )
81}
82
83pub fn parse_association_serialization_key(name: &str) -> Option<(String, SerializationOptions)> {
84 let payload = name.strip_prefix(ASSOCIATION_REQUEST_PREFIX)?;
85 let request = serde_json::from_str::<SerializationAssociationRequest>(payload).ok()?;
86 Some((request.association, request.options))
87}
88
89impl SerializationOptions {
90 pub fn new() -> Self {
92 Self::default()
93 }
94
95 pub fn only(mut self, attrs: Vec<String>) -> Self {
97 self.only = Some(attrs);
98 self
99 }
100
101 pub fn except(mut self, attrs: Vec<String>) -> Self {
103 self.except = Some(attrs);
104 self
105 }
106
107 pub fn methods(mut self, methods: Vec<String>) -> Self {
109 self.methods = Some(methods);
110 self
111 }
112
113 pub fn include(mut self, include: Vec<SerializationInclude>) -> Self {
115 self.include = Some(include);
116 self
117 }
118
119 pub fn root(mut self, root: String) -> Self {
121 self.root = Some(root);
122 self
123 }
124}
125
126pub trait Serialization: Attributes {
128 fn serializable_hash(&self, options: Option<SerializationOptions>) -> SerializationMap {
130 let options = options.unwrap_or_default();
131 let attribute_names = filter_attribute_names(Self::attribute_names(), &options);
132 let mut serialized = SerializationMap::with_capacity(
133 attribute_names.len()
134 + options.methods.as_ref().map_or(0, Vec::len)
135 + options.include.as_ref().map_or(0, Vec::len),
136 );
137
138 for attribute_name in attribute_names {
139 let value = self.read_attribute(&attribute_name).unwrap_or(Value::Null);
140 serialized.insert(attribute_name, value);
141 }
142
143 if let Some(methods) = options.methods.as_ref() {
144 for method_name in methods {
145 if let Some(value) = self.read_attribute(method_name) {
146 serialized.insert(method_name.clone(), value);
147 }
148 }
149 }
150
151 if let Some(includes) = options.include.as_ref() {
152 for include in includes {
153 let request =
154 association_serialization_key(include.association(), include.options());
155 if let Some(value) = self.read_attribute(&request) {
156 serialized.insert(include.association().to_string(), value);
157 }
158 }
159 }
160
161 serialized
162 }
163
164 fn as_json(&self, options: Option<SerializationOptions>) -> Value {
166 let root = options.as_ref().and_then(|opts| opts.root.clone());
167 let object = Value::Object(hash_to_json_map(self.serializable_hash(options)));
168
169 match root {
170 Some(root_key) => {
171 let mut wrapped = Map::with_capacity(1);
172 wrapped.insert(root_key, object);
173 Value::Object(wrapped)
174 }
175 None => object,
176 }
177 }
178
179 fn to_json(&self, options: Option<SerializationOptions>) -> String {
181 let root = options.as_ref().and_then(|opts| opts.root.clone());
182 let object = self.serializable_hash(options);
183
184 match root {
185 Some(root_key) => serde_json::to_string(&IndexMap::from([(
186 root_key,
187 Value::Object(hash_to_json_map(object)),
188 )]))
189 .expect("root-wrapped serialization should succeed"),
190 None => serde_json::to_string(&object).expect("serialization should succeed"),
191 }
192 }
193}
194
195impl<T> Serialization for T where T: Attributes {}
196
197fn filter_attribute_names(attribute_names: &[&str], options: &SerializationOptions) -> Vec<String> {
198 if let Some(only) = options.only.as_ref() {
199 let mut selected = Vec::with_capacity(only.len());
200
201 for candidate in only {
202 if attribute_names.contains(&candidate.as_str()) && !selected.contains(candidate) {
203 selected.push(candidate.clone());
204 }
205 }
206
207 return selected;
208 }
209
210 attribute_names
211 .iter()
212 .filter(|name| {
213 options
214 .except
215 .as_ref()
216 .is_none_or(|except| except.iter().all(|candidate| candidate != *name))
217 })
218 .map(|name| (*name).to_string())
219 .collect()
220}
221
222fn hash_to_json_map(hash: SerializationMap) -> Map<String, Value> {
223 hash.into_iter().collect()
224}
225
226#[cfg(test)]
227mod tests {
228 use std::collections::HashMap;
229
230 use serde_json::{Value, json};
231
232 use super::{
233 Serialization, SerializationInclude, SerializationMap, SerializationOptions,
234 association_serialization_key, parse_association_serialization_key,
235 };
236 use crate::{AttributeError, Attributes};
237
238 #[derive(Debug, Clone)]
239 struct TestUser {
240 id: u64,
241 name: String,
242 email: String,
243 }
244
245 impl Attributes for TestUser {
246 fn attribute_names() -> &'static [&'static str] {
247 &["id", "name", "email"]
248 }
249
250 fn read_attribute(&self, name: &str) -> Option<Value> {
251 match name {
252 "id" => Some(Value::from(self.id)),
253 "name" => Some(Value::String(self.name.clone())),
254 "email" => Some(Value::String(self.email.clone())),
255 "display_name" => Some(Value::String(format!("{} <{}>", self.name, self.email))),
256 _ => None,
257 }
258 }
259
260 fn write_attribute(&mut self, name: &str, value: Value) -> Result<(), AttributeError> {
261 match (name, value) {
262 ("id", Value::Number(number)) => {
263 let id = number
264 .as_u64()
265 .ok_or_else(|| AttributeError::TypeMismatch {
266 attribute: "id".to_string(),
267 expected: "u64".to_string(),
268 actual: "number".to_string(),
269 })?;
270 self.id = id;
271 Ok(())
272 }
273 ("name", Value::String(name)) => {
274 self.name = name;
275 Ok(())
276 }
277 ("email", Value::String(email)) => {
278 self.email = email;
279 Ok(())
280 }
281 ("id", other) => Err(AttributeError::TypeMismatch {
282 attribute: "id".to_string(),
283 expected: "u64".to_string(),
284 actual: other.to_string(),
285 }),
286 ("name", other) => Err(AttributeError::TypeMismatch {
287 attribute: "name".to_string(),
288 expected: "string".to_string(),
289 actual: other.to_string(),
290 }),
291 ("email", other) => Err(AttributeError::TypeMismatch {
292 attribute: "email".to_string(),
293 expected: "string".to_string(),
294 actual: other.to_string(),
295 }),
296 (unknown, _) => Err(AttributeError::UnknownAttribute(unknown.to_string())),
297 }
298 }
299
300 fn assign_attributes(
301 &mut self,
302 attrs: HashMap<String, Value>,
303 ) -> Result<(), AttributeError> {
304 for (name, value) in attrs {
305 self.write_attribute(&name, value)?;
306 }
307 Ok(())
308 }
309
310 fn attributes(&self) -> HashMap<String, Value> {
311 let mut attributes = HashMap::new();
312 attributes.insert("id".to_string(), Value::from(self.id));
313 attributes.insert("name".to_string(), Value::String(self.name.clone()));
314 attributes.insert("email".to_string(), Value::String(self.email.clone()));
315 attributes
316 }
317 }
318
319 fn test_user() -> TestUser {
320 TestUser {
321 id: 1,
322 name: "Alice".to_string(),
323 email: "alice@example.com".to_string(),
324 }
325 }
326
327 #[derive(Debug, Clone)]
328 struct TestAddress {
329 street: String,
330 city: String,
331 state: String,
332 zip: u32,
333 }
334
335 impl Attributes for TestAddress {
336 fn attribute_names() -> &'static [&'static str] {
337 &["street", "city", "state", "zip"]
338 }
339
340 fn read_attribute(&self, name: &str) -> Option<Value> {
341 match name {
342 "street" => Some(json!(self.street)),
343 "city" => Some(json!(self.city)),
344 "state" => Some(json!(self.state)),
345 "zip" => Some(json!(self.zip)),
346 _ => None,
347 }
348 }
349
350 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
351 Err(AttributeError::UnknownAttribute(name.to_string()))
352 }
353
354 fn attributes(&self) -> HashMap<String, Value> {
355 HashMap::from([
356 ("street".to_string(), json!(self.street)),
357 ("city".to_string(), json!(self.city)),
358 ("state".to_string(), json!(self.state)),
359 ("zip".to_string(), json!(self.zip)),
360 ])
361 }
362 }
363
364 #[derive(Debug, Clone)]
365 struct FriendList {
366 friends: Vec<SerializationUser>,
367 }
368
369 #[derive(Debug, Clone)]
370 enum FriendsAssociation {
371 Direct(Vec<SerializationUser>),
372 Ary(FriendList),
373 }
374
375 #[derive(Debug, Clone)]
376 struct SerializationUser {
377 name: String,
378 email: String,
379 gender: String,
380 address: Option<TestAddress>,
381 friends: FriendsAssociation,
382 }
383
384 impl SerializationUser {
385 fn new(name: &str, email: &str, gender: &str) -> Self {
386 Self {
387 name: name.to_string(),
388 email: email.to_string(),
389 gender: gender.to_string(),
390 address: None,
391 friends: FriendsAssociation::Direct(vec![]),
392 }
393 }
394 }
395
396 impl Attributes for SerializationUser {
397 fn attribute_names() -> &'static [&'static str] {
398 &["name", "email", "gender"]
399 }
400
401 fn read_attribute(&self, name: &str) -> Option<Value> {
402 if let Some((association, options)) = parse_association_serialization_key(name) {
403 return match association.as_str() {
404 "address" => self
405 .address
406 .as_ref()
407 .map(|address| serialize_record(address, options)),
408 "friends" => Some(match &self.friends {
409 FriendsAssociation::Direct(friends) => serialize_records(friends, options),
410 FriendsAssociation::Ary(list) => serialize_records(&list.friends, options),
411 }),
412 _ => None,
413 };
414 }
415
416 match name {
417 "name" => Some(json!(self.name)),
418 "email" => Some(json!(self.email)),
419 "gender" => Some(json!(self.gender)),
420 "full_name" => Some(json!(format!("{} <{}>", self.name, self.email))),
421 _ => None,
422 }
423 }
424
425 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
426 Err(AttributeError::UnknownAttribute(name.to_string()))
427 }
428
429 fn attributes(&self) -> HashMap<String, Value> {
430 HashMap::from([
431 ("name".to_string(), json!(self.name)),
432 ("email".to_string(), json!(self.email)),
433 ("gender".to_string(), json!(self.gender)),
434 ])
435 }
436 }
437
438 fn serialize_record<T: Serialization>(record: &T, options: SerializationOptions) -> Value {
439 Value::Object(super::hash_to_json_map(
440 record.serializable_hash(Some(options)),
441 ))
442 }
443
444 fn serialize_records<T: Serialization>(records: &[T], options: SerializationOptions) -> Value {
445 Value::Array(
446 records
447 .iter()
448 .map(|record| serialize_record(record, options.clone()))
449 .collect(),
450 )
451 }
452
453 fn serialization_friends() -> Vec<SerializationUser> {
454 vec![
455 SerializationUser::new("Joe", "joe@example.com", "male"),
456 SerializationUser::new("Sue", "sue@example.com", "female"),
457 ]
458 }
459
460 fn serialization_user_shallow() -> SerializationUser {
461 SerializationUser::new("David", "david@example.com", "male")
462 }
463
464 fn serialization_user() -> SerializationUser {
465 let mut user = serialization_user_shallow();
466 user.address = Some(TestAddress {
467 street: "123 Lane".to_string(),
468 city: "Springfield".to_string(),
469 state: "CA".to_string(),
470 zip: 11111,
471 });
472 user.friends = FriendsAssociation::Direct(serialization_friends());
473 user
474 }
475
476 #[test]
477 fn serializes_all_attributes_by_default() {
478 let user = test_user();
479
480 let serialized = user.serializable_hash(None);
481
482 assert_eq!(serialized.get("id"), Some(&json!(1)));
483 assert_eq!(serialized.get("name"), Some(&json!("Alice")));
484 assert_eq!(serialized.get("email"), Some(&json!("alice@example.com")));
485 }
486
487 #[test]
488 fn only_filters_attributes_and_wins_over_except() {
489 let user = test_user();
490 let options = SerializationOptions::new()
491 .only(vec!["name".to_string(), "email".to_string()])
492 .except(vec!["email".to_string()]);
493
494 let serialized = user.serializable_hash(Some(options));
495
496 assert_eq!(serialized.len(), 2);
497 assert!(serialized.contains_key("name"));
498 assert!(serialized.contains_key("email"));
499 assert!(!serialized.contains_key("id"));
500 }
501
502 #[test]
503 fn except_excludes_attributes() {
504 let user = test_user();
505 let serialized = user.serializable_hash(Some(
506 SerializationOptions::new().except(vec!["email".to_string()]),
507 ));
508
509 assert!(serialized.contains_key("id"));
510 assert!(serialized.contains_key("name"));
511 assert!(!serialized.contains_key("email"));
512 }
513
514 #[test]
515 fn methods_add_extra_values_when_available() {
516 let user = test_user();
517 let serialized = user.serializable_hash(Some(
518 SerializationOptions::new().methods(vec!["display_name".to_string()]),
519 ));
520
521 assert_eq!(
522 serialized.get("display_name"),
523 Some(&json!("Alice <alice@example.com>"))
524 );
525 }
526
527 #[test]
528 fn as_json_wraps_under_root_key() {
529 let user = test_user();
530 let value = user.as_json(Some(SerializationOptions::new().root("user".to_string())));
531
532 assert_eq!(
533 value,
534 json!({
535 "user": {
536 "id": 1,
537 "name": "Alice",
538 "email": "alice@example.com"
539 }
540 })
541 );
542 }
543
544 #[test]
545 fn to_json_returns_valid_json() {
546 let user = test_user();
547 let json = user.to_json(Some(
548 SerializationOptions::new().only(vec!["name".to_string()]),
549 ));
550
551 assert_eq!(json, "{\"name\":\"Alice\"}");
552 }
553 #[test]
554 fn association_request_keys_round_trip_nested_options() {
555 let options = SerializationOptions::new()
556 .only(vec!["name".to_string()])
557 .methods(vec!["full_name".to_string()]);
558 let key = association_serialization_key("friends", Some(options.clone()));
559
560 assert_eq!(
561 parse_association_serialization_key(&key),
562 Some(("friends".to_string(), options))
563 );
564 }
565
566 #[test]
567 fn builder_methods_store_requested_options() {
568 let options = SerializationOptions::new()
569 .only(vec!["name".to_string()])
570 .except(vec!["email".to_string()])
571 .methods(vec!["display_name".to_string()])
572 .include(vec![SerializationInclude::named("address")])
573 .root("user".to_string());
574
575 assert_eq!(options.only, Some(vec!["name".to_string()]));
576 assert_eq!(options.except, Some(vec!["email".to_string()]));
577 assert_eq!(options.methods, Some(vec!["display_name".to_string()]));
578 assert_eq!(
579 options.include,
580 Some(vec![SerializationInclude::named("address")])
581 );
582 assert_eq!(options.root, Some("user".to_string()));
583 }
584
585 #[test]
586 fn methods_ignore_unknown_dynamic_names() {
587 let user = test_user();
588 let serialized = user.serializable_hash(Some(
589 SerializationOptions::new().methods(vec!["missing".to_string()]),
590 ));
591
592 assert!(!serialized.contains_key("missing"));
593 }
594
595 #[test]
596 fn as_json_without_root_returns_plain_object() {
597 let user = test_user();
598 assert_eq!(
599 user.as_json(None),
600 json!({
601 "id": 1,
602 "name": "Alice",
603 "email": "alice@example.com"
604 })
605 );
606 }
607
608 #[test]
609 fn only_with_unknown_attributes_returns_empty_hash() {
610 let user = test_user();
611 let serialized = user.serializable_hash(Some(
612 SerializationOptions::new().only(vec!["missing".to_string()]),
613 ));
614
615 assert!(serialized.is_empty());
616 }
617
618 #[test]
619 fn except_can_remove_every_attribute() {
620 let user = test_user();
621 let serialized = user.serializable_hash(Some(SerializationOptions::new().except(vec![
622 "id".to_string(),
623 "name".to_string(),
624 "email".to_string(),
625 ])));
626
627 assert!(serialized.is_empty());
628 }
629
630 #[test]
631 fn to_json_preserves_root_wrapper() {
632 let user = test_user();
633 let json = user.to_json(Some(SerializationOptions::new().root("user".to_string())));
634
635 assert_eq!(
636 serde_json::from_str::<Value>(&json).expect("root-wrapped JSON should parse"),
637 json!({
638 "user": {
639 "id": 1,
640 "name": "Alice",
641 "email": "alice@example.com"
642 }
643 })
644 );
645 }
646
647 #[test]
648 fn unreadable_declared_attributes_serialize_as_null() {
649 #[derive(Debug, Clone)]
650 struct PartialRead;
651
652 impl Attributes for PartialRead {
653 fn attribute_names() -> &'static [&'static str] {
654 &["visible", "hidden"]
655 }
656
657 fn read_attribute(&self, name: &str) -> Option<Value> {
658 match name {
659 "visible" => Some(json!("value")),
660 _ => None,
661 }
662 }
663
664 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
665 Err(AttributeError::UnknownAttribute(name.to_string()))
666 }
667
668 fn attributes(&self) -> HashMap<String, Value> {
669 HashMap::from([
670 ("visible".to_string(), json!("value")),
671 ("hidden".to_string(), Value::Null),
672 ])
673 }
674 }
675
676 let value = PartialRead.as_json(None);
677 assert_eq!(value, json!({"visible": "value", "hidden": null}));
678 }
679
680 #[test]
681 fn serialization_options_start_empty() {
682 let options = SerializationOptions::new();
683
684 assert_eq!(options.only, None);
685 assert_eq!(options.except, None);
686 assert_eq!(options.methods, None);
687 assert_eq!(options.include, None);
688 assert_eq!(options.root, None);
689 }
690
691 #[test]
692 fn only_with_a_single_attribute_returns_that_attribute() {
693 let user = test_user();
694 let serialized = user.serializable_hash(Some(
695 SerializationOptions::new().only(vec!["name".to_string()]),
696 ));
697
698 assert_eq!(
699 serialized,
700 SerializationMap::from([("name".to_string(), json!("Alice"))])
701 );
702 }
703
704 #[test]
705 fn except_with_an_unknown_attribute_keeps_all_attributes() {
706 let user = test_user();
707 let serialized = user.serializable_hash(Some(
708 SerializationOptions::new().except(vec!["missing".to_string()]),
709 ));
710
711 assert_eq!(serialized, user.serializable_hash(None));
712 }
713
714 #[test]
715 fn empty_except_keeps_all_attributes() {
716 let user = test_user();
717 let serialized = user.serializable_hash(Some(SerializationOptions::new().except(vec![])));
718
719 assert_eq!(serialized, user.serializable_hash(None));
720 }
721
722 #[test]
723 fn empty_only_returns_an_empty_hash() {
724 let user = test_user();
725 let serialized = user.serializable_hash(Some(SerializationOptions::new().only(vec![])));
726
727 assert!(serialized.is_empty());
728 }
729
730 #[test]
731 fn duplicate_only_attributes_do_not_duplicate_entries() {
732 let user = test_user();
733 let serialized = user.serializable_hash(Some(
734 SerializationOptions::new().only(vec!["name".to_string(), "name".to_string()]),
735 ));
736
737 assert_eq!(serialized.len(), 1);
738 assert_eq!(serialized.get("name"), Some(&json!("Alice")));
739 }
740
741 #[test]
742 fn methods_can_be_returned_without_base_attributes() {
743 let user = test_user();
744 let serialized = user.serializable_hash(Some(
745 SerializationOptions::new()
746 .only(vec![])
747 .methods(vec!["display_name".to_string()]),
748 ));
749
750 assert_eq!(
751 serialized,
752 SerializationMap::from([(
753 "display_name".to_string(),
754 json!("Alice <alice@example.com>"),
755 )])
756 );
757 }
758
759 #[test]
760 fn mixed_known_and_unknown_methods_include_only_known_values() {
761 let user = test_user();
762 let serialized = user.serializable_hash(Some(
763 SerializationOptions::new()
764 .methods(vec!["display_name".to_string(), "missing".to_string()]),
765 ));
766
767 assert_eq!(
768 serialized.get("display_name"),
769 Some(&json!("Alice <alice@example.com>"))
770 );
771 assert!(!serialized.contains_key("missing"));
772 }
773
774 #[test]
775 fn methods_can_reintroduce_attributes_filtered_out_by_except() {
776 let user = test_user();
777 let serialized = user.serializable_hash(Some(
778 SerializationOptions::new()
779 .except(vec![
780 "id".to_string(),
781 "name".to_string(),
782 "email".to_string(),
783 ])
784 .methods(vec!["name".to_string()]),
785 ));
786
787 assert_eq!(
788 serialized,
789 SerializationMap::from([("name".to_string(), json!("Alice"))])
790 );
791 }
792
793 #[test]
794 fn as_json_without_root_can_include_method_values() {
795 let user = test_user();
796 let value = user.as_json(Some(
797 SerializationOptions::new().methods(vec!["display_name".to_string()]),
798 ));
799
800 assert_eq!(
801 value,
802 json!({
803 "id": 1,
804 "name": "Alice",
805 "email": "alice@example.com",
806 "display_name": "Alice <alice@example.com>"
807 })
808 );
809 }
810
811 #[test]
812 fn as_json_with_root_can_include_method_values() {
813 let user = test_user();
814 let value = user.as_json(Some(
815 SerializationOptions::new()
816 .methods(vec!["display_name".to_string()])
817 .root("user".to_string()),
818 ));
819
820 assert_eq!(
821 value,
822 json!({
823 "user": {
824 "id": 1,
825 "name": "Alice",
826 "email": "alice@example.com",
827 "display_name": "Alice <alice@example.com>"
828 }
829 })
830 );
831 }
832
833 #[test]
834 fn to_json_without_options_matches_as_json_output() {
835 let user = test_user();
836
837 assert_eq!(
838 serde_json::from_str::<Value>(&user.to_json(None)).expect("JSON should parse"),
839 user.as_json(None)
840 );
841 }
842
843 #[test]
844 fn to_json_with_root_and_methods_round_trips_through_json_parser() {
845 let user = test_user();
846 let json = user.to_json(Some(
847 SerializationOptions::new()
848 .methods(vec!["display_name".to_string()])
849 .root("user".to_string()),
850 ));
851
852 assert_eq!(
853 serde_json::from_str::<Value>(&json).expect("JSON should parse"),
854 json!({
855 "user": {
856 "id": 1,
857 "name": "Alice",
858 "email": "alice@example.com",
859 "display_name": "Alice <alice@example.com>"
860 }
861 })
862 );
863 }
864
865 #[test]
866 fn selected_unreadable_attributes_still_serialize_as_null() {
867 #[derive(Debug, Clone)]
868 struct HiddenOnly;
869
870 impl Attributes for HiddenOnly {
871 fn attribute_names() -> &'static [&'static str] {
872 &["hidden"]
873 }
874
875 fn read_attribute(&self, _name: &str) -> Option<Value> {
876 None
877 }
878
879 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
880 Err(AttributeError::UnknownAttribute(name.to_string()))
881 }
882
883 fn attributes(&self) -> HashMap<String, Value> {
884 HashMap::from([("hidden".to_string(), Value::Null)])
885 }
886 }
887
888 let serialized = HiddenOnly.serializable_hash(Some(
889 SerializationOptions::new().only(vec!["hidden".to_string()]),
890 ));
891
892 assert_eq!(
893 serialized,
894 SerializationMap::from([("hidden".to_string(), Value::Null)])
895 );
896 }
897
898 #[test]
899 fn as_json_allows_an_empty_root_key() {
900 let user = test_user();
901 let value = user.as_json(Some(SerializationOptions::new().root(String::new())));
902
903 assert_eq!(
904 value,
905 json!({
906 "": {
907 "id": 1,
908 "name": "Alice",
909 "email": "alice@example.com"
910 }
911 })
912 );
913 }
914
915 #[test]
916 fn to_json_serializes_nulls_for_unreadable_declared_attributes() {
917 #[derive(Debug, Clone)]
918 struct HiddenOnly;
919
920 impl Attributes for HiddenOnly {
921 fn attribute_names() -> &'static [&'static str] {
922 &["hidden"]
923 }
924
925 fn read_attribute(&self, _name: &str) -> Option<Value> {
926 None
927 }
928
929 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
930 Err(AttributeError::UnknownAttribute(name.to_string()))
931 }
932
933 fn attributes(&self) -> HashMap<String, Value> {
934 HashMap::from([("hidden".to_string(), Value::Null)])
935 }
936 }
937
938 assert_eq!(
939 serde_json::from_str::<Value>(&HiddenOnly.to_json(None)).expect("JSON should parse"),
940 json!({"hidden": null})
941 );
942 }
943
944 #[test]
945 fn methods_can_override_existing_attribute_values() {
946 #[derive(Debug)]
947 struct OverridableUser {
948 name_reads: std::cell::RefCell<usize>,
949 }
950
951 impl Attributes for OverridableUser {
952 fn attribute_names() -> &'static [&'static str] {
953 &["name"]
954 }
955
956 fn read_attribute(&self, name: &str) -> Option<Value> {
957 match name {
958 "name" => {
959 let mut reads = self.name_reads.borrow_mut();
960 *reads += 1;
961
962 Some(if *reads == 1 {
963 json!("Alice")
964 } else {
965 json!("Alicia")
966 })
967 }
968 _ => None,
969 }
970 }
971
972 fn write_attribute(&mut self, name: &str, _value: Value) -> Result<(), AttributeError> {
973 Err(AttributeError::UnknownAttribute(name.to_string()))
974 }
975
976 fn attributes(&self) -> HashMap<String, Value> {
977 HashMap::from([("name".to_string(), json!("Alice"))])
978 }
979 }
980
981 let user = OverridableUser {
982 name_reads: std::cell::RefCell::new(0),
983 };
984 let serialized = user.serializable_hash(Some(
985 SerializationOptions::new().methods(vec!["name".to_string()]),
986 ));
987
988 assert_eq!(
989 serialized,
990 SerializationMap::from([("name".to_string(), json!("Alicia"))])
991 );
992 }
993
994 #[test]
995 fn root_does_not_affect_serializable_hash_when_only_and_methods_are_used() {
996 let user = test_user();
997 let serialized = user.serializable_hash(Some(
998 SerializationOptions::new()
999 .only(vec![])
1000 .methods(vec!["display_name".to_string()])
1001 .root("user".to_string()),
1002 ));
1003
1004 assert_eq!(
1005 serialized,
1006 SerializationMap::from([(
1007 "display_name".to_string(),
1008 json!("Alice <alice@example.com>"),
1009 )])
1010 );
1011 assert!(!serialized.contains_key("user"));
1012 }
1013
1014 #[test]
1015 fn to_json_with_only_empty_and_root_round_trips_to_an_empty_object() {
1016 let user = test_user();
1017 let json = user.to_json(Some(
1018 SerializationOptions::new()
1019 .only(vec![])
1020 .root("user".to_string()),
1021 ));
1022
1023 assert_eq!(
1024 serde_json::from_str::<Value>(&json).expect("JSON should parse"),
1025 json!({"user": {}})
1026 );
1027 }
1028
1029 #[test]
1030 fn to_json_with_only_empty_methods_and_root_round_trips_to_a_methods_only_object() {
1031 let user = test_user();
1032 let json = user.to_json(Some(
1033 SerializationOptions::new()
1034 .only(vec![])
1035 .methods(vec!["display_name".to_string()])
1036 .root("user".to_string()),
1037 ));
1038
1039 assert_eq!(
1040 serde_json::from_str::<Value>(&json).expect("JSON should parse"),
1041 json!({
1042 "user": {
1043 "display_name": "Alice <alice@example.com>"
1044 }
1045 })
1046 );
1047 }
1048
1049 #[test]
1050 fn test_method_serializable_hash_should_work() {
1051 let user = test_user();
1052
1053 assert_eq!(
1054 user.serializable_hash(None),
1055 SerializationMap::from([
1056 ("id".to_string(), json!(1)),
1057 ("name".to_string(), json!("Alice")),
1058 ("email".to_string(), json!("alice@example.com")),
1059 ])
1060 );
1061 }
1062
1063 #[test]
1064 fn test_method_serializable_hash_should_work_with_only_option() {
1065 let user = test_user();
1066
1067 assert_eq!(
1068 user.serializable_hash(Some(
1069 SerializationOptions::new().only(vec!["name".to_string()]),
1070 )),
1071 SerializationMap::from([("name".to_string(), json!("Alice"))])
1072 );
1073 }
1074
1075 #[test]
1076 fn test_method_serializable_hash_should_work_with_only_option_with_order_of_given_keys() {
1077 let user = test_user();
1078 let serialized = user.serializable_hash(Some(
1079 SerializationOptions::new().only(vec!["name".to_string(), "email".to_string()]),
1080 ));
1081
1082 assert_eq!(
1083 serialized.keys().cloned().collect::<Vec<_>>(),
1084 vec!["name".to_string(), "email".to_string()]
1085 );
1086 }
1087
1088 #[test]
1089 fn test_method_serializable_hash_should_work_with_except_option() {
1090 let user = test_user();
1091
1092 assert_eq!(
1093 user.serializable_hash(Some(
1094 SerializationOptions::new().except(vec!["name".to_string()]),
1095 )),
1096 SerializationMap::from([
1097 ("id".to_string(), json!(1)),
1098 ("email".to_string(), json!("alice@example.com")),
1099 ])
1100 );
1101 }
1102
1103 #[test]
1104 fn test_method_serializable_hash_should_work_with_methods_option() {
1105 let user = test_user();
1106
1107 assert_eq!(
1108 user.serializable_hash(Some(
1109 SerializationOptions::new().methods(vec!["display_name".to_string()]),
1110 )),
1111 SerializationMap::from([
1112 ("id".to_string(), json!(1)),
1113 ("name".to_string(), json!("Alice")),
1114 ("email".to_string(), json!("alice@example.com")),
1115 (
1116 "display_name".to_string(),
1117 json!("Alice <alice@example.com>"),
1118 ),
1119 ])
1120 );
1121 }
1122
1123 #[test]
1124 fn test_method_serializable_hash_should_work_with_only_and_methods() {
1125 let user = test_user();
1126
1127 assert_eq!(
1128 user.serializable_hash(Some(
1129 SerializationOptions::new()
1130 .only(vec![])
1131 .methods(vec!["display_name".to_string()]),
1132 )),
1133 SerializationMap::from([(
1134 "display_name".to_string(),
1135 json!("Alice <alice@example.com>"),
1136 )])
1137 );
1138 }
1139
1140 #[test]
1141 fn test_method_serializable_hash_should_work_with_except_and_methods() {
1142 let user = test_user();
1143
1144 assert_eq!(
1145 user.serializable_hash(Some(
1146 SerializationOptions::new()
1147 .except(vec!["name".to_string()])
1148 .methods(vec!["display_name".to_string()]),
1149 )),
1150 SerializationMap::from([
1151 ("id".to_string(), json!(1)),
1152 ("email".to_string(), json!("alice@example.com")),
1153 (
1154 "display_name".to_string(),
1155 json!("Alice <alice@example.com>"),
1156 ),
1157 ])
1158 );
1159 }
1160
1161 #[test]
1162 #[ignore = "Rails-specific: Rust serialization skips unknown method names instead of raising an error"]
1163 fn test_should_raise_no_method_error_for_non_existing_method() {}
1164
1165 #[test]
1166 #[ignore = "Rails-specific: the Serialization trait has no read_attribute_for_serialization override hook"]
1167 fn test_should_use_read_attribute_for_serialization() {}
1168
1169 #[test]
1170 fn test_include_option_with_singular_association() {
1171 let user = serialization_user();
1172 let serialized = user.serializable_hash(Some(
1173 SerializationOptions::new().include(vec![SerializationInclude::named("address")]),
1174 ));
1175
1176 assert_eq!(
1177 serialized,
1178 SerializationMap::from([
1179 ("name".to_string(), json!("David")),
1180 ("email".to_string(), json!("david@example.com")),
1181 ("gender".to_string(), json!("male")),
1182 (
1183 "address".to_string(),
1184 json!({
1185 "street": "123 Lane",
1186 "city": "Springfield",
1187 "state": "CA",
1188 "zip": 11111
1189 }),
1190 ),
1191 ])
1192 );
1193 }
1194
1195 #[test]
1196 fn test_include_option_with_plural_association() {
1197 let user = serialization_user();
1198 let serialized = user.serializable_hash(Some(
1199 SerializationOptions::new().include(vec![SerializationInclude::named("friends")]),
1200 ));
1201
1202 assert_eq!(
1203 serialized,
1204 SerializationMap::from([
1205 ("name".to_string(), json!("David")),
1206 ("email".to_string(), json!("david@example.com")),
1207 ("gender".to_string(), json!("male")),
1208 (
1209 "friends".to_string(),
1210 json!([
1211 {"name": "Joe", "email": "joe@example.com", "gender": "male"},
1212 {"name": "Sue", "email": "sue@example.com", "gender": "female"}
1213 ]),
1214 ),
1215 ])
1216 );
1217 }
1218
1219 #[test]
1220 fn test_include_option_with_empty_association() {
1221 let mut user = serialization_user();
1222 user.friends = FriendsAssociation::Direct(vec![]);
1223
1224 let serialized = user.serializable_hash(Some(
1225 SerializationOptions::new().include(vec![SerializationInclude::named("friends")]),
1226 ));
1227
1228 assert_eq!(
1229 serialized,
1230 SerializationMap::from([
1231 ("name".to_string(), json!("David")),
1232 ("email".to_string(), json!("david@example.com")),
1233 ("gender".to_string(), json!("male")),
1234 ("friends".to_string(), json!([])),
1235 ])
1236 );
1237 }
1238
1239 #[test]
1240 fn test_include_option_with_ary() {
1241 let mut user = serialization_user();
1242 user.friends = FriendsAssociation::Ary(FriendList {
1243 friends: serialization_friends(),
1244 });
1245
1246 let serialized = user.serializable_hash(Some(
1247 SerializationOptions::new().include(vec![SerializationInclude::named("friends")]),
1248 ));
1249
1250 assert_eq!(
1251 serialized,
1252 SerializationMap::from([
1253 ("name".to_string(), json!("David")),
1254 ("email".to_string(), json!("david@example.com")),
1255 ("gender".to_string(), json!("male")),
1256 (
1257 "friends".to_string(),
1258 json!([
1259 {"name": "Joe", "email": "joe@example.com", "gender": "male"},
1260 {"name": "Sue", "email": "sue@example.com", "gender": "female"}
1261 ]),
1262 ),
1263 ])
1264 );
1265 }
1266
1267 #[test]
1268 fn test_multiple_includes() {
1269 let user = serialization_user();
1270 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1271 SerializationInclude::named("address"),
1272 SerializationInclude::named("friends"),
1273 ])));
1274
1275 assert_eq!(
1276 serialized,
1277 SerializationMap::from([
1278 ("name".to_string(), json!("David")),
1279 ("email".to_string(), json!("david@example.com")),
1280 ("gender".to_string(), json!("male")),
1281 (
1282 "address".to_string(),
1283 json!({
1284 "street": "123 Lane",
1285 "city": "Springfield",
1286 "state": "CA",
1287 "zip": 11111
1288 }),
1289 ),
1290 (
1291 "friends".to_string(),
1292 json!([
1293 {"name": "Joe", "email": "joe@example.com", "gender": "male"},
1294 {"name": "Sue", "email": "sue@example.com", "gender": "female"}
1295 ]),
1296 ),
1297 ])
1298 );
1299 }
1300
1301 #[test]
1302 fn test_include_with_options() {
1303 let user = serialization_user();
1304 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1305 SerializationInclude::with_options(
1306 "address",
1307 SerializationOptions::new().only(vec!["street".to_string()]),
1308 ),
1309 ])));
1310
1311 assert_eq!(
1312 serialized,
1313 SerializationMap::from([
1314 ("name".to_string(), json!("David")),
1315 ("email".to_string(), json!("david@example.com")),
1316 ("gender".to_string(), json!("male")),
1317 ("address".to_string(), json!({"street": "123 Lane"})),
1318 ])
1319 );
1320 }
1321
1322 #[test]
1323 fn test_include_with_nested_methods() {
1324 let user = serialization_user();
1325 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1326 SerializationInclude::with_options(
1327 "friends",
1328 SerializationOptions::new()
1329 .only(vec!["name".to_string()])
1330 .methods(vec!["full_name".to_string()]),
1331 ),
1332 ])));
1333
1334 assert_eq!(
1335 serialized,
1336 SerializationMap::from([
1337 ("name".to_string(), json!("David")),
1338 ("email".to_string(), json!("david@example.com")),
1339 ("gender".to_string(), json!("male")),
1340 (
1341 "friends".to_string(),
1342 json!([
1343 {"name": "Joe", "full_name": "Joe <joe@example.com>"},
1344 {"name": "Sue", "full_name": "Sue <sue@example.com>"}
1345 ]),
1346 ),
1347 ])
1348 );
1349 }
1350
1351 #[test]
1352 fn test_nested_include() {
1353 let mut user = serialization_user();
1354 if let FriendsAssociation::Direct(friends) = &mut user.friends {
1355 friends[0].friends = FriendsAssociation::Direct(vec![serialization_user_shallow()]);
1356 }
1357
1358 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1359 SerializationInclude::with_options(
1360 "friends",
1361 SerializationOptions::new().include(vec![SerializationInclude::named("friends")]),
1362 ),
1363 ])));
1364
1365 assert_eq!(
1366 serialized,
1367 SerializationMap::from([
1368 ("name".to_string(), json!("David")),
1369 ("email".to_string(), json!("david@example.com")),
1370 ("gender".to_string(), json!("male")),
1371 (
1372 "friends".to_string(),
1373 json!([
1374 {
1375 "name": "Joe",
1376 "email": "joe@example.com",
1377 "gender": "male",
1378 "friends": [{"name": "David", "email": "david@example.com", "gender": "male"}]
1379 },
1380 {
1381 "name": "Sue",
1382 "email": "sue@example.com",
1383 "gender": "female",
1384 "friends": []
1385 }
1386 ]),
1387 ),
1388 ])
1389 );
1390 }
1391
1392 #[test]
1393 fn test_only_include() {
1394 let user = serialization_user();
1395 let serialized = user.serializable_hash(Some(
1396 SerializationOptions::new()
1397 .only(vec!["name".to_string()])
1398 .include(vec![SerializationInclude::with_options(
1399 "friends",
1400 SerializationOptions::new().only(vec!["name".to_string()]),
1401 )]),
1402 ));
1403
1404 assert_eq!(
1405 serialized,
1406 SerializationMap::from([
1407 ("name".to_string(), json!("David")),
1408 (
1409 "friends".to_string(),
1410 json!([
1411 {"name": "Joe"},
1412 {"name": "Sue"}
1413 ]),
1414 ),
1415 ])
1416 );
1417 }
1418
1419 #[test]
1420 fn test_except_include() {
1421 let user = serialization_user();
1422 let serialized = user.serializable_hash(Some(
1423 SerializationOptions::new()
1424 .except(vec!["gender".to_string()])
1425 .include(vec![SerializationInclude::with_options(
1426 "friends",
1427 SerializationOptions::new().except(vec!["gender".to_string()]),
1428 )]),
1429 ));
1430
1431 assert_eq!(
1432 serialized,
1433 SerializationMap::from([
1434 ("name".to_string(), json!("David")),
1435 ("email".to_string(), json!("david@example.com")),
1436 (
1437 "friends".to_string(),
1438 json!([
1439 {"name": "Joe", "email": "joe@example.com"},
1440 {"name": "Sue", "email": "sue@example.com"}
1441 ]),
1442 ),
1443 ])
1444 );
1445 }
1446
1447 #[test]
1448 fn test_multiple_includes_with_options() {
1449 let user = serialization_user();
1450 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1451 SerializationInclude::with_options(
1452 "address",
1453 SerializationOptions::new().only(vec!["street".to_string()]),
1454 ),
1455 SerializationInclude::named("friends"),
1456 ])));
1457
1458 assert_eq!(
1459 serialized,
1460 SerializationMap::from([
1461 ("name".to_string(), json!("David")),
1462 ("email".to_string(), json!("david@example.com")),
1463 ("gender".to_string(), json!("male")),
1464 ("address".to_string(), json!({"street": "123 Lane"})),
1465 (
1466 "friends".to_string(),
1467 json!([
1468 {"name": "Joe", "email": "joe@example.com", "gender": "male"},
1469 {"name": "Sue", "email": "sue@example.com", "gender": "female"}
1470 ]),
1471 ),
1472 ])
1473 );
1474 }
1475
1476 #[test]
1477 fn test_all_includes_with_options() {
1478 let user = serialization_user();
1479 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1480 SerializationInclude::with_options(
1481 "address",
1482 SerializationOptions::new().only(vec!["street".to_string()]),
1483 ),
1484 SerializationInclude::with_options(
1485 "friends",
1486 SerializationOptions::new().only(vec!["name".to_string()]),
1487 ),
1488 ])));
1489
1490 assert_eq!(
1491 serialized,
1492 SerializationMap::from([
1493 ("name".to_string(), json!("David")),
1494 ("email".to_string(), json!("david@example.com")),
1495 ("gender".to_string(), json!("male")),
1496 ("address".to_string(), json!({"street": "123 Lane"})),
1497 (
1498 "friends".to_string(),
1499 json!([
1500 {"name": "Joe"},
1501 {"name": "Sue"}
1502 ]),
1503 ),
1504 ])
1505 );
1506 }
1507
1508 #[test]
1509 fn unknown_includes_are_skipped() {
1510 let user = serialization_user();
1511 let serialized = user.serializable_hash(Some(
1512 SerializationOptions::new().include(vec![SerializationInclude::named("missing")]),
1513 ));
1514
1515 assert_eq!(
1516 serialized,
1517 SerializationMap::from([
1518 ("name".to_string(), json!("David")),
1519 ("email".to_string(), json!("david@example.com")),
1520 ("gender".to_string(), json!("male")),
1521 ])
1522 );
1523 }
1524 #[test]
1525 fn test_multiple_includes_preserve_requested_association_order() {
1526 let user = serialization_user();
1527 let serialized = user.serializable_hash(Some(SerializationOptions::new().include(vec![
1528 SerializationInclude::named("friends"),
1529 SerializationInclude::named("address"),
1530 ])));
1531
1532 assert_eq!(
1533 serialized.keys().cloned().collect::<Vec<_>>(),
1534 vec![
1535 "name".to_string(),
1536 "email".to_string(),
1537 "gender".to_string(),
1538 "friends".to_string(),
1539 "address".to_string(),
1540 ]
1541 );
1542 }
1543
1544 #[test]
1545 fn methods_are_serialized_before_included_associations() {
1546 let user = serialization_user();
1547 let serialized = user.serializable_hash(Some(
1548 SerializationOptions::new()
1549 .methods(vec!["full_name".to_string()])
1550 .include(vec![SerializationInclude::named("address")]),
1551 ));
1552
1553 assert_eq!(
1554 serialized.keys().cloned().collect::<Vec<_>>(),
1555 vec![
1556 "name".to_string(),
1557 "email".to_string(),
1558 "gender".to_string(),
1559 "full_name".to_string(),
1560 "address".to_string(),
1561 ]
1562 );
1563 }
1564}