Skip to main content

dyn_encoding/
registry.rs

1//! Content-type-keyed registry of [`WireCodec`] implementations.
2
3use std::collections::BTreeMap;
4
5use crate::codec::{
6    bebop::BebopCodec, bson::BsonCodec, capnp::CapnpCodec, cbor::CborCodec,
7    flatbuffers::FlatbuffersCodec, json::JsonCodec, protobuf::ProtobufCodec,
8};
9use crate::value::WireCodec;
10
11/// Bag of [`WireCodec`] implementations indexed by content-type.
12///
13/// The registry is populated at startup (typically through
14/// [`Self::with_baseline`] plus per-message-type
15/// [`JsonCodec::register`] / [`CborCodec::register`] /
16/// [`ProtobufCodec::register`] calls) and then queried per-request via
17/// [`Self::for_content_type`] to pick the codec the peer asked for.
18pub struct CodecRegistry {
19    by_content_type: BTreeMap<&'static str, Box<dyn WireCodec>>,
20}
21
22impl CodecRegistry {
23    /// Build an empty registry.
24    #[must_use]
25    pub fn new() -> Self {
26        Self {
27            by_content_type: BTreeMap::new(),
28        }
29    }
30
31    /// Build a registry pre-populated with the seven baseline
32    /// codecs: [`JsonCodec`], [`CborCodec`], [`ProtobufCodec`],
33    /// [`FlatbuffersCodec`], [`CapnpCodec`], [`BebopCodec`], and
34    /// [`BsonCodec`]. The codecs are returned with empty type
35    /// tables; callers attach their message types through the
36    /// codec-specific `register` entry points before installing the
37    /// codecs into the registry, or use [`Self::register`] to
38    /// install codecs that already have their type tables
39    /// populated.
40    #[must_use]
41    pub fn with_baseline() -> Self {
42        let mut r = Self::new();
43        r.register(JsonCodec::new());
44        r.register(CborCodec::new());
45        r.register(ProtobufCodec::new());
46        r.register(FlatbuffersCodec::new());
47        r.register(CapnpCodec::new());
48        r.register(BebopCodec::new());
49        r.register(BsonCodec::new());
50        r
51    }
52
53    /// Install a codec under its declared content-type. If a codec
54    /// was already installed under the same content-type it is
55    /// replaced.
56    pub fn register<C: WireCodec + 'static>(&mut self, codec: C) {
57        let ct = codec.content_type();
58        self.by_content_type.insert(ct, Box::new(codec));
59    }
60
61    /// Look up the codec installed under `ct`, or `None` if no codec
62    /// claims that content-type.
63    #[must_use]
64    pub fn for_content_type(&self, ct: &str) -> Option<&dyn WireCodec> {
65        self.by_content_type
66            .get(ct)
67            .map(std::convert::AsRef::as_ref)
68    }
69
70    /// Iterate over the content-types currently registered. Useful
71    /// for emitting an `Accept`-style negotiation header.
72    pub fn content_types(&self) -> impl Iterator<Item = &'static str> + '_ {
73        self.by_content_type.keys().copied()
74    }
75}
76
77impl Default for CodecRegistry {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::codec::json::JsonCodec;
87    use crate::value::{WireTypeId, WireValue};
88    use serde::{Deserialize, Serialize};
89
90    #[derive(Debug, Default, Deserialize, PartialEq, Serialize)]
91    struct Probe {
92        s: String,
93    }
94
95    impl WireValue for Probe {
96        fn wire_type_id() -> WireTypeId {
97            WireTypeId::new("test.Probe")
98        }
99    }
100
101    #[test]
102    fn register_and_lookup_by_content_type() {
103        let mut codec = JsonCodec::new();
104        codec.register::<Probe>();
105
106        let mut registry = CodecRegistry::new();
107        registry.register(codec);
108
109        let codec = registry
110            .for_content_type("application/json")
111            .expect("json codec installed");
112        assert_eq!(codec.content_type(), "application/json");
113    }
114
115    #[test]
116    fn unknown_content_type_returns_none() {
117        let registry = CodecRegistry::with_baseline();
118        assert!(registry.for_content_type("application/x-mystery").is_none());
119    }
120
121    #[test]
122    fn baseline_registers_all_seven_content_types() {
123        let r = CodecRegistry::with_baseline();
124        let cts: Vec<&'static str> = r.content_types().collect();
125        assert!(cts.contains(&"application/json"));
126        assert!(cts.contains(&"application/cbor"));
127        assert!(cts.contains(&"application/x-protobuf"));
128        assert!(cts.contains(&"application/octet-stream;schema=flatbuffers"));
129        assert!(cts.contains(&"application/capnproto"));
130        assert!(cts.contains(&"application/x-bebop"));
131        assert!(cts.contains(&"application/bson"));
132        assert_eq!(cts.len(), 7);
133    }
134
135    #[test]
136    fn register_replaces_existing_codec_for_same_content_type() {
137        // Two distinct JsonCodec instances; the second replaces the
138        // first under the shared "application/json" key. We can
139        // observe the replacement by encoding a probe value: the
140        // first instance has no registrations and would error with
141        // UnknownTypeId; the second instance registers Probe and
142        // succeeds.
143        let empty = JsonCodec::new();
144        let mut populated = JsonCodec::new();
145        populated.register::<Probe>();
146
147        let mut r = CodecRegistry::new();
148        r.register(empty);
149        r.register(populated);
150
151        let codec = r.for_content_type("application/json").expect("present");
152        let probe = Probe { s: "hello".into() };
153        let bytes = codec.encode(&probe).expect("populated codec encodes");
154        assert!(!bytes.is_empty());
155    }
156
157    #[test]
158    fn default_is_empty() {
159        let r = CodecRegistry::default();
160        assert_eq!(r.content_types().count(), 0);
161        assert!(r.for_content_type("application/json").is_none());
162    }
163}