Skip to main content

union_square/proxy/
types.rs

1//! Type definitions for the proxy module
2//!
3//! This module defines all the domain types used throughout the proxy service.
4//! All types use the `nutype` crate for validation, ensuring that invalid states
5//! are impossible to represent.
6//!
7//! ## Type Categories
8//!
9//! ### Size and Capacity Types
10//! Types for representing various sizes and capacities with validation:
11//! - `RequestSizeLimit`, `ResponseSizeLimit`: Maximum sizes for HTTP payloads
12//! - `BufferSize`, `SlotSize`: Ring buffer dimensions
13//! - `BodySize`, `DataSize`: Actual data sizes
14//!
15//! ### Identifier Types
16//! Unique identifiers with specific formats:
17//! - `RequestId`: V7 UUID for request correlation
18//! - `SessionId`: V7 UUID for session tracking
19//! - `ApiKey`: Non-empty string for authentication
20//!
21//! ### HTTP Types
22//! HTTP-specific types with validation:
23//! - `HttpMethod`, `HttpStatusCode`: Standard HTTP elements
24//! - `TargetUrl`: Validated URL for proxying
25//! - `RequestUri`: Valid URI path
26//!
27//! ### Audit Types
28//! Types for the audit system:
29//! - `AuditEvent`: Events captured during request processing
30//! - `AuditEventType`: Different types of audit events
31//! - `ErrorPhase`: When errors occurred in processing
32//!
33//! ## Example Usage
34//!
35//! ```rust,ignore
36//! use union_square::proxy::types::*;
37//!
38//! // Create validated types
39//! let request_size = RequestSizeLimit::try_new(1024 * 1024)?; // 1MB
40//! let request_id = RequestId::new(); // Generates V7 UUID
41//! let api_key = ApiKey::try_new("sk-123456")?;
42//!
43//! // Types ensure validation at compile time
44//! let config = ProxyConfig {
45//!     max_request_size: request_size,
46//!     max_response_size: ResponseSizeLimit::try_new(10 * 1024 * 1024)?,
47//!     request_timeout: Duration::from_secs(30),
48//!     ring_buffer: RingBufferConfig::default(),
49//! };
50//! ```
51
52use crate::providers::bedrock::types::AwsRegion;
53use nutype::nutype;
54use serde::{Deserialize, Serialize};
55use std::time::Duration;
56use thiserror::Error;
57use uuid::Uuid;
58
59// ========== Size and Capacity Types ==========
60
61/// Maximum size for HTTP requests in bytes
62#[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/// Maximum size for HTTP responses in bytes
69#[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/// Total buffer size for ring buffer in bytes
76#[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/// Size of individual slots in ring buffer in bytes
83#[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/// Actual size of data in a buffer slot
90#[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/// Size of HTTP body in bytes
97#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
98pub struct BodySize(usize);
99
100// ========== Count Types ==========
101
102/// Number of events dropped due to buffer overflow
103#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
104pub struct DroppedEventCount(u64);
105
106/// Number of slots in the ring buffer
107#[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// ========== Time Types ==========
114
115/// Duration in milliseconds
116#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
117pub struct DurationMillis(u64);
118
119/// Timestamp in nanoseconds since epoch
120#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
121pub struct TimestampNanos(u64);
122
123// ========== HTTP Types ==========
124
125/// HTTP method as a string (for serialization)
126#[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/// HTTP request URI
133#[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/// HTTP status code
140#[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/// HTTP header name
147#[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/// HTTP header value
154#[nutype(derive(Clone, Debug, Display, Deserialize, Serialize, From, AsRef))]
155pub struct HeaderValue(String);
156
157/// Collection of HTTP headers
158#[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// ========== Path Types ==========
196
197/// Path that bypasses authentication
198#[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/// Offset for chunked data
205#[nutype(derive(Clone, Copy, Debug, Display, Deserialize, Serialize, From, AsRef))]
206pub struct ChunkOffset(usize);
207
208// ========== Constants ==========
209
210/// Size of UUID in bytes
211pub const UUID_SIZE_BYTES: usize = 16;
212
213/// Cache line size for alignment
214pub const CACHE_LINE_SIZE: usize = 64;
215
216/// Common HTTP methods
217pub const METHOD_GET: &str = "GET";
218pub const METHOD_POST: &str = "POST";
219
220/// Common HTTP status codes
221pub const STATUS_OK: u16 = 200;
222pub const STATUS_INTERNAL_ERROR: u16 = 500;
223
224// ========== Size Constants ==========
225
226/// Common byte sizes
227pub 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
239/// Buffer sizes for testing
240pub const BUFFER_SIZE_SMALL: usize = 256; // For stress tests
241pub const BUFFER_SIZE_TEST: usize = BYTES_1KB; // Standard test size
242pub const BUFFER_SIZE_DEFAULT: usize = BYTES_1MB; // Default buffer
243
244/// Slot sizes for testing
245pub const SLOT_SIZE_TINY: usize = 64; // For stress tests
246pub const SLOT_SIZE_SMALL: usize = 128; // Small test slots
247pub const SLOT_SIZE_TEST: usize = BYTES_1KB; // Standard test slots
248
249/// Thread and iteration counts for testing
250pub const TEST_THREAD_COUNT: usize = 10;
251pub const TEST_ITERATIONS_SMALL: usize = 100;
252pub const TEST_ITERATIONS_LARGE: usize = 1000;
253
254/// Network and timeout constants
255pub 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
260/// Channel buffer sizes
261pub const CHANNEL_BUFFER_SIZE: usize = 16;
262
263/// Proxy configuration
264#[derive(Clone, Debug, Deserialize, Serialize)]
265pub struct ProxyConfig {
266    /// Maximum request size in bytes
267    pub max_request_size: RequestSizeLimit,
268    /// Maximum response size in bytes
269    pub max_response_size: ResponseSizeLimit,
270    /// Request timeout
271    pub request_timeout: Duration,
272    /// Ring buffer configuration
273    pub ring_buffer: RingBufferConfig,
274    /// AWS region for Bedrock provider
275    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/// Ring buffer configuration
291#[derive(Clone, Debug, Deserialize, Serialize)]
292pub struct RingBufferConfig {
293    /// Total buffer size in bytes
294    pub buffer_size: BufferSize,
295    /// Size of each slot in bytes
296    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/// Request ID for correlation between hot and audit paths
309#[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    /// Create a new RequestId with a v7 UUID
317    pub fn new() -> Self {
318        // Uuid::now_v7() always creates a valid v7 UUID, so unwrap() is safe here
319        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/// Target URL for proxying
330#[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/// API key for authentication
337#[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/// Session ID for tracking related requests
344#[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    /// Create a new SessionId with a v7 UUID
352    pub fn new() -> Self {
353        // Uuid::now_v7() always creates a valid v7 UUID, so unwrap() is safe here
354        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/// Errors that can occur in the proxy
365#[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
419/// Result type for proxy operations
420pub type ProxyResult<T> = Result<T, ProxyError>;
421
422/// Event captured for audit logging
423#[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/// Types of audit events
432#[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/// Phase where an error occurred
476#[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;