Skip to main content

arri_repr/
serializable.rs

1// TODO: create a macro which automatically generates this implementation with a derive
2use std::{any::type_name, collections::HashMap, fmt::Debug};
3
4use downcast_rs::{Downcast, impl_downcast};
5
6use crate::{MetadataSchema, serializer::Serializer};
7
8/// Escapes special characters in a string for JSON output.
9///
10/// Follows the JSON specification for string escaping:
11/// - `"` -> `\"`
12/// - `\` -> `\\`
13/// - `/` -> `\/`
14/// - Backspace -> `\b`
15/// - Form feed -> `\f`
16/// - Newline -> `\n`
17/// - Carriage return -> `\r`
18/// - Tab -> `\t`
19/// - Control characters and other special Unicode -> `\uXXXX`
20fn escape_json_string(s: &str) -> String {
21    let mut result = String::with_capacity(s.len() + 16);
22    result.push('"');
23
24    for ch in s.chars() {
25        match ch {
26            '"' => result.push_str("\\\""),
27            '\\' => result.push_str("\\\\"),
28            '/' => result.push_str("\\/"),
29            '\u{0008}' => result.push_str("\\b"), // backspace
30            '\u{000C}' => result.push_str("\\f"), // form feed
31            '\n' => result.push_str("\\n"),
32            '\r' => result.push_str("\\r"),
33            '\t' => result.push_str("\\t"),
34            ch if ch.is_control() => {
35                // Escape other control characters as \uXXXX
36                result.push_str(&format!("\\u{:04x}", ch as u32));
37            }
38            ch => result.push(ch),
39        }
40    }
41
42    result.push('"');
43    result
44}
45
46/// Triggers a panic with a detailed error message, including the type, serialized data,
47/// and value that caused the issue. This function is intended to report bugs.
48///
49/// # Parameters
50/// - `message`: A message describing the context of the panic.
51/// - `serialized`: The serialized representation of the data.
52/// - `value`: The original value that caused the issue.
53///
54/// # Panics
55/// This function always panics with a formatted message containing the provided details.
56///
57/// # Note
58/// The panic message includes a link to report bugs, encouraging users to provide feedback.
59fn do_panic<T>(message: impl std::fmt::Display, serialized: impl Debug, value: impl Debug) -> !
60where
61    T: ?Sized,
62{
63    panic!(
64        "{}!\n\
65        This is a bug, please report it @ <https://github.com/Arthurdw/ronky/issues>\n\
66        Type: {:?}\n\
67        Serialized: {:?}\n\
68        Value: {:?}",
69        message,
70        type_name::<T>(),
71        serialized,
72        value
73    );
74}
75
76/// A trait for types that can be serialized into a string representation.
77///
78/// This trait also provides default implementations for setting metadata,
79/// nullability, and renaming, which trigger a panic if not implemented.
80pub trait Serializable: Downcast {
81    /// Serializes the object into an optional string representation.
82    ///
83    /// # Returns
84    /// An `Option<String>` containing the serialized representation, or `None` if serialization fails.
85    fn serialize(&self) -> Option<String>;
86
87    /// Sets metadata for the object.
88    ///
89    /// # Arguments
90    /// - `metadata`: The metadata to set.
91    ///
92    /// # Panics
93    /// This method panics if not implemented for the type.
94    fn set_metadata(&mut self, metadata: MetadataSchema) {
95        do_panic::<Self>(
96            "set_metadata is not implemented for this type",
97            self.serialize(),
98            metadata,
99        );
100    }
101
102    /// Sets the nullability of the object.
103    ///
104    /// # Arguments
105    /// - `nullable`: A boolean indicating whether the object is nullable.
106    ///
107    /// # Panics
108    /// This method panics if not implemented for the type.
109    fn set_nullable(&mut self, nullable: bool) {
110        do_panic::<Self>(
111            "set_nullable is not implemented for this type",
112            self.serialize(),
113            nullable,
114        );
115    }
116
117    /// Renames the object.
118    ///
119    /// # Arguments
120    /// - `new_name`: The new name to assign to the object.
121    ///
122    /// # Panics
123    /// This method panics if not implemented for the type.
124    fn set_rename(&mut self, new_name: &str) {
125        do_panic::<Self>(
126            "set_rename is not implemented for this type",
127            self.serialize(),
128            new_name,
129        );
130    }
131}
132
133impl_downcast!(Serializable);
134
135impl Debug for dyn Serializable {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        write!(f, "{}", self.serialize().unwrap_or("undefined".to_string()))
138    }
139}
140
141impl PartialEq for dyn Serializable {
142    fn eq(&self, other: &Self) -> bool {
143        self.serialize() == other.serialize()
144    }
145}
146
147impl Eq for dyn Serializable {}
148
149impl Serializable for &'static str {
150    fn serialize(&self) -> Option<String> {
151        escape_json_string(self).into()
152    }
153}
154
155impl Serializable for String {
156    fn serialize(&self) -> Option<String> {
157        escape_json_string(self).into()
158    }
159}
160
161impl Serializable for bool {
162    fn serialize(&self) -> Option<String> {
163        self.to_string().into()
164    }
165}
166
167// Macro to generate Serializable implementations for numeric types
168macro_rules! impl_serializable_for_numeric {
169    ($($type:ty),* $(,)?) => {
170        $(
171            impl Serializable for $type {
172                fn serialize(&self) -> Option<String> {
173                    self.to_string().into()
174                }
175            }
176        )*
177    };
178}
179
180// Numeric type implementations
181impl_serializable_for_numeric!(
182    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64,
183);
184
185impl Serializable for () {
186    fn serialize(&self) -> Option<String> {
187        "null".to_string().into()
188    }
189}
190
191impl<T: Serializable> Serializable for Vec<T> {
192    fn serialize(&self) -> Option<String> {
193        let serialized_elements: Vec<String> = self.iter().filter_map(|e| e.serialize()).collect();
194        format!("[{}]", serialized_elements.join(",")).into()
195    }
196}
197
198impl<T: Serializable> Serializable for Option<T> {
199    fn serialize(&self) -> Option<String> {
200        self.as_ref().and_then(|value| value.serialize())
201    }
202}
203
204impl<T: Serializable> Serializable for HashMap<String, T> {
205    fn serialize(&self) -> Option<String> {
206        self.iter()
207            .fold(Serializer::builder(), |mut builder, (key, value)| {
208                builder.set(key, value);
209                builder
210            })
211            .build()
212            .into()
213    }
214}
215
216impl<T: Serializable> Serializable for indexmap::IndexMap<String, T> {
217    fn serialize(&self) -> Option<String> {
218        self.iter()
219            .fold(Serializer::builder(), |mut builder, (key, value)| {
220                builder.set(key, value);
221                builder
222            })
223            .build()
224            .into()
225    }
226}
227
228impl<T: Serializable> Serializable for Box<T> {
229    fn serialize(&self) -> Option<String> {
230        self.as_ref().serialize()
231    }
232
233    fn set_metadata(&mut self, metadata: MetadataSchema) {
234        self.as_mut().set_metadata(metadata)
235    }
236
237    fn set_nullable(&mut self, nullable: bool) {
238        self.as_mut().set_nullable(nullable)
239    }
240
241    fn set_rename(&mut self, new_name: &str) {
242        self.as_mut().set_rename(new_name)
243    }
244}
245
246impl Serializable for Box<dyn Serializable> {
247    fn serialize(&self) -> Option<String> {
248        self.as_ref().serialize()
249    }
250
251    fn set_metadata(&mut self, metadata: MetadataSchema) {
252        self.as_mut().set_metadata(metadata)
253    }
254
255    fn set_nullable(&mut self, nullable: bool) {
256        self.as_mut().set_nullable(nullable)
257    }
258
259    fn set_rename(&mut self, new_name: &str) {
260        self.as_mut().set_rename(new_name)
261    }
262}
263
264#[cfg(feature = "chrono")]
265impl Serializable for chrono::DateTime<chrono::FixedOffset> {
266    fn serialize(&self) -> Option<String> {
267        self.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
268            .serialize()
269    }
270}
271
272#[cfg(feature = "chrono")]
273impl Serializable for chrono::DateTime<chrono::Utc> {
274    fn serialize(&self) -> Option<String> {
275        self.to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
276            .serialize()
277    }
278}
279
280// TODO: implement other features serialization and general object serialization
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[derive(Clone)]
287    struct MockSerializable {
288        value: String,
289    }
290
291    impl Serializable for MockSerializable {
292        fn serialize(&self) -> Option<String> {
293            self.value.serialize()
294        }
295    }
296
297    #[test]
298    fn test_serialize() {
299        let mock = MockSerializable {
300            value: "test_value".to_string(),
301        };
302        assert_eq!(mock.serialize(), Some("\"test_value\"".to_string()));
303    }
304
305    #[test]
306    fn test_debug_trait() {
307        let mock = MockSerializable {
308            value: "debug_value".to_string(),
309        };
310        assert_eq!(
311            format!("{:?}", &mock as &dyn Serializable),
312            "\"debug_value\""
313        );
314    }
315
316    #[test]
317    fn test_partial_eq_trait() {
318        let mock1 = MockSerializable {
319            value: "value".to_string(),
320        };
321        let mock2 = MockSerializable {
322            value: "value".to_string(),
323        };
324        assert_eq!(&mock1 as &dyn Serializable, &mock2 as &dyn Serializable);
325    }
326
327    #[test]
328    fn test_serialize_str() {
329        let value = "Hello, world!";
330        assert_eq!(value.serialize(), Some("\"Hello, world!\"".to_string()));
331    }
332
333    #[test]
334    fn test_serialize_string() {
335        let value = "Hello, world!".to_string();
336        assert_eq!(value.serialize(), Some("\"Hello, world!\"".to_string()));
337    }
338
339    #[test]
340    fn test_serialize_vec() {
341        let vec = vec![
342            MockSerializable {
343                value: "value1".to_string(),
344            },
345            MockSerializable {
346                value: "value2".to_string(),
347            },
348        ];
349        let serialized: serde_json::Value =
350            serde_json::from_str(&vec.serialize().unwrap()).unwrap();
351
352        assert_eq!(serialized, serde_json::json!(["value1", "value2"]));
353    }
354
355    #[test]
356    fn test_serialize_option() {
357        let value = Some(MockSerializable {
358            value: "optional_value".to_string(),
359        });
360
361        let none_value: Option<MockSerializable> = None;
362
363        assert_eq!(value.serialize(), Some("\"optional_value\"".to_string()));
364        assert_eq!(none_value.serialize(), None);
365    }
366
367    #[test]
368    fn test_recursive_serialize_vec() {
369        let vec = vec![
370            MockSerializable {
371                value: "value1".to_string(),
372            },
373            MockSerializable {
374                value: "value2".to_string(),
375            },
376        ];
377
378        let serialized: serde_json::Value =
379            serde_json::from_str(&vec.serialize().unwrap()).unwrap();
380
381        assert_eq!(serialized, serde_json::json!(["value1", "value2"]));
382    }
383
384    #[test]
385    fn test_serialize_hashmap_basic() {
386        let mut hashmap: HashMap<String, String> = HashMap::new();
387        hashmap.insert("key1".to_string(), "value1".to_string());
388        hashmap.insert("key2".to_string(), "value2".to_string());
389
390        let serialized: serde_json::Value =
391            serde_json::from_str(&hashmap.serialize().unwrap()).unwrap();
392
393        assert_eq!(
394            serialized,
395            serde_json::json!({
396                "key1": "value1",
397                "key2": "value2"
398            })
399        );
400    }
401
402    #[test]
403    fn test_serialize_hashmap_partial_recursion() {
404        let hashmap = HashMap::from([
405            (
406                "value1".to_string(),
407                MockSerializable {
408                    value: "nested_value1".to_string(),
409                },
410            ),
411            (
412                "value2".to_string(),
413                MockSerializable {
414                    value: "nested_value2".to_string(),
415                },
416            ),
417        ]);
418
419        let serialized: serde_json::Value =
420            serde_json::from_str(&hashmap.serialize().unwrap()).unwrap();
421
422        assert_eq!(
423            serialized,
424            serde_json::json!({
425                "value1": "nested_value1",
426                "value2": "nested_value2"
427            })
428        );
429    }
430
431    #[test]
432    fn test_serialize_hashmap_recursion() {
433        let mut hashmap: HashMap<String, HashMap<String, MockSerializable>> = HashMap::new();
434        hashmap.insert(
435            "key1".to_string(),
436            HashMap::from([(
437                "value1".to_string(),
438                MockSerializable {
439                    value: "nested_value1".to_string(),
440                },
441            )]),
442        );
443        hashmap.insert(
444            "key2".to_string(),
445            HashMap::from([(
446                "value2".to_string(),
447                MockSerializable {
448                    value: "nested_value2".to_string(),
449                },
450            )]),
451        );
452
453        let serialized: serde_json::Value =
454            serde_json::from_str(&hashmap.serialize().unwrap()).unwrap();
455
456        assert_eq!(
457            serialized,
458            serde_json::json!({
459                "key1": {
460                    "value1": "nested_value1"
461                },
462                "key2": {
463                    "value2": "nested_value2"
464                }
465            })
466        );
467    }
468
469    #[test]
470    fn test_serialize_box() {
471        let boxed_value = Box::new(MockSerializable {
472            value: "boxed_value".to_string(),
473        });
474        assert_eq!(boxed_value.serialize(), Some("\"boxed_value\"".to_string()));
475    }
476
477    #[test]
478    fn test_serialize_box_dyn() {
479        let boxed_value: Box<dyn Serializable> = Box::new(MockSerializable {
480            value: "boxed_dyn_value".to_string(),
481        });
482        assert_eq!(
483            boxed_value.serialize(),
484            Some("\"boxed_dyn_value\"".to_string())
485        );
486    }
487
488    #[cfg(feature = "chrono")]
489    #[test]
490    fn test_serialize_chrono_fixed_offset() {
491        use chrono::{DateTime, FixedOffset};
492
493        let datetime =
494            DateTime::<FixedOffset>::parse_from_rfc3339("1985-04-12T23:20:50.520Z").unwrap();
495
496        assert_eq!(
497            datetime.serialize(),
498            Some("\"1985-04-12T23:20:50.520Z\"".to_string())
499        );
500    }
501
502    #[cfg(feature = "chrono")]
503    #[test]
504    fn test_serialize_chrono_utc() {
505        use chrono::{DateTime, FixedOffset, Utc};
506        let datetime =
507            DateTime::<FixedOffset>::parse_from_rfc3339("1985-04-12T23:20:50.520Z").unwrap();
508        let datetime_utc = datetime.with_timezone(&Utc);
509
510        assert_eq!(
511            datetime_utc.serialize(),
512            Some("\"1985-04-12T23:20:50.520Z\"".to_string())
513        );
514    }
515
516    #[test]
517    fn test_serialize_string_with_special_characters() {
518        // Test case from issue #233 - special characters should be escaped
519        let test_cases: Vec<(&str, &str)> = vec![
520            ("simple", "\"simple\""),
521            ("with\"quote", "\"with\\\"quote\""),
522            ("with\\backslash", "\"with\\\\backslash\""),
523            ("with\nnewline", "\"with\\nnewline\""),
524            ("with\ttab", "\"with\\ttab\""),
525            ("with\rcarriage", "\"with\\rcarriage\""),
526            ("with/solidus", "\"with\\/solidus\""),
527            (
528                "a comma separated list of tags to filter by\nex: \"foo,bar\"",
529                "\"a comma separated list of tags to filter by\\nex: \\\"foo,bar\\\"\"",
530            ),
531        ];
532
533        for (input, expected) in test_cases {
534            let serialized = input.to_string().serialize().unwrap();
535            let result: serde_json::Value = serde_json::from_str(&serialized)
536                .unwrap_or_else(|_| panic!("Failed to parse JSON for input: {}", input));
537            let expected_result: serde_json::Value = serde_json::from_str(expected)
538                .unwrap_or_else(|_| panic!("Failed to parse expected JSON: {}", expected));
539            assert_eq!(result, expected_result, "Mismatch for input: {}", input);
540        }
541    }
542}