1use crate::providers::bedrock::types::AwsRegion;
53use nutype::nutype;
54use serde::{Deserialize, Serialize};
55use std::time::Duration;
56use thiserror::Error;
57use uuid::Uuid;
58
59#[nutype(
63 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
64 validate(predicate = |size: &usize| *size > 0),
65)]
66pub struct RequestSizeLimit(usize);
67
68#[nutype(
70 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
71 validate(predicate = |size: &usize| *size > 0),
72)]
73pub struct ResponseSizeLimit(usize);
74
75#[nutype(
77 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
78 validate(predicate = |size: &usize| *size > 0 && size.is_power_of_two()),
79)]
80pub struct BufferSize(usize);
81
82#[nutype(
84 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
85 validate(predicate = |size: &usize| *size > 0),
86)]
87pub struct SlotSize(usize);
88
89#[nutype(
91 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
92 validate(predicate = |size: &usize| *size > 0),
93)]
94pub struct DataSize(usize);
95
96#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
98pub struct BodySize(usize);
99
100#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
104pub struct DroppedEventCount(u64);
105
106#[nutype(
108 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
109 validate(predicate = |count: &usize| *count > 0 && count.is_power_of_two()),
110)]
111pub struct SlotCount(usize);
112
113#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
117pub struct DurationMillis(u64);
118
119#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
121pub struct TimestampNanos(u64);
122
123#[nutype(
127 derive(Clone, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
128 validate(predicate = |s: &str| !s.is_empty()),
129)]
130pub struct HttpMethod(String);
131
132#[nutype(
134 derive(Clone, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
135 validate(predicate = |s: &str| !s.is_empty()),
136)]
137pub struct RequestUri(String);
138
139#[nutype(
141 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
142 validate(predicate = |code: &u16| (100..=599).contains(code)),
143)]
144pub struct HttpStatusCode(u16);
145
146#[nutype(
148 derive(Clone, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
149 validate(predicate = |s: &str| !s.is_empty()),
150)]
151pub struct HeaderName(String);
152
153#[nutype(derive(Clone, Debug, Display, Deserialize, Serialize, From, AsRef))]
155pub struct HeaderValue(String);
156
157#[derive(Clone, Debug, Deserialize, Serialize)]
159pub struct Headers(Vec<(HeaderName, HeaderValue)>);
160
161impl Headers {
162 pub fn new() -> Self {
163 Self(Vec::new())
164 }
165
166 pub fn from_vec(headers: Vec<(String, String)>) -> Result<Self, ProxyError> {
167 let typed_headers = headers
168 .into_iter()
169 .map(|(name, value)| {
170 Ok((
171 HeaderName::try_new(name)
172 .map_err(|e| ProxyError::Internal(format!("Invalid header name: {e}")))?,
173 HeaderValue::from(value),
174 ))
175 })
176 .collect::<Result<Vec<_>, ProxyError>>()?;
177 Ok(Self(typed_headers))
178 }
179
180 pub fn as_vec(&self) -> &Vec<(HeaderName, HeaderValue)> {
181 &self.0
182 }
183
184 pub fn into_vec(self) -> Vec<(HeaderName, HeaderValue)> {
185 self.0
186 }
187}
188
189impl Default for Headers {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[nutype(
199 derive(Clone, Debug, Display, Hash, PartialEq, Eq, Deserialize, Serialize, TryFrom, AsRef),
200 validate(predicate = |s: &str| s.starts_with('/')),
201)]
202pub struct BypassPath(String);
203
204#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
206pub struct ChunkOffset(usize);
207
208pub const UUID_SIZE_BYTES: usize = 16;
212
213pub const CACHE_LINE_SIZE: usize = 64;
215
216pub const METHOD_GET: &str = "GET";
218pub const METHOD_POST: &str = "POST";
219
220pub const STATUS_OK: u16 = 200;
222pub const STATUS_INTERNAL_ERROR: u16 = 500;
223
224pub const BYTES_1KB: usize = 1024;
228pub const BYTES_2KB: usize = 2 * BYTES_1KB;
229pub const BYTES_16KB: usize = 16 * BYTES_1KB;
230pub const BYTES_32KB: usize = 32 * BYTES_1KB;
231pub const BYTES_64KB: usize = 64 * BYTES_1KB;
232pub const BYTES_128KB: usize = 128 * BYTES_1KB;
233pub const BYTES_1MB: usize = 1024 * BYTES_1KB;
234pub const BYTES_2MB: usize = 2 * BYTES_1MB;
235pub const BYTES_10MB: usize = 10 * BYTES_1MB;
236pub const BYTES_512MB: usize = 512 * BYTES_1MB;
237pub const BYTES_1GB: usize = 1024 * BYTES_1MB;
238
239pub const BUFFER_SIZE_SMALL: usize = 256; pub const BUFFER_SIZE_TEST: usize = BYTES_1KB; pub const BUFFER_SIZE_DEFAULT: usize = BYTES_1MB; pub const SLOT_SIZE_TINY: usize = 64; pub const SLOT_SIZE_SMALL: usize = 128; pub const SLOT_SIZE_TEST: usize = BYTES_1KB; pub const TEST_THREAD_COUNT: usize = 10;
251pub const TEST_ITERATIONS_SMALL: usize = 100;
252pub const TEST_ITERATIONS_LARGE: usize = 1000;
253
254pub const TEST_PORT_BASE: u16 = 8080;
256pub const TIMEOUT_SHORT_MS: u64 = 100;
257pub const TIMEOUT_DEFAULT_SECS: u64 = 30;
258pub const TIMEOUT_LONG_SECS: u64 = 60;
259
260pub const CHANNEL_BUFFER_SIZE: usize = 16;
262
263#[derive(Clone, Debug, Deserialize, Serialize)]
265pub struct ProxyConfig {
266 pub max_request_size: RequestSizeLimit,
268 pub max_response_size: ResponseSizeLimit,
270 pub request_timeout: Duration,
272 pub ring_buffer: RingBufferConfig,
274 pub bedrock_region: Option<AwsRegion>,
276}
277
278impl Default for ProxyConfig {
279 fn default() -> Self {
280 Self {
281 max_request_size: RequestSizeLimit::try_new(BYTES_10MB).expect("10MB is valid"),
282 max_response_size: ResponseSizeLimit::try_new(BYTES_10MB).expect("10MB is valid"),
283 request_timeout: Duration::from_secs(TIMEOUT_DEFAULT_SECS),
284 ring_buffer: RingBufferConfig::default(),
285 bedrock_region: None,
286 }
287 }
288}
289
290#[derive(Clone, Debug, Deserialize, Serialize)]
292pub struct RingBufferConfig {
293 pub buffer_size: BufferSize,
295 pub slot_size: SlotSize,
297}
298
299impl Default for RingBufferConfig {
300 fn default() -> Self {
301 Self {
302 buffer_size: BufferSize::try_new(BYTES_1GB).expect("1GB is valid power of 2"),
303 slot_size: SlotSize::try_new(BYTES_64KB).expect("64KB is valid"),
304 }
305 }
306}
307
308#[nutype(
310 derive(Clone, Copy, Debug, Display, Hash, PartialEq, Eq, Deserialize, Serialize, TryFrom, AsRef),
311 validate(predicate = |id: &Uuid| id.get_version_num() == 7),
312)]
313pub struct RequestId(Uuid);
314
315impl RequestId {
316 pub fn new() -> Self {
318 Self::try_new(Uuid::now_v7()).unwrap()
320 }
321}
322
323impl Default for RequestId {
324 fn default() -> Self {
325 Self::new()
326 }
327}
328
329#[nutype(
331 derive(Clone, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
332 validate(predicate = |s: &str| s.starts_with("http://") || s.starts_with("https://")),
333)]
334pub struct TargetUrl(String);
335
336#[nutype(
338 derive(Clone, Debug, Hash, PartialEq, Eq, Deserialize, Serialize, TryFrom, AsRef),
339 validate(predicate = |s: &str| !s.is_empty()),
340)]
341pub struct ApiKey(String);
342
343#[nutype(
345 derive(Clone, Copy, Debug, Display, Deserialize, Serialize, TryFrom, AsRef),
346 validate(predicate = |id: &Uuid| id.get_version_num() == 7),
347)]
348pub struct SessionId(Uuid);
349
350impl SessionId {
351 pub fn new() -> Self {
353 Self::try_new(Uuid::now_v7()).unwrap()
355 }
356}
357
358impl Default for SessionId {
359 fn default() -> Self {
360 Self::new()
361 }
362}
363
364#[derive(Error, Debug)]
366pub enum ProxyError {
367 #[error("Request too large: {size} bytes (max: {max_size} bytes)")]
368 RequestTooLarge {
369 size: BodySize,
370 max_size: RequestSizeLimit,
371 },
372
373 #[error("Response too large: {size} bytes (max: {max_size} bytes)")]
374 ResponseTooLarge {
375 size: BodySize,
376 max_size: ResponseSizeLimit,
377 },
378
379 #[error("Request timeout after {0:?}")]
380 RequestTimeout(Duration),
381
382 #[error("Invalid target URL: {0}")]
383 InvalidTargetUrl(String),
384
385 #[error("Ring buffer overflow: {dropped} events dropped")]
386 RingBufferOverflow { dropped: DroppedEventCount },
387
388 #[error("HTTP error: {0}")]
389 HttpError(#[from] http::Error),
390
391 #[error("Hyper error: {0}")]
392 HyperError(#[from] hyper::Error),
393
394 #[error("IO error: {0}")]
395 IoError(#[from] std::io::Error),
396
397 #[error("Serialization error: {0}")]
398 SerializationError(#[from] serde_json::Error),
399
400 #[error("Internal error: {0}")]
401 Internal(String),
402
403 #[error("Invalid HTTP method: {0}")]
404 InvalidHttpMethod(String),
405
406 #[error("Invalid request URI: {0}")]
407 InvalidRequestUri(String),
408
409 #[error("Invalid HTTP status code: {0}")]
410 InvalidHttpStatusCode(u16),
411
412 #[error("Invalid header: {name}")]
413 InvalidHeader { name: String },
414
415 #[error("Failed to create audit event: {0}")]
416 AuditEventCreationFailed(String),
417}
418
419pub type ProxyResult<T> = Result<T, ProxyError>;
421
422#[derive(Clone, Debug, Serialize, Deserialize)]
424pub struct AuditEvent {
425 pub request_id: RequestId,
426 pub session_id: SessionId,
427 pub timestamp: chrono::DateTime<chrono::Utc>,
428 pub event_type: AuditEventType,
429}
430
431#[derive(Clone, Debug, Serialize, Deserialize)]
433pub enum AuditEventType {
434 RequestReceived {
435 method: HttpMethod,
436 uri: RequestUri,
437 headers: Headers,
438 body_size: BodySize,
439 },
440 RequestForwarded {
441 target_url: TargetUrl,
442 start_time: chrono::DateTime<chrono::Utc>,
443 },
444 ResponseReceived {
445 status: HttpStatusCode,
446 headers: Headers,
447 body_size: BodySize,
448 duration_ms: DurationMillis,
449 },
450 ResponseReturned {
451 duration_ms: DurationMillis,
452 },
453 RequestBody {
454 content: Vec<u8>,
455 truncated: bool,
456 },
457 ResponseBody {
458 content: Vec<u8>,
459 truncated: bool,
460 },
461 RequestChunk {
462 offset: ChunkOffset,
463 data: Vec<u8>,
464 },
465 ResponseChunk {
466 offset: ChunkOffset,
467 data: Vec<u8>,
468 },
469 Error {
470 error: String,
471 phase: ErrorPhase,
472 },
473}
474
475#[derive(Clone, Debug, Serialize, Deserialize)]
477pub enum ErrorPhase {
478 RequestParsing,
479 RequestForwarding,
480 ResponseReceiving,
481 ResponseReturning,
482 AuditRecording,
483}
484
485#[cfg(test)]
486#[path = "error_handling_tests.rs"]
487mod error_handling_tests;