Skip to main content

gproxy_protocol/codec/
mod.rs

1//! Bounded, runtime-neutral wire codecs.
2//!
3//! These codecs only understand transport framing and JSON syntax. They do not
4//! decode vendor objects, perform conversion, or preserve unknown application
5//! fields as a common representation.
6
7mod body;
8mod json;
9mod multipart;
10mod sse;
11
12use std::{error::Error, fmt, future::Future, pin::Pin};
13
14pub use body::read_http_body;
15pub use json::{
16    JsonArrayDecoder, JsonArrayEncoder, JsonDecoder, NdjsonDecoder, NdjsonEncoder, decode_json,
17    encode_json,
18};
19pub use multipart::{MultipartDecoder, MultipartEncoder};
20pub use sse::{SseDecoder, SseEncoder, SseEvent, SseFrame, encode_sse_done, encode_sse_event};
21
22/// A boxed codec operation future. Native futures are `Send`; wasm follows the
23/// connection stream contract and does not require `Send`.
24#[cfg(not(target_arch = "wasm32"))]
25pub type CodecFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
26
27#[cfg(target_arch = "wasm32")]
28pub type CodecFuture<'a, T> = Pin<Box<dyn Future<Output = T> + 'a>>;
29
30/// Explicit bounds required by every decoder and encoder.
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub struct CodecLimits {
33    /// Maximum incomplete input retained by a decoder.
34    pub max_buffer_bytes: u64,
35    /// Maximum encoded JSON value or SSE data event.
36    pub max_value_bytes: u64,
37    /// Maximum aggregate HTTP body bytes consumed or produced.
38    pub max_body_bytes: u64,
39    /// Maximum one line in NDJSON/SSE or one multipart header line.
40    pub max_line_bytes: u64,
41    /// Maximum one multipart part body.
42    pub max_part_bytes: u64,
43    /// Maximum multipart parts.
44    pub max_parts: usize,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
49pub enum CodecErrorKind {
50    Invalid,
51    UnexpectedEof,
52    Utf8,
53    Json,
54    Multipart,
55    Limit,
56    Transport,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
61pub enum CodecErrorStage {
62    Start,
63    Body,
64    Stream,
65    Finish,
66}
67
68/// A codec failure with an optional lower-level source.
69#[derive(Debug)]
70pub struct CodecError {
71    kind: CodecErrorKind,
72    stage: CodecErrorStage,
73    message: String,
74    source: Option<Box<dyn Error + Send + Sync + 'static>>,
75}
76
77impl CodecError {
78    pub fn new(kind: CodecErrorKind, stage: CodecErrorStage, message: impl Into<String>) -> Self {
79        Self {
80            kind,
81            stage,
82            message: message.into(),
83            source: None,
84        }
85    }
86
87    pub fn with_source(
88        kind: CodecErrorKind,
89        stage: CodecErrorStage,
90        message: impl Into<String>,
91        source: impl Into<Box<dyn Error + Send + Sync + 'static>>,
92    ) -> Self {
93        Self {
94            kind,
95            stage,
96            message: message.into(),
97            source: Some(source.into()),
98        }
99    }
100
101    pub fn kind(&self) -> CodecErrorKind {
102        self.kind
103    }
104
105    pub fn stage(&self) -> CodecErrorStage {
106        self.stage
107    }
108
109    pub fn source_error(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
110        self.source.as_deref()
111    }
112}
113
114impl fmt::Display for CodecError {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        write!(f, "{} ({:?}/{:?})", self.message, self.kind, self.stage)
117    }
118}
119
120impl Error for CodecError {
121    fn source(&self) -> Option<&(dyn Error + 'static)> {
122        self.source
123            .as_deref()
124            .map(|source| source as &(dyn Error + 'static))
125    }
126}