use std::{env, num::ParseIntError}; use thiserror::Error; const DEFAULT_MAX_CONCURRENT_UNARY: usize = 64; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct RuntimeLimits { pub max_concurrent_unary: usize, } impl Default for RuntimeLimits { fn default() -> Self { Self { max_concurrent_unary: DEFAULT_MAX_CONCURRENT_UNARY, } } } impl RuntimeLimits { pub fn from_env() -> Result { Ok(Self { max_concurrent_unary: parse_limit( "CRANK_RUNTIME_MAX_CONCURRENT_UNARY", DEFAULT_MAX_CONCURRENT_UNARY, )?, }) } } fn parse_limit(name: &'static str, default: usize) -> Result { match env::var(name) { Ok(raw) => { let value = raw.parse::() .map_err(|source| RuntimeLimitsConfigError::InvalidValue { name, value: raw, source, })?; if value == 0 { return Err(RuntimeLimitsConfigError::ZeroValue { name }); } Ok(value) } Err(env::VarError::NotPresent) => Ok(default), Err(env::VarError::NotUnicode(_)) => Err(RuntimeLimitsConfigError::InvalidUnicode { name }), } } #[derive(Debug, Error)] pub enum RuntimeLimitsConfigError { #[error("{name} must contain valid UTF-8")] InvalidUnicode { name: &'static str }, #[error("{name} must be a positive integer, got {value}")] InvalidValue { name: &'static str, value: String, source: ParseIntError, }, #[error("{name} must be greater than zero")] ZeroValue { name: &'static str }, } #[cfg(test)] mod tests { use super::{RuntimeLimits, RuntimeLimitsConfigError}; #[test] fn defaults_are_positive() { let limits = RuntimeLimits::default(); assert!(limits.max_concurrent_unary > 0); } #[test] fn rejects_zero_limit_values() { unsafe { std::env::set_var("CRANK_RUNTIME_MAX_CONCURRENT_UNARY", "0"); } let error = RuntimeLimits::from_env().unwrap_err(); assert!(matches!( error, RuntimeLimitsConfigError::ZeroValue { name: "CRANK_RUNTIME_MAX_CONCURRENT_UNARY" } )); unsafe { std::env::remove_var("CRANK_RUNTIME_MAX_CONCURRENT_UNARY"); } } }