strest 0.1.10

Blazing-fast async HTTP load tester in Rust - lock-free design, real-time stats, distributed runs, and optional chart exports for high-load API testing.
Documentation
use std::collections::BTreeMap;
use std::time::Duration;

use serde::Deserialize;

use crate::args::{HttpMethod, OutputFormat};
use crate::error::{AppResult, ConfigError};
use crate::sinks::config::SinksConfig;

#[derive(Debug, Default, Deserialize)]
pub struct ConfigFile {
    pub method: Option<HttpMethod>,
    pub url: Option<String>,
    pub urls_from_file: Option<bool>,
    pub rand_regex_url: Option<bool>,
    pub max_repeat: Option<usize>,
    pub dump_urls: Option<usize>,
    pub headers: Option<Vec<String>>,
    pub accept: Option<String>,
    pub content_type: Option<String>,
    pub data: Option<String>,
    pub form: Option<Vec<String>>,
    pub data_file: Option<String>,
    pub data_lines: Option<String>,
    pub basic_auth: Option<String>,
    pub aws_session: Option<String>,
    pub aws_sigv4: Option<String>,
    pub duration: Option<u64>,
    pub wait_ongoing_requests_after_deadline: Option<bool>,
    pub requests: Option<u64>,
    pub timeout: Option<DurationValue>,
    pub connect_timeout: Option<DurationValue>,
    pub warmup: Option<DurationValue>,
    pub status: Option<u16>,
    pub redirect: Option<u32>,
    pub disable_keepalive: Option<bool>,
    pub disable_compression: Option<bool>,
    pub pool_max_idle_per_host: Option<usize>,
    pub pool_idle_timeout_ms: Option<u64>,
    pub charts_path: Option<String>,
    pub no_charts: Option<bool>,
    pub charts_latency_bucket_ms: Option<u64>,
    pub no_ua: Option<bool>,
    pub authorized: Option<bool>,
    pub tmp_path: Option<String>,
    pub keep_tmp: Option<bool>,
    pub output: Option<String>,
    pub output_format: Option<OutputFormat>,
    pub time_unit: Option<crate::args::TimeUnit>,
    pub export_csv: Option<String>,
    pub export_json: Option<String>,
    pub export_jsonl: Option<String>,
    pub db_url: Option<String>,
    pub log_shards: Option<usize>,
    pub no_ui: Option<bool>,
    pub ui_window_ms: Option<u64>,
    pub summary: Option<bool>,
    pub tls_min: Option<crate::args::TlsVersion>,
    pub tls_max: Option<crate::args::TlsVersion>,
    pub cacert: Option<String>,
    pub cert: Option<String>,
    pub key: Option<String>,
    pub insecure: Option<bool>,
    pub http2: Option<bool>,
    pub http2_parallel: Option<usize>,
    pub http3: Option<bool>,
    pub http_version: Option<crate::args::HttpVersion>,
    pub alpn: Option<Vec<String>>,
    #[serde(alias = "proxy")]
    pub proxy_url: Option<String>,
    pub proxy_headers: Option<Vec<String>>,
    pub proxy_http_version: Option<crate::args::HttpVersion>,
    pub proxy_http2: Option<bool>,
    #[serde(alias = "concurrency", alias = "connections")]
    pub max_tasks: Option<usize>,
    pub spawn_rate: Option<usize>,
    pub spawn_interval: Option<u64>,
    pub rate: Option<u64>,
    pub rpm: Option<u64>,
    pub burst_delay: Option<DurationValue>,
    pub burst_rate: Option<usize>,
    pub latency_correction: Option<bool>,
    pub connect_to: Option<Vec<String>>,
    pub host: Option<String>,
    pub ipv6: Option<bool>,
    pub ipv4: Option<bool>,
    pub no_pre_lookup: Option<bool>,
    pub no_color: Option<bool>,
    pub fps: Option<u32>,
    pub stats_success_breakdown: Option<bool>,
    pub unix_socket: Option<String>,
    pub load: Option<LoadConfig>,
    pub metrics_range: Option<String>,
    pub metrics_max: Option<usize>,
    pub rss_log_ms: Option<u64>,
    pub alloc_profiler_ms: Option<u64>,
    pub alloc_profiler_dump_ms: Option<u64>,
    pub alloc_profiler_dump_path: Option<String>,
    pub scenario: Option<ScenarioConfig>,
    pub scenarios: Option<BTreeMap<String, ScenarioConfig>>,
    pub script: Option<String>,
    pub plugin: Option<Vec<String>>,
    pub sinks: Option<SinksConfig>,
    pub distributed: Option<DistributedConfig>,
}

#[derive(Debug, Default, Deserialize)]
pub struct LoadConfig {
    pub rate: Option<u64>,
    pub rpm: Option<u64>,
    pub stages: Option<Vec<LoadStageConfig>>,
}

#[derive(Debug, Default, Deserialize)]
pub struct LoadStageConfig {
    pub duration: String,
    pub target: Option<u64>,
    pub rate: Option<u64>,
    pub rpm: Option<u64>,
}

#[derive(Debug, Default, Deserialize, Clone)]
pub struct ScenarioConfig {
    pub schema_version: Option<u32>,
    pub base_url: Option<String>,
    pub method: Option<HttpMethod>,
    pub headers: Option<Vec<String>>,
    pub data: Option<String>,
    pub vars: Option<BTreeMap<String, String>>,
    pub steps: Vec<ScenarioStepConfig>,
}

pub const SCENARIO_SCHEMA_VERSION: u32 = 1;

#[derive(Debug, Default, Deserialize, Clone)]
pub struct ScenarioStepConfig {
    pub name: Option<String>,
    pub method: Option<HttpMethod>,
    pub url: Option<String>,
    pub path: Option<String>,
    pub headers: Option<Vec<String>>,
    pub data: Option<String>,
    pub assert_status: Option<u16>,
    pub assert_body_contains: Option<String>,
    pub think_time: Option<DurationValue>,
    pub vars: Option<BTreeMap<String, String>>,
}

#[derive(Debug, Default, Deserialize)]
pub struct DistributedConfig {
    pub role: Option<String>,
    pub controller_mode: Option<crate::args::ControllerMode>,
    pub listen: Option<String>,
    pub control_listen: Option<String>,
    pub control_auth_token: Option<String>,
    pub join: Option<String>,
    pub auth_token: Option<String>,
    pub agent_id: Option<String>,
    pub weight: Option<u64>,
    pub min_agents: Option<usize>,
    pub agent_wait_timeout_ms: Option<u64>,
    pub agent_standby: Option<bool>,
    pub agent_reconnect_ms: Option<u64>,
    pub agent_heartbeat_interval_ms: Option<u64>,
    pub agent_heartbeat_timeout_ms: Option<u64>,
    pub stream_summaries: Option<bool>,
    pub stream_interval_ms: Option<u64>,
}

#[derive(Debug, Clone, Deserialize)]
#[serde(untagged)]
pub enum DurationValue {
    Seconds(u64),
    Text(String),
}

impl DurationValue {
    pub(crate) fn to_duration(&self) -> AppResult<Duration> {
        match self {
            DurationValue::Seconds(secs) => {
                if *secs == 0 {
                    Err(crate::error::AppError::config(ConfigError::DurationZero))
                } else {
                    Ok(Duration::from_secs(*secs))
                }
            }
            DurationValue::Text(text) => super::parse_duration_value(text),
        }
    }
}