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 crate::args::{LoadProfile, LoadStage};
use crate::error::{AppError, AppResult, ConfigError};

use super::super::types::{LoadConfig, LoadStageConfig};

pub(super) fn parse_load_profile(load: &LoadConfig) -> AppResult<LoadProfile> {
    let initial_rpm = resolve_rpm(load.rate, load.rpm, "load")?.unwrap_or(0);

    let mut stages = Vec::new();
    if let Some(stage_configs) = load.stages.as_ref() {
        for (idx, stage) in stage_configs.iter().enumerate() {
            let duration = super::super::parse_duration_value(&stage.duration)?;
            let target_rpm = resolve_stage_rpm(stage, idx)?;
            stages.push(LoadStage {
                duration,
                target_rpm,
            });
        }
    }

    if initial_rpm == 0 && stages.is_empty() {
        return Err(AppError::config(
            ConfigError::LoadProfileMissingRateOrStages,
        ));
    }

    Ok(LoadProfile {
        initial_rpm,
        stages,
    })
}

pub(super) fn parse_simple_load(rate: Option<u64>, rpm: Option<u64>) -> AppResult<LoadProfile> {
    let initial_rpm = resolve_rpm(rate, rpm, "rate/rpm")?.unwrap_or(0);
    if initial_rpm == 0 {
        return Err(AppError::config(ConfigError::RateRpmMustBePositive));
    }

    Ok(LoadProfile {
        initial_rpm,
        stages: Vec::new(),
    })
}

fn resolve_stage_rpm(stage: &LoadStageConfig, idx: usize) -> AppResult<u64> {
    let mut configured = 0u8;
    if stage.target.is_some() {
        configured = configured.saturating_add(1);
    }
    if stage.rate.is_some() {
        configured = configured.saturating_add(1);
    }
    if stage.rpm.is_some() {
        configured = configured.saturating_add(1);
    }

    let stage_index = idx.saturating_add(1);
    if configured == 0 {
        return Err(AppError::config(ConfigError::StageMissingTargetRateRpm {
            index: stage_index,
        }));
    }
    if configured > 1 {
        return Err(AppError::config(
            ConfigError::StageConflictingTargetRateRpm { index: stage_index },
        ));
    }

    if let Some(rpm) = stage.rpm {
        return Ok(rpm);
    }

    let rate = stage.target.or(stage.rate).unwrap_or(0);
    Ok(rate.saturating_mul(60))
}

fn resolve_rpm(rate: Option<u64>, rpm: Option<u64>, context: &str) -> AppResult<Option<u64>> {
    if rate.is_some() && rpm.is_some() {
        return Err(AppError::config(ConfigError::RateRpmConflict {
            context: context.to_owned(),
        }));
    }
    if let Some(rpm) = rpm {
        return Ok(Some(rpm));
    }
    if let Some(rate) = rate {
        return Ok(Some(rate.saturating_mul(60)));
    }
    Ok(None)
}