use bon::Builder;
use conflaguration::{Settings, Validate, ValidationMessage};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Builder, Deserialize, Serialize, Settings)]
#[settings(prefix = "HTTP")]
#[builder(derive(Clone, Debug))]
pub struct HttpTuning {
#[setting(default = 8192)]
#[serde(default = "default_response_buffer_bytes")]
#[builder(default = default_response_buffer_bytes())]
pub response_buffer_bytes: usize,
#[setting(default = 16384)]
#[serde(default = "default_read_buffer_bytes")]
#[builder(default = default_read_buffer_bytes())]
pub read_buffer_bytes: usize,
#[setting(default = 32)]
#[serde(default = "default_headers_capacity")]
#[builder(default = default_headers_capacity())]
pub headers_capacity: usize,
#[setting(default = 100)]
#[serde(default = "default_h1_max_headers")]
#[builder(default = default_h1_max_headers())]
pub h1_max_headers: usize,
#[setting(default = 16384)]
#[serde(default = "default_h2_max_frame_size")]
#[builder(default = default_h2_max_frame_size())]
pub h2_max_frame_size: u32,
#[setting(default = 100)]
#[serde(default = "default_h2_max_concurrent_streams")]
#[builder(default = default_h2_max_concurrent_streams())]
pub h2_max_concurrent_streams: u32,
#[setting(default = 4096)]
#[serde(default = "default_h2_header_table_size")]
#[builder(default = default_h2_header_table_size())]
pub h2_header_table_size: u32,
#[setting(skip)]
#[serde(default)]
pub max_body_bytes: Option<usize>,
}
impl Default for HttpTuning {
fn default() -> Self {
Self::builder().build()
}
}
impl Validate for HttpTuning {
fn validate(&self) -> conflaguration::Result<()> {
let mut errors = Vec::new();
if self.response_buffer_bytes == 0 {
errors.push(ValidationMessage::new(
"response_buffer_bytes",
"must be > 0",
));
}
if self.read_buffer_bytes == 0 {
errors.push(ValidationMessage::new("read_buffer_bytes", "must be > 0"));
}
if !(16_384..=16_777_215).contains(&self.h2_max_frame_size) {
errors.push(ValidationMessage::new(
"h2_max_frame_size",
"must be in [16384, 16777215] per RFC 7540 §6.5.2",
));
}
if errors.is_empty() {
Ok(())
} else {
Err(conflaguration::Error::Validation { errors })
}
}
}
fn default_response_buffer_bytes() -> usize {
8192
}
fn default_read_buffer_bytes() -> usize {
16_384
}
fn default_headers_capacity() -> usize {
32
}
fn default_h1_max_headers() -> usize {
100
}
fn default_h2_max_frame_size() -> u32 {
16_384
}
fn default_h2_max_concurrent_streams() -> u32 {
100
}
fn default_h2_header_table_size() -> u32 {
4_096
}
#[derive(Debug, Clone, Builder, Deserialize, Serialize, Settings)]
#[settings(prefix = "ZSTD")]
#[builder(derive(Clone, Debug))]
pub struct ZstdTuning {
#[setting(default = 3)]
#[serde(default = "default_zstd_level")]
#[builder(default = default_zstd_level())]
pub compression_level: i32,
}
impl Default for ZstdTuning {
fn default() -> Self {
Self::builder().build()
}
}
impl Validate for ZstdTuning {
fn validate(&self) -> conflaguration::Result<()> {
if !(1..=22).contains(&self.compression_level) {
return Err(conflaguration::Error::Validation {
errors: vec![ValidationMessage::new(
"compression_level",
"must be in [1, 22] per zstd",
)],
});
}
Ok(())
}
}
fn default_zstd_level() -> i32 {
3
}
#[derive(Debug, Clone, Builder, Deserialize, Serialize, Settings)]
#[settings(prefix = "BUFFER_POOL")]
#[builder(derive(Clone, Debug))]
pub struct BufferPoolTuning {
#[setting(default = 256)]
#[serde(default = "default_pool_depth")]
#[builder(default = default_pool_depth())]
pub depth_per_worker: usize,
#[setting(default = 16384)]
#[serde(default = "default_buffer_bytes")]
#[builder(default = default_buffer_bytes())]
pub buffer_bytes: usize,
}
impl Default for BufferPoolTuning {
fn default() -> Self {
Self::builder().build()
}
}
impl Validate for BufferPoolTuning {
fn validate(&self) -> conflaguration::Result<()> {
let mut errors = Vec::new();
if self.depth_per_worker == 0 {
errors.push(ValidationMessage::new("depth_per_worker", "must be > 0"));
}
if self.buffer_bytes == 0 {
errors.push(ValidationMessage::new("buffer_bytes", "must be > 0"));
}
if errors.is_empty() {
Ok(())
} else {
Err(conflaguration::Error::Validation { errors })
}
}
}
fn default_pool_depth() -> usize {
256
}
fn default_buffer_bytes() -> usize {
16_384
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn defaults_are_valid() {
HttpTuning::default()
.validate()
.expect("http defaults valid");
ZstdTuning::default()
.validate()
.expect("zstd defaults valid");
BufferPoolTuning::default()
.validate()
.expect("pool defaults valid");
}
#[test]
fn builder_constructs_with_defaults_and_overrides() {
let custom = HttpTuning::builder().response_buffer_bytes(32_768).build();
assert_eq!(custom.response_buffer_bytes, 32_768);
assert_eq!(custom.read_buffer_bytes, default_read_buffer_bytes());
assert_eq!(custom.h2_max_frame_size, default_h2_max_frame_size());
}
#[test]
fn h2_max_frame_size_under_minimum_fails_validation() {
let bad = HttpTuning::builder().h2_max_frame_size(1024).build();
let outcome = bad.validate();
assert!(outcome.is_err());
}
#[test]
fn zstd_level_above_22_fails_validation() {
let bad = ZstdTuning::builder().compression_level(50).build();
let outcome = bad.validate();
assert!(outcome.is_err());
}
}