refactor(runner): reorganize config module

This commit is contained in:
2026-02-11 17:53:42 +02:00
parent cfdcd983e0
commit f836bdf6c4
2 changed files with 105 additions and 104 deletions

85
runner/src/config/mod.rs Normal file
View File

@@ -0,0 +1,85 @@
mod utils;
use crate::{
args::Args,
config::utils::validate_config,
error::{self, ConfigError},
};
use common::{self, KeyExchangeMode};
use miette::{NamedSource, SourceSpan};
use serde::Deserialize;
use std::{fs::read_to_string, net::SocketAddr, path::Path};
#[derive(Debug, Clone, Deserialize)]
pub struct BenchmarkConfig {
pub mode: String,
pub payload: u32,
pub iters: u32,
pub warmup: u32,
pub concurrency: u32,
pub server: SocketAddr,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub benchmarks: Vec<BenchmarkConfig>,
}
/// Load benchmark configuration from a TOML file.
///
/// # Errors
/// Returns an error if the file cannot be read or parsed.
pub fn load_from_file(path: &Path) -> error::Result<Config> {
let content = read_to_string(path).map_err(|source| ConfigError::ReadError {
source,
path: path.to_owned(),
})?;
let src = NamedSource::new(path.display().to_string(), content.clone());
let config = toml::from_str::<Config>(&content).map_err(|source| {
let span = source
.span()
.map(|s| SourceSpan::new(s.start.into(), s.end - s.start));
ConfigError::TomlParseError {
src: src.clone(),
span,
source,
}
})?;
validate_config(&config, &content, path)?;
Ok(config)
}
/// Create benchmark configuration from CLI arguments.
///
/// # Errors
/// Never returns an error, but returns Result for consistency.
pub fn load_from_cli(args: &Args) -> error::Result<Config> {
Ok(Config {
benchmarks: vec![BenchmarkConfig {
mode: args.mode.to_string(),
payload: args.payload_bytes,
iters: args.iters,
warmup: args.warmup,
concurrency: args.concurrency,
server: args
.server
.ok_or_else(|| common::Error::config("--server ir required"))?,
}],
})
}
impl Config {
/// Get the key exchange mode from the first benchmark configuration.
#[must_use]
pub fn server_mode(&self) -> KeyExchangeMode {
self.benchmarks
.first()
.and_then(|b| b.mode.parse().ok())
.unwrap_or(KeyExchangeMode::X25519)
}
}

View File

@@ -1,88 +1,13 @@
use crate::{ use crate::{
args::Args, config::{BenchmarkConfig, Config},
error::{self, ConfigError}, error::{self, ConfigError},
}; };
use common::{self, KeyExchangeMode}; use common::{self, KeyExchangeMode};
use miette::{NamedSource, SourceSpan}; use miette::{NamedSource, SourceSpan};
use serde::Deserialize; use std::path::Path;
use std::{fs::read_to_string, net::SocketAddr, path::Path};
#[derive(Debug, Clone, Deserialize)]
pub struct BenchmarkConfig {
pub mode: String,
pub payload: u32,
pub iters: u32,
pub warmup: u32,
pub concurrency: u32,
pub server: SocketAddr,
}
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub benchmarks: Vec<BenchmarkConfig>,
}
/// Load benchmark configuration from a TOML file.
///
/// # Errors
/// Returns an error if the file cannot be read or parsed.
pub fn load_from_file(path: &Path) -> error::Result<Config> {
let content = read_to_string(path).map_err(|source| ConfigError::ReadError {
source,
path: path.to_owned(),
})?;
let src = NamedSource::new(path.display().to_string(), content.clone());
let config = toml::from_str::<Config>(&content).map_err(|source| {
let span = source
.span()
.map(|s| SourceSpan::new(s.start.into(), s.end - s.start));
ConfigError::TomlParseError {
src: src.clone(),
span,
source,
}
})?;
validate_config(&config, &content, path)?;
Ok(config)
}
/// Create benchmark configuration from CLI arguments.
///
/// # Errors
/// Never returns an error, but returns Result for consistency.
pub fn load_from_cli(args: &Args) -> error::Result<Config> {
Ok(Config {
benchmarks: vec![BenchmarkConfig {
mode: args.mode.to_string(),
payload: args.payload_bytes,
iters: args.iters,
warmup: args.warmup,
concurrency: args.concurrency,
server: args
.server
.ok_or_else(|| common::Error::config("--server ir required"))?,
}],
})
}
impl Config {
/// Get the key exchange mode from the first benchmark configuration.
#[must_use]
pub fn server_mode(&self) -> KeyExchangeMode {
self.benchmarks
.first()
.and_then(|b| b.mode.parse().ok())
.unwrap_or(KeyExchangeMode::X25519)
}
}
/// Validate the configuration after parsing. /// Validate the configuration after parsing.
fn validate_config(config: &Config, content: &str, path: &Path) -> error::Result<()> { pub fn validate_config(config: &Config, content: &str, path: &Path) -> error::Result<()> {
if config.benchmarks.is_empty() { if config.benchmarks.is_empty() {
return Err(ConfigError::EmptyBenchmarks { return Err(ConfigError::EmptyBenchmarks {
src: NamedSource::new(path.display().to_string(), content.to_string()), src: NamedSource::new(path.display().to_string(), content.to_string()),
@@ -121,39 +46,30 @@ fn validate_benchmark(
.into()); .into());
} }
if benchmark.payload == 0 { validate_positive_field(src.clone(), content, idx, "payload", benchmark.payload)?;
validate_positive_field(src.clone(), content, idx, "iters", benchmark.iters)?;
validate_positive_field(src, content, idx, "concurrency", benchmark.concurrency)?;
Ok(())
}
fn validate_positive_field(
src: NamedSource<String>,
content: &str,
idx: usize,
field_name: &str,
value: u32,
) -> error::Result<()> {
if value == 0 {
return Err(ConfigError::ValidationError { return Err(ConfigError::ValidationError {
src, src,
span: find_field_span(content, idx, "payload"), span: find_field_span(content, idx, field_name),
field: "payload".into(), field: field_name.into(),
idx, idx,
message: "Must be greater than 0".into(), message: "Must be greater than 0".into(),
} }
.into()); .into());
} }
if benchmark.iters == 0 {
return Err(ConfigError::ValidationError {
src,
span: find_field_span(content, idx, "iters"),
field: "iters".into(),
idx,
message: "Must be greater than 0".into(),
}
.into());
}
if benchmark.concurrency == 0 {
return Err(ConfigError::ValidationError {
src,
span: find_field_span(content, idx, "concurrency"),
field: "concurrency".into(),
idx,
message: "Must be greater than 0".into(),
}
.into());
}
Ok(()) Ok(())
} }