Skip to main content

wip_http/
error.rs

1use std::error::Error;
2use std::fmt::{self, Display, Formatter};
3
4/// Caller-selected hard limits applied before or while decoding JSON.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub struct Limits {
7    /// Largest accepted request body in bytes.
8    pub(crate) max_request_bytes: usize,
9    /// Largest accepted response body in bytes.
10    pub(crate) max_response_bytes: usize,
11    /// Largest accepted JSON container nesting depth.
12    pub(crate) max_nesting: usize,
13}
14
15impl Limits {
16    /// Creates a nonzero limit set.
17    pub fn new(
18        max_request_bytes: usize,
19        max_response_bytes: usize,
20        max_nesting: usize,
21    ) -> Result<Self, LimitConfigurationError> {
22        if max_request_bytes == 0 {
23            return Err(LimitConfigurationError::ZeroRequestBytes);
24        }
25        if max_response_bytes == 0 {
26            return Err(LimitConfigurationError::ZeroResponseBytes);
27        }
28        if max_nesting == 0 {
29            return Err(LimitConfigurationError::ZeroNesting);
30        }
31        Ok(Self {
32            max_request_bytes,
33            max_response_bytes,
34            max_nesting,
35        })
36    }
37
38    /// Returns the request body byte limit.
39    #[must_use]
40    pub const fn max_request_bytes(self) -> usize {
41        self.max_request_bytes
42    }
43
44    /// Returns the response body byte limit.
45    #[must_use]
46    pub const fn max_response_bytes(self) -> usize {
47        self.max_response_bytes
48    }
49
50    /// Returns the JSON container nesting limit.
51    #[must_use]
52    pub const fn max_nesting(self) -> usize {
53        self.max_nesting
54    }
55}
56
57/// Why a [`Limits`] configuration is not bounded.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum LimitConfigurationError {
60    /// Request-byte limit was zero.
61    ZeroRequestBytes,
62    /// Response-byte limit was zero.
63    ZeroResponseBytes,
64    /// Nesting limit was zero.
65    ZeroNesting,
66}
67
68impl Display for LimitConfigurationError {
69    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
70        formatter.write_str(match self {
71            Self::ZeroRequestBytes => "request byte limit must be nonzero",
72            Self::ZeroResponseBytes => "response byte limit must be nonzero",
73            Self::ZeroNesting => "nesting limit must be nonzero",
74        })
75    }
76}
77
78impl Error for LimitConfigurationError {}
79
80/// Whether a body is being decoded as a request or response.
81#[derive(Debug, Clone, Copy, PartialEq, Eq)]
82pub enum BodyKind {
83    /// An inbound server request.
84    Request,
85    /// An inbound client response.
86    Response,
87}
88
89/// A malformed or locally disallowed wire representation.
90#[derive(Debug)]
91pub enum CodecError {
92    /// The body exceeds its configured byte limit.
93    BodyTooLarge {
94        /// Actual body size.
95        actual: usize,
96        /// Configured maximum.
97        maximum: usize,
98    },
99    /// JSON syntax is invalid.
100    InvalidJson(serde_json::Error),
101    /// A JSON container exceeds the configured nesting depth.
102    NestingTooDeep {
103        /// Configured maximum.
104        maximum: usize,
105    },
106    /// A required field is absent.
107    MissingField {
108        /// Field name.
109        field: String,
110    },
111    /// An undeclared field is present.
112    UnknownField {
113        /// Field name.
114        field: String,
115    },
116    /// A field has an invalid JSON type or semantic representation.
117    InvalidField {
118        /// Field name or value location.
119        field: String,
120        /// Stable human-readable reason.
121        reason: String,
122    },
123    /// An optional field was represented by JSON null rather than omission.
124    NullOptionalField {
125        /// Field name.
126        field: String,
127    },
128    /// Base64 is not canonical RFC 4648 Section 4 encoding.
129    InvalidBase64 {
130        /// Field name or value location.
131        field: String,
132    },
133    /// Logical protocol validation rejected the decoded representation.
134    ProtocolValidation(wip_protocol::ValidationError),
135}
136
137impl Display for CodecError {
138    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
139        match self {
140            Self::BodyTooLarge { actual, maximum } => {
141                write!(formatter, "body has {actual} bytes; maximum is {maximum}")
142            }
143            Self::InvalidJson(error) => write!(formatter, "invalid JSON: {error}"),
144            Self::NestingTooDeep { maximum } => {
145                write!(formatter, "JSON nesting exceeds maximum {maximum}")
146            }
147            Self::MissingField { field } => write!(formatter, "missing field `{field}`"),
148            Self::UnknownField { field } => write!(formatter, "unknown field `{field}`"),
149            Self::InvalidField { field, reason } => {
150                write!(formatter, "invalid field `{field}`: {reason}")
151            }
152            Self::NullOptionalField { field } => {
153                write!(
154                    formatter,
155                    "optional field `{field}` must be omitted instead of null"
156                )
157            }
158            Self::InvalidBase64 { field } => {
159                write!(formatter, "field `{field}` is not canonical base64")
160            }
161            Self::ProtocolValidation(error) => error.fmt(formatter),
162        }
163    }
164}
165
166impl Error for CodecError {
167    fn source(&self) -> Option<&(dyn Error + 'static)> {
168        match self {
169            Self::InvalidJson(error) => Some(error),
170            Self::ProtocolValidation(error) => Some(error),
171            _ => None,
172        }
173    }
174}
175
176impl From<wip_protocol::ValidationError> for CodecError {
177    fn from(value: wip_protocol::ValidationError) -> Self {
178        Self::ProtocolValidation(value)
179    }
180}