Skip to main content

gateway_runtime/
codec.rs

1//! # Codec
2//!
3//! ## Purpose
4//! Defines the interface and implementations for encoding and decoding gRPC messages
5//! to and from HTTP body formats. This module abstracts the wire format details, allowing
6//! the gateway to support multiple content types (e.g., JSON, Protocol Buffers).
7//!
8//! ## Scope
9//! This module provides:
10//! -   The `Codec` trait, which defines the contract for message serialization and deserialization.
11//! -   `ProtobufCodec`: A concrete implementation for the binary Protocol Buffers format (`application/octet-stream`).
12//! -   `JsonCodec`: A concrete implementation for the JSON format (`application/json`).
13//! -   `MultimediaCodec`: A codec that selects between JSON and Protocol Buffers based on MIME types.
14//!
15//! ## Position in the Architecture
16//! The `Codec` is used by the generated service registration code to unmarshal incoming HTTP request bodies
17//! into gRPC request messages and to marshal gRPC response messages back into HTTP response bodies.
18//!
19//! ## Design Constraints
20//! -   **Concurrency**: Codecs must be `Send`, `Sync`, and `'static` to allow sharing across threads.
21//! -   **Statelessness**: Implementations are generally expected to be stateless.
22//! -   **Serialization Support**: Relies on `serde` for JSON and `prost` for Protocol Buffers.
23
24#[allow(unused_imports)]
25use crate::alloc;
26use crate::errors::GatewayError;
27use alloc::string::{String, ToString};
28use alloc::vec::Vec;
29use bytes::Bytes;
30use prost::Message;
31use serde::de::DeserializeOwned;
32
33/// Defines how to encode and decode gRPC messages to/from HTTP bodies.
34///
35/// This trait abstracts the serialization logic, enabling the gateway to support various
36/// wire formats.
37pub trait Codec: Send + Sync + 'static {
38    /// Returns the content type that this codec will use for encoding.
39    ///
40    /// # Parameters
41    /// * `accept`: The `Accept` header value from the request, if any.
42    fn encoder_content_type(&self, accept: Option<&str>) -> String;
43
44    /// Encodes a message into a buffer.
45    ///
46    /// # Parameters
47    /// *   `val`: The message to encode. Must implement `prost::Message` and `serde::Serialize`.
48    /// *   `mime`: The MIME type requested (e.g. from Accept header).
49    ///
50    /// # Returns
51    /// A `Result` containing the encoded bytes as `bytes::Bytes` or a `GatewayError` on failure.
52    fn encode<T: Message + serde::Serialize>(
53        &self,
54        val: &T,
55        mime: Option<&str>,
56    ) -> Result<Bytes, GatewayError>;
57
58    /// Decodes a buffer into a message.
59    ///
60    /// # Parameters
61    /// *   `buf`: The byte slice to decode.
62    /// *   `mime`: The content type of the incoming data.
63    ///
64    /// # Returns
65    /// A `Result` containing the decoded message of type `T` or a `GatewayError` on failure.
66    fn decode<T: Message + Default + DeserializeOwned>(
67        &self,
68        buf: &[u8],
69        mime: Option<&str>,
70    ) -> Result<T, GatewayError>;
71}
72
73/// Implements `Codec` for the Protocol Buffers binary format.
74///
75/// This codec handles the `application/octet-stream` content type.
76#[derive(Debug, Clone, Copy, Default)]
77pub struct ProtobufCodec;
78
79impl Codec for ProtobufCodec {
80    fn encoder_content_type(&self, _accept: Option<&str>) -> String {
81        "application/octet-stream".to_string()
82    }
83
84    /// Encodes a message using `prost`.
85    ///
86    /// # Parameters
87    /// *   `val`: The message to encode.
88    /// *   `_mime`: Ignored.
89    ///
90    /// # Returns
91    /// The binary protobuf encoding of the message.
92    ///
93    /// # Errors
94    /// Returns `GatewayError::Encoding` if the encoding process fails.
95    fn encode<T: Message + serde::Serialize>(
96        &self,
97        val: &T,
98        _mime: Option<&str>,
99    ) -> Result<Bytes, GatewayError> {
100        let mut buf = Vec::new();
101        val.encode(&mut buf).map_err(|e| {
102            GatewayError::Encoding(
103                #[cfg(feature = "std")]
104                Box::new(e),
105                #[cfg(not(feature = "std"))]
106                e.to_string(),
107            )
108        })?;
109        Ok(Bytes::from(buf))
110    }
111
112    /// Decodes a message using `prost`.
113    ///
114    /// # Parameters
115    /// *   `buf`: The binary data to decode.
116    /// *   `_mime`: Ignored.
117    ///
118    /// # Returns
119    /// The decoded message object.
120    ///
121    /// # Errors
122    /// Returns `GatewayError::Encoding` if the data cannot be decoded into the target type.
123    fn decode<T: Message + Default + DeserializeOwned>(
124        &self,
125        buf: &[u8],
126        _mime: Option<&str>,
127    ) -> Result<T, GatewayError> {
128        T::decode(buf).map_err(|e| {
129            GatewayError::Encoding(
130                #[cfg(feature = "std")]
131                Box::new(e),
132                #[cfg(not(feature = "std"))]
133                e.to_string(),
134            )
135        })
136    }
137}
138
139/// Options for JSON encoding.
140#[derive(Debug, Clone, Default)]
141pub struct JsonEncoderOptions {
142    /// Whether to format the output in indented-form with every textual element on a new line.
143    /// If `indent` is empty, then an arbitrary indent (usually 2 spaces) is chosen if this is true.
144    pub pretty_print: bool,
145
146    /// The set of indentation characters to use in a multiline formatted output.
147    /// If non-empty, then `pretty_print` is treated as true.
148    pub indent: String,
149}
150
151/// Options for JSON decoding.
152#[derive(Debug, Clone, Default)]
153pub struct JsonDecoderOptions {
154    // Reserved for future options like recursion limit.
155}
156
157/// Implements `Codec` for the JSON format.
158///
159/// This codec handles the `application/json` content type.
160#[derive(Debug, Clone, Default)]
161pub struct JsonCodec {
162    encoder_opts: JsonEncoderOptions,
163    decoder_opts: JsonDecoderOptions,
164}
165
166impl JsonCodec {
167    /// Creates a new `JsonCodec` with default options.
168    pub fn new() -> Self {
169        Self::default()
170    }
171
172    /// Creates a new `JsonCodec` with pretty printing enabled (default indentation).
173    pub fn pretty() -> Self {
174        Self {
175            encoder_opts: JsonEncoderOptions {
176                pretty_print: true,
177                indent: String::new(),
178            },
179            decoder_opts: JsonDecoderOptions::default(),
180        }
181    }
182
183    /// Configures the codec with the given encoder options.
184    pub fn with_encoder_options(mut self, options: JsonEncoderOptions) -> Self {
185        self.encoder_opts = options;
186        self
187    }
188
189    /// Configures the codec with the given decoder options.
190    pub fn with_decoder_options(mut self, options: JsonDecoderOptions) -> Self {
191        self.decoder_opts = options;
192        self
193    }
194}
195
196impl Codec for JsonCodec {
197    fn encoder_content_type(&self, _accept: Option<&str>) -> String {
198        "application/json".to_string()
199    }
200
201    /// Encodes a message using `serde_json`.
202    ///
203    /// # Parameters
204    /// *   `val`: The message to encode.
205    /// *   `_mime`: Ignored.
206    ///
207    /// # Returns
208    /// The JSON string representation of the message as bytes.
209    ///
210    /// # Errors
211    /// Returns `GatewayError::Encoding` if serialization fails.
212    fn encode<T: Message + serde::Serialize>(
213        &self,
214        val: &T,
215        _mime: Option<&str>,
216    ) -> Result<Bytes, GatewayError> {
217        let mut buf = Vec::new();
218        let indent_str = &self.encoder_opts.indent;
219        let pretty_print = self.encoder_opts.pretty_print;
220
221        let res = if pretty_print || !indent_str.is_empty() {
222            let indent = if indent_str.is_empty() {
223                b"  "
224            } else {
225                indent_str.as_bytes()
226            };
227            let formatter = serde_json::ser::PrettyFormatter::with_indent(indent);
228            let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
229            val.serialize(&mut ser)
230        } else {
231            serde_json::to_writer(&mut buf, val)
232        };
233
234        res.map_err(|e| {
235            GatewayError::Encoding(
236                #[cfg(feature = "std")]
237                Box::new(e),
238                #[cfg(not(feature = "std"))]
239                e.to_string(),
240            )
241        })?;
242        Ok(Bytes::from(buf))
243    }
244
245    /// Decodes a message using `serde_json`.
246    ///
247    /// # Parameters
248    /// *   `buf`: The JSON data to decode.
249    /// *   `_mime`: Ignored.
250    ///
251    /// # Returns
252    /// The decoded message object.
253    ///
254    /// # Errors
255    /// Returns `GatewayError::Encoding` if the JSON is invalid or cannot map to the target type.
256    fn decode<T: Message + Default + DeserializeOwned>(
257        &self,
258        buf: &[u8],
259        _mime: Option<&str>,
260    ) -> Result<T, GatewayError> {
261        serde_json::from_slice(buf).map_err(|e| {
262            GatewayError::Encoding(
263                #[cfg(feature = "std")]
264                Box::new(e),
265                #[cfg(not(feature = "std"))]
266                e.to_string(),
267            )
268        })
269    }
270}
271
272/// Implements `Codec` for both JSON and Protocol Buffers formats.
273///
274/// This codec selects the appropriate format based on the `Content-Type` (for decoding)
275/// and `Accept` (for encoding) headers.
276#[derive(Debug, Clone, Default)]
277pub struct MultimediaCodec {
278    json: JsonCodec,
279    proto: ProtobufCodec,
280}
281
282impl MultimediaCodec {
283    /// Creates a new `MultimediaCodec` with default configuration.
284    pub fn new() -> Self {
285        Self::default()
286    }
287
288    /// Creates a new `MultimediaCodec` with the provided codec instances.
289    pub fn with_codecs(json: JsonCodec, proto: ProtobufCodec) -> Self {
290        Self { json, proto }
291    }
292}
293
294impl Codec for MultimediaCodec {
295    fn encoder_content_type(&self, accept: Option<&str>) -> String {
296        if let Some(accept) = accept {
297            if accept.contains("application/octet-stream")
298                || accept.contains("application/x-protobuf")
299            {
300                return "application/octet-stream".to_string();
301            }
302        }
303        // Default to JSON
304        "application/json".to_string()
305    }
306
307    fn encode<T: Message + serde::Serialize>(
308        &self,
309        val: &T,
310        mime: Option<&str>,
311    ) -> Result<Bytes, GatewayError> {
312        let content_type = self.encoder_content_type(mime);
313        if content_type == "application/octet-stream" {
314            self.proto.encode(val, mime)
315        } else {
316            self.json.encode(val, mime)
317        }
318    }
319
320    fn decode<T: Message + Default + DeserializeOwned>(
321        &self,
322        buf: &[u8],
323        mime: Option<&str>,
324    ) -> Result<T, GatewayError> {
325        if let Some(mime) = mime {
326            if mime.contains("application/octet-stream") || mime.contains("application/x-protobuf")
327            {
328                return self.proto.decode(buf, Some(mime));
329            }
330        }
331        self.json.decode(buf, mime)
332    }
333}
334
335#[cfg(test)]
336mod tests {
337    use super::*;
338    use serde::{Deserialize, Serialize};
339
340    #[derive(Clone, PartialEq, Message, Serialize, Deserialize)]
341    struct TestMessage {
342        #[prost(string, tag = "1")]
343        pub name: String,
344        #[prost(int32, tag = "2")]
345        pub id: i32,
346    }
347
348    #[test]
349    fn test_protobuf_codec_encoding() {
350        let codec = ProtobufCodec;
351        let msg = TestMessage {
352            name: "test".to_string(),
353            id: 123,
354        };
355        let encoded = codec.encode(&msg, None).unwrap();
356
357        let mut expected = Vec::new();
358        msg.encode(&mut expected).unwrap();
359
360        assert_eq!(encoded, Bytes::from(expected));
361        assert_eq!(codec.encoder_content_type(None), "application/octet-stream");
362    }
363
364    #[test]
365    fn test_protobuf_codec_decoding() {
366        let codec = ProtobufCodec;
367        let msg = TestMessage {
368            name: "test".to_string(),
369            id: 123,
370        };
371        let mut buf = Vec::new();
372        msg.encode(&mut buf).unwrap();
373
374        let decoded: TestMessage = codec.decode(&buf, None).unwrap();
375        assert_eq!(decoded, msg);
376    }
377
378    #[test]
379    fn test_json_codec_default_encoding() {
380        let codec = JsonCodec::new();
381        let msg = TestMessage {
382            name: "test".to_string(),
383            id: 123,
384        };
385        let encoded = codec.encode(&msg, None).unwrap();
386
387        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
388        assert_eq!(json_str, r#"{"name":"test","id":123}"#);
389        assert_eq!(codec.encoder_content_type(None), "application/json");
390    }
391
392    #[test]
393    fn test_json_codec_pretty_encoding() {
394        let codec = JsonCodec::pretty();
395        let msg = TestMessage {
396            name: "test".to_string(),
397            id: 123,
398        };
399        let encoded = codec.encode(&msg, None).unwrap();
400
401        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
402        // Check for newlines and indentation
403        assert!(json_str.contains('\n'));
404        assert!(json_str.contains("  \"name\""));
405    }
406
407    #[test]
408    fn test_json_codec_custom_indent_encoding() {
409        let options = JsonEncoderOptions {
410            pretty_print: true,
411            indent: "\t".to_string(),
412        };
413        let codec = JsonCodec::new().with_encoder_options(options);
414        let msg = TestMessage {
415            name: "test".to_string(),
416            id: 123,
417        };
418        let encoded = codec.encode(&msg, None).unwrap();
419
420        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
421        assert!(json_str.contains("\t\"name\""));
422    }
423
424    #[test]
425    fn test_json_codec_decoding() {
426        let codec = JsonCodec::new();
427        let json_data = r#"{"name":"test","id":123}"#;
428        let decoded: TestMessage = codec.decode(json_data.as_bytes(), None).unwrap();
429
430        assert_eq!(decoded.name, "test");
431        assert_eq!(decoded.id, 123);
432    }
433
434    #[test]
435    fn test_multimedia_codec_default_encoding() {
436        let codec = MultimediaCodec::new();
437        let msg = TestMessage {
438            name: "test".to_string(),
439            id: 123,
440        };
441
442        // No Accept header -> defaults to JSON
443        let encoded = codec.encode(&msg, None).unwrap();
444        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
445        assert_eq!(json_str, r#"{"name":"test","id":123}"#);
446        assert_eq!(codec.encoder_content_type(None), "application/json");
447    }
448
449    #[test]
450    fn test_multimedia_codec_json_encoding() {
451        let codec = MultimediaCodec::new();
452        let msg = TestMessage {
453            name: "test".to_string(),
454            id: 123,
455        };
456
457        let encoded = codec.encode(&msg, Some("application/json")).unwrap();
458        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
459        assert_eq!(json_str, r#"{"name":"test","id":123}"#);
460        assert_eq!(
461            codec.encoder_content_type(Some("application/json")),
462            "application/json"
463        );
464    }
465
466    #[test]
467    fn test_multimedia_codec_protobuf_encoding() {
468        let codec = MultimediaCodec::new();
469        let msg = TestMessage {
470            name: "test".to_string(),
471            id: 123,
472        };
473
474        let encoded = codec
475            .encode(&msg, Some("application/octet-stream"))
476            .unwrap();
477        let mut expected = Vec::new();
478        msg.encode(&mut expected).unwrap();
479        assert_eq!(encoded, Bytes::from(expected));
480        assert_eq!(
481            codec.encoder_content_type(Some("application/octet-stream")),
482            "application/octet-stream"
483        );
484    }
485
486    #[test]
487    fn test_multimedia_codec_protobuf_alias_encoding() {
488        let codec = MultimediaCodec::new();
489        let msg = TestMessage {
490            name: "test".to_string(),
491            id: 123,
492        };
493
494        // Check x-protobuf alias
495        let encoded = codec.encode(&msg, Some("application/x-protobuf")).unwrap();
496        let mut expected = Vec::new();
497        msg.encode(&mut expected).unwrap();
498        assert_eq!(encoded, Bytes::from(expected));
499        assert_eq!(
500            codec.encoder_content_type(Some("application/x-protobuf")),
501            "application/octet-stream"
502        );
503    }
504
505    #[test]
506    fn test_multimedia_codec_wildcard_json_decoding() {
507        let codec = MultimediaCodec::new();
508        let json_data = r#"{"name":"test","id":123}"#;
509
510        // No Content-Type -> defaults to JSON
511        let decoded: TestMessage = codec.decode(json_data.as_bytes(), None).unwrap();
512        assert_eq!(decoded.name, "test");
513        assert_eq!(decoded.id, 123);
514    }
515
516    #[test]
517    fn test_multimedia_codec_explicit_json_decoding() {
518        let codec = MultimediaCodec::new();
519        let json_data = r#"{"name":"test","id":123}"#;
520
521        let decoded: TestMessage = codec
522            .decode(json_data.as_bytes(), Some("application/json"))
523            .unwrap();
524        assert_eq!(decoded.name, "test");
525        assert_eq!(decoded.id, 123);
526    }
527
528    #[test]
529    fn test_multimedia_codec_protobuf_decoding() {
530        let codec = MultimediaCodec::new();
531        let msg = TestMessage {
532            name: "test".to_string(),
533            id: 123,
534        };
535        let mut buf = Vec::new();
536        msg.encode(&mut buf).unwrap();
537
538        let decoded: TestMessage = codec
539            .decode(&buf, Some("application/octet-stream"))
540            .unwrap();
541        assert_eq!(decoded, msg);
542    }
543
544    #[test]
545    fn test_multimedia_codec_with_custom_codecs() {
546        let json_codec = JsonCodec::pretty();
547        let proto_codec = ProtobufCodec;
548        let codec = MultimediaCodec::with_codecs(json_codec, proto_codec);
549
550        let msg = TestMessage {
551            name: "test".to_string(),
552            id: 123,
553        };
554
555        // Should use pretty JSON
556        let encoded = codec.encode(&msg, None).unwrap();
557        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
558        assert!(json_str.contains('\n'));
559    }
560
561    #[test]
562    fn test_json_decoding_error() {
563        let codec = JsonCodec::new();
564        let bad_json = r#"{"name": 123}"#; // name expects string, got int
565        let result: Result<TestMessage, _> = codec.decode(bad_json.as_bytes(), None);
566        assert!(result.is_err());
567    }
568
569    #[test]
570    fn test_protobuf_decoding_error() {
571        let codec = ProtobufCodec;
572        let bad_proto = vec![255, 255, 255]; // Invalid varint or similar garbage
573        let result: Result<TestMessage, _> = codec.decode(&bad_proto, None);
574        assert!(result.is_err());
575    }
576
577    #[test]
578    fn test_multimedia_fallback_decoding() {
579        let codec = MultimediaCodec::new();
580        // Unknown content type -> defaults to JSON
581        let json_data = r#"{"name":"test","id":123}"#;
582        let decoded: TestMessage = codec
583            .decode(json_data.as_bytes(), Some("text/plain"))
584            .unwrap();
585        assert_eq!(decoded.name, "test");
586    }
587
588    #[test]
589    fn test_multimedia_fallback_encoding() {
590        let codec = MultimediaCodec::new();
591        let msg = TestMessage {
592            name: "test".to_string(),
593            id: 123,
594        };
595        // Unknown accept type -> defaults to JSON
596        let encoded = codec.encode(&msg, Some("text/html")).unwrap();
597        let json_str = String::from_utf8(encoded.to_vec()).unwrap();
598        assert_eq!(json_str, r#"{"name":"test","id":123}"#);
599    }
600
601    #[test]
602    fn test_json_codec_empty_input() {
603        let codec = JsonCodec::new();
604        let result: Result<TestMessage, _> = codec.decode(&[], None);
605        assert!(result.is_err()); // Empty JSON is invalid for a struct
606    }
607
608    #[test]
609    fn test_protobuf_codec_empty_input() {
610        let codec = ProtobufCodec;
611        let result: Result<TestMessage, _> = codec.decode(&[], None);
612        // Empty bytes are valid for protobuf (defaults everything)
613        assert!(result.is_ok());
614        let decoded = result.unwrap();
615        assert_eq!(decoded.name, "");
616        assert_eq!(decoded.id, 0);
617    }
618}