Skip to main content

celers_protocol/
serializer.rs

1//! Pluggable serialization framework
2//!
3//! This module provides a unified [`Serializer`] trait for different serialization
4//! formats (JSON, MessagePack, etc.) with automatic content type detection.
5//!
6//! # Example
7//!
8//! ```
9//! use celers_protocol::serializer::{Serializer, JsonSerializer};
10//!
11//! let serializer = JsonSerializer;
12//! let data = vec![1, 2, 3];
13//! let bytes = serializer.serialize(&data).unwrap();
14//! let decoded: Vec<i32> = serializer.deserialize(&bytes).unwrap();
15//! assert_eq!(data, decoded);
16//! ```
17
18use crate::{ContentEncoding, ContentType};
19use serde::{de::DeserializeOwned, Serialize};
20use std::fmt;
21
22/// Error type for serialization operations
23#[derive(Debug)]
24pub enum SerializerError {
25    /// Serialization failed
26    Serialize(String),
27    /// Deserialization failed
28    Deserialize(String),
29    /// Unsupported content type
30    UnsupportedContentType(String),
31    /// Compression error
32    Compression(String),
33}
34
35impl fmt::Display for SerializerError {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            SerializerError::Serialize(msg) => write!(f, "Serialization error: {}", msg),
39            SerializerError::Deserialize(msg) => write!(f, "Deserialization error: {}", msg),
40            SerializerError::UnsupportedContentType(ct) => {
41                write!(f, "Unsupported content type: {}", ct)
42            }
43            SerializerError::Compression(msg) => write!(f, "Compression error: {}", msg),
44        }
45    }
46}
47
48impl std::error::Error for SerializerError {}
49
50/// Result type for serialization operations
51pub type SerializerResult<T> = Result<T, SerializerError>;
52
53/// Trait for pluggable serialization formats
54///
55/// Implementors of this trait can serialize and deserialize data to/from bytes,
56/// providing their content type and encoding information.
57pub trait Serializer: Send + Sync {
58    /// Returns the content type for this serializer
59    fn content_type(&self) -> ContentType;
60
61    /// Returns the content encoding for this serializer
62    fn content_encoding(&self) -> ContentEncoding;
63
64    /// Serialize a value to bytes
65    fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>>;
66
67    /// Deserialize bytes to a value
68    fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T>;
69
70    /// Returns the name of this serializer
71    fn name(&self) -> &'static str;
72}
73
74/// JSON serializer implementation
75#[derive(Debug, Clone, Copy, Default)]
76pub struct JsonSerializer;
77
78impl Serializer for JsonSerializer {
79    #[inline]
80    fn content_type(&self) -> ContentType {
81        ContentType::Json
82    }
83
84    #[inline]
85    fn content_encoding(&self) -> ContentEncoding {
86        ContentEncoding::Utf8
87    }
88
89    fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>> {
90        serde_json::to_vec(value).map_err(|e| SerializerError::Serialize(e.to_string()))
91    }
92
93    fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T> {
94        serde_json::from_slice(bytes).map_err(|e| SerializerError::Deserialize(e.to_string()))
95    }
96
97    #[inline]
98    fn name(&self) -> &'static str {
99        "json"
100    }
101}
102
103/// MessagePack serializer implementation
104#[cfg(feature = "msgpack")]
105#[derive(Debug, Clone, Copy, Default)]
106pub struct MessagePackSerializer;
107
108#[cfg(feature = "msgpack")]
109impl Serializer for MessagePackSerializer {
110    #[inline]
111    fn content_type(&self) -> ContentType {
112        ContentType::MessagePack
113    }
114
115    #[inline]
116    fn content_encoding(&self) -> ContentEncoding {
117        ContentEncoding::Binary
118    }
119
120    fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>> {
121        rmp_serde::to_vec(value).map_err(|e| SerializerError::Serialize(e.to_string()))
122    }
123
124    fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T> {
125        rmp_serde::from_slice(bytes).map_err(|e| SerializerError::Deserialize(e.to_string()))
126    }
127
128    #[inline]
129    fn name(&self) -> &'static str {
130        "msgpack"
131    }
132}
133
134/// YAML serializer implementation
135#[cfg(feature = "yaml")]
136#[derive(Debug, Clone, Copy, Default)]
137pub struct YamlSerializer;
138
139#[cfg(feature = "yaml")]
140impl Serializer for YamlSerializer {
141    #[inline]
142    fn content_type(&self) -> ContentType {
143        ContentType::Custom("application/x-yaml".to_string())
144    }
145
146    #[inline]
147    fn content_encoding(&self) -> ContentEncoding {
148        ContentEncoding::Utf8
149    }
150
151    fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>> {
152        serde_yaml::to_string(value)
153            .map(|s| s.into_bytes())
154            .map_err(|e| SerializerError::Serialize(e.to_string()))
155    }
156
157    fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T> {
158        serde_yaml::from_slice(bytes).map_err(|e| SerializerError::Deserialize(e.to_string()))
159    }
160
161    #[inline]
162    fn name(&self) -> &'static str {
163        "yaml"
164    }
165}
166
167/// BSON serializer implementation
168#[cfg(feature = "bson-format")]
169#[derive(Debug, Clone, Copy, Default)]
170pub struct BsonSerializer;
171
172#[cfg(feature = "bson-format")]
173impl Serializer for BsonSerializer {
174    #[inline]
175    fn content_type(&self) -> ContentType {
176        ContentType::Custom("application/bson".to_string())
177    }
178
179    #[inline]
180    fn content_encoding(&self) -> ContentEncoding {
181        ContentEncoding::Binary
182    }
183
184    fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>> {
185        bson::serialize_to_vec(value).map_err(|e| SerializerError::Serialize(e.to_string()))
186    }
187
188    fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T> {
189        bson::deserialize_from_slice(bytes).map_err(|e| SerializerError::Deserialize(e.to_string()))
190    }
191
192    #[inline]
193    fn name(&self) -> &'static str {
194        "bson"
195    }
196}
197
198/// Protobuf serializer implementation
199///
200/// Note: This is a generic Protobuf serializer using prost.
201/// For proper Protobuf support, you should use prost code generation
202/// to create message-specific serializers.
203#[cfg(feature = "protobuf")]
204#[derive(Debug, Clone, Copy, Default)]
205pub struct ProtobufSerializer;
206
207#[cfg(feature = "protobuf")]
208impl ProtobufSerializer {
209    /// Serialize a prost::Message to bytes
210    pub fn serialize_message<T: prost::Message>(&self, value: &T) -> SerializerResult<Vec<u8>> {
211        let mut buf = Vec::new();
212        value
213            .encode(&mut buf)
214            .map_err(|e| SerializerError::Serialize(e.to_string()))?;
215        Ok(buf)
216    }
217
218    /// Deserialize bytes to a prost::Message
219    pub fn deserialize_message<T: prost::Message + Default>(
220        &self,
221        bytes: &[u8],
222    ) -> SerializerResult<T> {
223        T::decode(bytes).map_err(|e| SerializerError::Deserialize(e.to_string()))
224    }
225
226    /// Get content type for Protobuf
227    #[inline]
228    pub fn content_type(&self) -> ContentType {
229        ContentType::Custom("application/protobuf".to_string())
230    }
231
232    /// Get content encoding for Protobuf
233    #[inline]
234    pub fn content_encoding(&self) -> ContentEncoding {
235        ContentEncoding::Binary
236    }
237
238    /// Get the name
239    #[inline]
240    pub fn name(&self) -> &'static str {
241        "protobuf"
242    }
243}
244
245/// Serializer type enum for dynamic dispatch without dyn trait issues
246#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
247pub enum SerializerType {
248    /// JSON serializer
249    #[default]
250    Json,
251    /// MessagePack serializer
252    #[cfg(feature = "msgpack")]
253    MessagePack,
254    /// YAML serializer
255    #[cfg(feature = "yaml")]
256    Yaml,
257    /// BSON serializer
258    #[cfg(feature = "bson-format")]
259    Bson,
260    /// Protobuf serializer
261    #[cfg(feature = "protobuf")]
262    Protobuf,
263}
264
265impl SerializerType {
266    /// Get a serializer type by content type string
267    pub fn from_content_type(content_type: &str) -> SerializerResult<Self> {
268        match content_type {
269            "application/json" => Ok(SerializerType::Json),
270            #[cfg(feature = "msgpack")]
271            "application/x-msgpack" => Ok(SerializerType::MessagePack),
272            #[cfg(feature = "yaml")]
273            "application/x-yaml" | "application/yaml" | "text/yaml" => Ok(SerializerType::Yaml),
274            #[cfg(feature = "bson-format")]
275            "application/bson" => Ok(SerializerType::Bson),
276            #[cfg(feature = "protobuf")]
277            "application/protobuf" | "application/x-protobuf" => Ok(SerializerType::Protobuf),
278            _ => Err(SerializerError::UnsupportedContentType(
279                content_type.to_string(),
280            )),
281        }
282    }
283
284    /// Serialize a value to bytes
285    pub fn serialize<T: Serialize>(&self, value: &T) -> SerializerResult<Vec<u8>> {
286        match self {
287            SerializerType::Json => JsonSerializer.serialize(value),
288            #[cfg(feature = "msgpack")]
289            SerializerType::MessagePack => MessagePackSerializer.serialize(value),
290            #[cfg(feature = "yaml")]
291            SerializerType::Yaml => YamlSerializer.serialize(value),
292            #[cfg(feature = "bson-format")]
293            SerializerType::Bson => BsonSerializer.serialize(value),
294            #[cfg(feature = "protobuf")]
295            SerializerType::Protobuf => Err(SerializerError::Serialize(
296                "Protobuf requires prost::Message; use ProtobufSerializer::serialize_message instead".to_string(),
297            )),
298        }
299    }
300
301    /// Deserialize bytes to a value
302    pub fn deserialize<T: DeserializeOwned>(&self, bytes: &[u8]) -> SerializerResult<T> {
303        match self {
304            SerializerType::Json => JsonSerializer.deserialize(bytes),
305            #[cfg(feature = "msgpack")]
306            SerializerType::MessagePack => MessagePackSerializer.deserialize(bytes),
307            #[cfg(feature = "yaml")]
308            SerializerType::Yaml => YamlSerializer.deserialize(bytes),
309            #[cfg(feature = "bson-format")]
310            SerializerType::Bson => BsonSerializer.deserialize(bytes),
311            #[cfg(feature = "protobuf")]
312            SerializerType::Protobuf => Err(SerializerError::Deserialize(
313                "Protobuf requires prost::Message; use ProtobufSerializer::deserialize_message instead".to_string(),
314            )),
315        }
316    }
317
318    /// Get the content type
319    #[inline]
320    pub fn content_type(&self) -> ContentType {
321        match self {
322            SerializerType::Json => JsonSerializer.content_type(),
323            #[cfg(feature = "msgpack")]
324            SerializerType::MessagePack => MessagePackSerializer.content_type(),
325            #[cfg(feature = "yaml")]
326            SerializerType::Yaml => YamlSerializer.content_type(),
327            #[cfg(feature = "bson-format")]
328            SerializerType::Bson => BsonSerializer.content_type(),
329            #[cfg(feature = "protobuf")]
330            SerializerType::Protobuf => ProtobufSerializer.content_type(),
331        }
332    }
333
334    /// Get the content encoding
335    #[inline]
336    pub fn content_encoding(&self) -> ContentEncoding {
337        match self {
338            SerializerType::Json => JsonSerializer.content_encoding(),
339            #[cfg(feature = "msgpack")]
340            SerializerType::MessagePack => MessagePackSerializer.content_encoding(),
341            #[cfg(feature = "yaml")]
342            SerializerType::Yaml => YamlSerializer.content_encoding(),
343            #[cfg(feature = "bson-format")]
344            SerializerType::Bson => BsonSerializer.content_encoding(),
345            #[cfg(feature = "protobuf")]
346            SerializerType::Protobuf => ProtobufSerializer.content_encoding(),
347        }
348    }
349
350    /// Get the name
351    #[inline]
352    pub fn name(&self) -> &'static str {
353        match self {
354            SerializerType::Json => JsonSerializer.name(),
355            #[cfg(feature = "msgpack")]
356            SerializerType::MessagePack => MessagePackSerializer.name(),
357            #[cfg(feature = "yaml")]
358            SerializerType::Yaml => YamlSerializer.name(),
359            #[cfg(feature = "bson-format")]
360            SerializerType::Bson => BsonSerializer.name(),
361            #[cfg(feature = "protobuf")]
362            SerializerType::Protobuf => ProtobufSerializer.name(),
363        }
364    }
365}
366
367impl fmt::Display for SerializerType {
368    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369        write!(f, "{}", self.name())
370    }
371}
372
373impl TryFrom<&str> for SerializerType {
374    type Error = SerializerError;
375
376    fn try_from(content_type: &str) -> Result<Self, Self::Error> {
377        Self::from_content_type(content_type)
378    }
379}
380
381/// Get a serializer type by content type string
382///
383/// Returns the appropriate serializer type for the given content type string,
384/// or an error if the content type is not supported.
385///
386/// # Supported Content Types
387///
388/// - `application/json` - JSON serializer
389/// - `application/x-msgpack` - MessagePack serializer (requires `msgpack` feature)
390/// - `application/x-yaml` - YAML serializer (requires `yaml` feature)
391/// - `application/bson` - BSON serializer (requires `bson-format` feature)
392pub fn get_serializer(content_type: &str) -> SerializerResult<SerializerType> {
393    SerializerType::from_content_type(content_type)
394}
395
396/// Registry of available serializers
397pub struct SerializerRegistry {
398    default: SerializerType,
399}
400
401impl Default for SerializerRegistry {
402    fn default() -> Self {
403        Self::new()
404    }
405}
406
407impl SerializerRegistry {
408    /// Create a new serializer registry with JSON as default
409    pub fn new() -> Self {
410        Self {
411            default: SerializerType::Json,
412        }
413    }
414
415    /// Get the default serializer type
416    #[inline]
417    pub fn default_serializer(&self) -> SerializerType {
418        self.default
419    }
420
421    /// Get a serializer by content type
422    pub fn get(&self, content_type: &str) -> SerializerResult<SerializerType> {
423        get_serializer(content_type)
424    }
425
426    /// List all available serializers
427    pub fn available() -> Vec<&'static str> {
428        vec![
429            "application/json",
430            #[cfg(feature = "msgpack")]
431            "application/x-msgpack",
432            #[cfg(feature = "yaml")]
433            "application/x-yaml",
434            #[cfg(feature = "bson-format")]
435            "application/bson",
436            #[cfg(feature = "protobuf")]
437            "application/protobuf",
438        ]
439    }
440
441    /// Detect serialization format from raw bytes using magic numbers and heuristics.
442    ///
443    /// This performs best-effort detection by examining byte patterns:
444    /// - JSON: starts with `{` or `[` (after optional whitespace)
445    /// - YAML: starts with `---` document marker
446    /// - BSON: 4-byte LE size header matching data length, trailing `0x00`
447    /// - MessagePack: binary type markers in the `0x80..=0x9f` / `0xc0..=0xdf` ranges
448    /// - Protobuf: valid wire-type and field-number in the first tag byte (weak heuristic)
449    ///
450    /// Returns `None` if the format cannot be determined.
451    pub fn detect_format(data: &[u8]) -> Option<SerializerType> {
452        if data.is_empty() {
453            return None;
454        }
455
456        // JSON: starts with '{' or '[' (after optional whitespace)
457        let trimmed = data.iter().position(|&b| !b.is_ascii_whitespace());
458        if let Some(pos) = trimmed {
459            if data[pos] == b'{' || data[pos] == b'[' {
460                return Some(SerializerType::Json);
461            }
462        }
463
464        // YAML: starts with "---" document marker
465        #[cfg(feature = "yaml")]
466        if data.starts_with(b"---") {
467            return Some(SerializerType::Yaml);
468        }
469
470        // BSON: starts with 4-byte LE document size, ends with 0x00
471        #[cfg(feature = "bson-format")]
472        if data.len() >= 5 {
473            let size = u32::from_le_bytes([data[0], data[1], data[2], data[3]]) as usize;
474            if size == data.len() && data[data.len() - 1] == 0x00 {
475                return Some(SerializerType::Bson);
476            }
477        }
478
479        // MessagePack: various type markers
480        // fixmap (0x80-0x8f), fixarray (0x90-0x9f), nil/bool/bin/ext/float/int/str/array/map
481        #[cfg(feature = "msgpack")]
482        if matches!(
483            data[0],
484            0x80..=0x9f | 0xc0..=0xd3 | 0xd4..=0xd8 | 0xd9..=0xdf
485        ) {
486            // Additional heuristic: not valid JSON start
487            if data[0] != b'{' && data[0] != b'[' {
488                return Some(SerializerType::MessagePack);
489            }
490        }
491
492        // Protobuf: harder to detect, use heuristic
493        // Field tag format: (field_number << 3) | wire_type
494        // Wire type 0-5, field number > 0
495        // Require at least 2 bytes (tag + value) and the first byte must not be
496        // plain ASCII whitespace or printable text (to avoid false positives).
497        #[cfg(feature = "protobuf")]
498        if data.len() >= 2 {
499            let first = data[0];
500            let wire_type = first & 0x07;
501            let field_number = first >> 3;
502            if wire_type <= 5
503                && field_number > 0
504                && field_number < 100
505                && !first.is_ascii_whitespace()
506                && !first.is_ascii_alphanumeric()
507                && first != b'_'
508                && first != b'-'
509            {
510                return Some(SerializerType::Protobuf);
511            }
512        }
513
514        None
515    }
516
517    /// Negotiate the best serialization format between local and remote capabilities.
518    ///
519    /// Iterates through `local_preferred` in order and returns the first type
520    /// that also appears in `remote_supported`. Returns `None` if there is no
521    /// overlap between the two sets.
522    pub fn negotiate(
523        local_preferred: &[SerializerType],
524        remote_supported: &[SerializerType],
525    ) -> Option<SerializerType> {
526        local_preferred
527            .iter()
528            .find(|s| remote_supported.contains(s))
529            .copied()
530    }
531
532    /// Get all available serializer types based on enabled features.
533    ///
534    /// JSON is always included. Additional types are added when their
535    /// corresponding Cargo features are enabled.
536    pub fn available_types() -> Vec<SerializerType> {
537        #[allow(unused_mut)]
538        let mut types = vec![SerializerType::Json]; // always available
539
540        #[cfg(feature = "msgpack")]
541        types.push(SerializerType::MessagePack);
542
543        #[cfg(feature = "yaml")]
544        types.push(SerializerType::Yaml);
545
546        #[cfg(feature = "bson-format")]
547        types.push(SerializerType::Bson);
548
549        #[cfg(feature = "protobuf")]
550        types.push(SerializerType::Protobuf);
551
552        types
553    }
554}
555
556#[cfg(test)]
557mod tests {
558    use super::*;
559    use serde::{Deserialize, Serialize};
560
561    #[derive(Debug, PartialEq, Serialize, Deserialize)]
562    struct TestData {
563        name: String,
564        value: i32,
565    }
566
567    #[test]
568    fn test_json_serializer_round_trip() {
569        let serializer = JsonSerializer;
570        let data = TestData {
571            name: "test".to_string(),
572            value: 42,
573        };
574
575        let bytes = serializer.serialize(&data).unwrap();
576        let decoded: TestData = serializer.deserialize(&bytes).unwrap();
577
578        assert_eq!(data, decoded);
579    }
580
581    #[test]
582    fn test_json_serializer_content_type() {
583        let serializer = JsonSerializer;
584        assert_eq!(serializer.content_type(), ContentType::Json);
585        assert_eq!(serializer.content_encoding(), ContentEncoding::Utf8);
586        assert_eq!(serializer.name(), "json");
587    }
588
589    #[cfg(feature = "msgpack")]
590    #[test]
591    fn test_msgpack_serializer_round_trip() {
592        let serializer = MessagePackSerializer;
593        let data = TestData {
594            name: "msgpack_test".to_string(),
595            value: 100,
596        };
597
598        let bytes = serializer.serialize(&data).unwrap();
599        let decoded: TestData = serializer.deserialize(&bytes).unwrap();
600
601        assert_eq!(data, decoded);
602    }
603
604    #[cfg(feature = "msgpack")]
605    #[test]
606    fn test_msgpack_serializer_content_type() {
607        let serializer = MessagePackSerializer;
608        assert_eq!(serializer.content_type(), ContentType::MessagePack);
609        assert_eq!(serializer.content_encoding(), ContentEncoding::Binary);
610        assert_eq!(serializer.name(), "msgpack");
611    }
612
613    #[test]
614    fn test_get_serializer_json() {
615        let serializer = get_serializer("application/json").unwrap();
616        assert_eq!(serializer.name(), "json");
617    }
618
619    #[cfg(feature = "msgpack")]
620    #[test]
621    fn test_get_serializer_msgpack() {
622        let serializer = get_serializer("application/x-msgpack").unwrap();
623        assert_eq!(serializer.name(), "msgpack");
624    }
625
626    #[test]
627    fn test_get_serializer_unsupported() {
628        let result = get_serializer("application/unsupported");
629        assert!(result.is_err());
630        match result {
631            Err(SerializerError::UnsupportedContentType(ct)) => {
632                assert_eq!(ct, "application/unsupported");
633            }
634            _ => panic!("Expected UnsupportedContentType error"),
635        }
636    }
637
638    #[test]
639    fn test_serializer_registry() {
640        let registry = SerializerRegistry::new();
641        assert_eq!(registry.default_serializer().name(), "json");
642
643        let json = registry.get("application/json").unwrap();
644        assert_eq!(json.name(), "json");
645    }
646
647    #[test]
648    fn test_serializer_registry_available() {
649        let available = SerializerRegistry::available();
650        assert!(available.contains(&"application/json"));
651    }
652
653    #[test]
654    fn test_serializer_error_display() {
655        let err = SerializerError::Serialize("test error".to_string());
656        assert_eq!(err.to_string(), "Serialization error: test error");
657
658        let err = SerializerError::Deserialize("parse failed".to_string());
659        assert_eq!(err.to_string(), "Deserialization error: parse failed");
660
661        let err = SerializerError::UnsupportedContentType("text/plain".to_string());
662        assert_eq!(err.to_string(), "Unsupported content type: text/plain");
663
664        let err = SerializerError::Compression("gzip failed".to_string());
665        assert_eq!(err.to_string(), "Compression error: gzip failed");
666    }
667
668    #[cfg(feature = "bson-format")]
669    #[test]
670    fn test_bson_serializer_round_trip() {
671        let serializer = BsonSerializer;
672        let data = TestData {
673            name: "bson_test".to_string(),
674            value: 200,
675        };
676
677        let bytes = serializer.serialize(&data).unwrap();
678        let decoded: TestData = serializer.deserialize(&bytes).unwrap();
679
680        assert_eq!(data, decoded);
681    }
682
683    #[cfg(feature = "bson-format")]
684    #[test]
685    fn test_bson_serializer_content_type() {
686        let serializer = BsonSerializer;
687        assert_eq!(
688            serializer.content_type(),
689            ContentType::Custom("application/bson".to_string())
690        );
691        assert_eq!(serializer.content_encoding(), ContentEncoding::Binary);
692        assert_eq!(serializer.name(), "bson");
693    }
694
695    #[cfg(feature = "bson-format")]
696    #[test]
697    fn test_get_serializer_bson() {
698        let serializer = get_serializer("application/bson").unwrap();
699        assert_eq!(serializer.name(), "bson");
700    }
701
702    #[test]
703    fn test_serializer_type_equality() {
704        let json1 = SerializerType::Json;
705        let json2 = SerializerType::Json;
706        assert_eq!(json1, json2);
707
708        #[cfg(feature = "msgpack")]
709        {
710            let msgpack = SerializerType::MessagePack;
711            assert_ne!(json1, msgpack);
712        }
713    }
714
715    #[test]
716    fn test_serializer_type_hash() {
717        use std::collections::HashSet;
718
719        let mut set = HashSet::new();
720        set.insert(SerializerType::Json);
721        set.insert(SerializerType::Json); // Duplicate
722
723        #[cfg(feature = "msgpack")]
724        set.insert(SerializerType::MessagePack);
725
726        #[cfg(feature = "msgpack")]
727        assert_eq!(set.len(), 2);
728
729        #[cfg(not(feature = "msgpack"))]
730        assert_eq!(set.len(), 1);
731
732        assert!(set.contains(&SerializerType::Json));
733    }
734
735    #[test]
736    fn test_serializer_type_display() {
737        assert_eq!(SerializerType::Json.to_string(), "json");
738
739        #[cfg(feature = "msgpack")]
740        assert_eq!(SerializerType::MessagePack.to_string(), "msgpack");
741
742        #[cfg(feature = "yaml")]
743        assert_eq!(SerializerType::Yaml.to_string(), "yaml");
744
745        #[cfg(feature = "bson-format")]
746        assert_eq!(SerializerType::Bson.to_string(), "bson");
747    }
748
749    #[test]
750    fn test_serializer_type_try_from() {
751        use std::convert::TryFrom;
752
753        let json = SerializerType::try_from("application/json").unwrap();
754        assert_eq!(json, SerializerType::Json);
755
756        #[cfg(feature = "msgpack")]
757        {
758            let msgpack = SerializerType::try_from("application/x-msgpack").unwrap();
759            assert_eq!(msgpack, SerializerType::MessagePack);
760        }
761
762        #[cfg(feature = "yaml")]
763        {
764            let yaml = SerializerType::try_from("application/yaml").unwrap();
765            assert_eq!(yaml, SerializerType::Yaml);
766        }
767
768        // Test error case
769        let result = SerializerType::try_from("application/unsupported");
770        assert!(result.is_err());
771    }
772
773    #[test]
774    fn test_serializer_type_default() {
775        let default_type = SerializerType::default();
776        assert_eq!(default_type, SerializerType::Json);
777    }
778
779    #[test]
780    fn test_serializer_type_copy() {
781        let json = SerializerType::Json;
782        let json_copy = json; // Copy trait
783        let _json_original = json; // Can still use original
784
785        assert_eq!(json_copy, SerializerType::Json);
786    }
787
788    // ---- Format detection tests ----
789
790    #[test]
791    fn test_detect_format_empty_data() {
792        assert!(SerializerRegistry::detect_format(&[]).is_none());
793    }
794
795    #[test]
796    fn test_detect_format_json_object() {
797        let data = br#"{"key": "value"}"#;
798        assert_eq!(
799            SerializerRegistry::detect_format(data),
800            Some(SerializerType::Json)
801        );
802    }
803
804    #[test]
805    fn test_detect_format_json_array() {
806        let data = b"[1,2,3]";
807        assert_eq!(
808            SerializerRegistry::detect_format(data),
809            Some(SerializerType::Json)
810        );
811    }
812
813    #[test]
814    fn test_detect_format_json_with_leading_whitespace() {
815        let data = b"  \t\n  {\"key\": \"value\"}";
816        assert_eq!(
817            SerializerRegistry::detect_format(data),
818            Some(SerializerType::Json)
819        );
820    }
821
822    #[test]
823    fn test_detect_format_json_array_with_whitespace() {
824        let data = b"   [1, 2, 3]";
825        assert_eq!(
826            SerializerRegistry::detect_format(data),
827            Some(SerializerType::Json)
828        );
829    }
830
831    #[cfg(feature = "msgpack")]
832    #[test]
833    fn test_detect_format_msgpack() {
834        use std::collections::HashMap;
835
836        let mut map = HashMap::new();
837        map.insert("key", "value");
838        let bytes = rmp_serde::to_vec(&map).expect("msgpack serialization failed");
839        assert_eq!(
840            SerializerRegistry::detect_format(&bytes),
841            Some(SerializerType::MessagePack)
842        );
843    }
844
845    #[cfg(feature = "msgpack")]
846    #[test]
847    fn test_detect_format_msgpack_fixmap() {
848        // fixmap with 1 entry: 0x81
849        let data: &[u8] = &[
850            0x81, 0xa3, b'k', b'e', b'y', 0xa5, b'v', b'a', b'l', b'u', b'e',
851        ];
852        assert_eq!(
853            SerializerRegistry::detect_format(data),
854            Some(SerializerType::MessagePack)
855        );
856    }
857
858    #[cfg(feature = "bson-format")]
859    #[test]
860    fn test_detect_format_bson() {
861        let data = TestData {
862            name: "bson_detect".to_string(),
863            value: 42,
864        };
865        let bytes = bson::serialize_to_vec(&data).expect("bson serialization failed");
866        assert_eq!(
867            SerializerRegistry::detect_format(&bytes),
868            Some(SerializerType::Bson)
869        );
870    }
871
872    #[cfg(feature = "bson-format")]
873    #[test]
874    fn test_detect_format_bson_not_matching_size() {
875        // 4-byte LE size says 100, but actual length is 5 -> not BSON
876        let data: &[u8] = &[100, 0, 0, 0, 0x00];
877        assert_ne!(
878            SerializerRegistry::detect_format(data),
879            Some(SerializerType::Bson)
880        );
881    }
882
883    #[cfg(feature = "yaml")]
884    #[test]
885    fn test_detect_format_yaml() {
886        let data = b"---\nkey: value\n";
887        assert_eq!(
888            SerializerRegistry::detect_format(data),
889            Some(SerializerType::Yaml)
890        );
891    }
892
893    #[cfg(feature = "yaml")]
894    #[test]
895    fn test_detect_format_yaml_minimal() {
896        let data = b"---";
897        assert_eq!(
898            SerializerRegistry::detect_format(data),
899            Some(SerializerType::Yaml)
900        );
901    }
902
903    #[test]
904    fn test_detect_format_unknown_binary() {
905        // Random binary that doesn't match any known pattern
906        let data: &[u8] = &[0x00, 0x00, 0x00];
907        // With all features, BSON check: size=0 != len=3, msgpack: 0x00 not in range,
908        // protobuf: field_number=0 (invalid). Should be None.
909        assert!(SerializerRegistry::detect_format(data).is_none());
910    }
911
912    #[test]
913    fn test_detect_format_whitespace_only() {
914        let data = b"   \t\n  ";
915        // No non-whitespace byte found, trimmed position is None
916        assert!(SerializerRegistry::detect_format(data).is_none());
917    }
918
919    // ---- Negotiation tests ----
920
921    #[test]
922    fn test_negotiate_overlap() {
923        let local = [SerializerType::Json];
924        let remote = [SerializerType::Json];
925        assert_eq!(
926            SerializerRegistry::negotiate(&local, &remote),
927            Some(SerializerType::Json)
928        );
929    }
930
931    #[test]
932    fn test_negotiate_prefers_local_order() {
933        #[cfg(feature = "msgpack")]
934        {
935            let local = [SerializerType::MessagePack, SerializerType::Json];
936            let remote = [SerializerType::Json, SerializerType::MessagePack];
937            assert_eq!(
938                SerializerRegistry::negotiate(&local, &remote),
939                Some(SerializerType::MessagePack)
940            );
941        }
942    }
943
944    #[test]
945    fn test_negotiate_disjoint() {
946        // With only Json on one side and nothing on the other
947        let local = [SerializerType::Json];
948        let remote: &[SerializerType] = &[];
949        assert!(SerializerRegistry::negotiate(&local, remote).is_none());
950    }
951
952    #[test]
953    fn test_negotiate_empty_local() {
954        let local: &[SerializerType] = &[];
955        let remote = [SerializerType::Json];
956        assert!(SerializerRegistry::negotiate(local, &remote).is_none());
957    }
958
959    #[test]
960    fn test_negotiate_both_empty() {
961        let local: &[SerializerType] = &[];
962        let remote: &[SerializerType] = &[];
963        assert!(SerializerRegistry::negotiate(local, remote).is_none());
964    }
965
966    #[cfg(feature = "msgpack")]
967    #[test]
968    fn test_negotiate_partial_overlap() {
969        let local = [SerializerType::MessagePack, SerializerType::Json];
970        let remote = [SerializerType::Json];
971        assert_eq!(
972            SerializerRegistry::negotiate(&local, &remote),
973            Some(SerializerType::Json)
974        );
975    }
976
977    // ---- available_types tests ----
978
979    #[test]
980    fn test_available_types_always_has_json() {
981        let types = SerializerRegistry::available_types();
982        assert!(types.contains(&SerializerType::Json));
983        // JSON should be the first element
984        assert_eq!(types[0], SerializerType::Json);
985    }
986
987    #[cfg(feature = "msgpack")]
988    #[test]
989    fn test_available_types_has_msgpack() {
990        let types = SerializerRegistry::available_types();
991        assert!(types.contains(&SerializerType::MessagePack));
992    }
993
994    #[cfg(feature = "yaml")]
995    #[test]
996    fn test_available_types_has_yaml() {
997        let types = SerializerRegistry::available_types();
998        assert!(types.contains(&SerializerType::Yaml));
999    }
1000
1001    #[cfg(feature = "bson-format")]
1002    #[test]
1003    fn test_available_types_has_bson() {
1004        let types = SerializerRegistry::available_types();
1005        assert!(types.contains(&SerializerType::Bson));
1006    }
1007
1008    #[cfg(feature = "protobuf")]
1009    #[test]
1010    fn test_available_types_has_protobuf() {
1011        let types = SerializerRegistry::available_types();
1012        assert!(types.contains(&SerializerType::Protobuf));
1013    }
1014
1015    #[test]
1016    fn test_available_types_count() {
1017        let types = SerializerRegistry::available_types();
1018        let mut expected = 1; // Json always
1019
1020        #[cfg(feature = "msgpack")]
1021        {
1022            expected += 1;
1023        }
1024        #[cfg(feature = "yaml")]
1025        {
1026            expected += 1;
1027        }
1028        #[cfg(feature = "bson-format")]
1029        {
1030            expected += 1;
1031        }
1032        #[cfg(feature = "protobuf")]
1033        {
1034            expected += 1;
1035        }
1036
1037        assert_eq!(types.len(), expected);
1038    }
1039
1040    #[cfg(feature = "protobuf")]
1041    #[test]
1042    fn test_serializer_type_protobuf_name() {
1043        assert_eq!(SerializerType::Protobuf.name(), "protobuf");
1044    }
1045
1046    #[cfg(feature = "protobuf")]
1047    #[test]
1048    fn test_serializer_type_protobuf_content_type() {
1049        assert_eq!(
1050            SerializerType::Protobuf.content_type(),
1051            ContentType::Custom("application/protobuf".to_string())
1052        );
1053    }
1054}