use std::error::Error;
use std::fmt;
const MAX_FORMULA_TOKENS: &str = "max_formula_tokens";
const MAX_FORMULA_SOURCE_BYTES: &str = "max_formula_source_bytes";
const MAX_FORMULA_AST_NODES: &str = "max_formula_ast_nodes";
const MAX_FORMULA_NESTING_DEPTH: &str = "max_formula_nesting_depth";
const MAX_DEPENDENCY_EDGES: &str = "max_dependency_edges";
const MAX_REFERENCE_AREAS: &str = "max_reference_areas";
const MAX_ARRAY_CELLS: &str = "max_array_cells";
const MAX_TEXT_BYTES: &str = "max_text_bytes";
const MAX_FUNCTION_ITERATIONS: &str = "max_function_iterations";
const MAX_LET_BINDINGS: &str = "max_let_bindings";
const MAX_LAMBDA_DEPTH: &str = "max_lambda_depth";
const MAX_LAMBDA_INVOCATIONS: &str = "max_lambda_invocations";
const MESSAGE_ZERO_LIMIT: &str = "calculation limit must be greater than zero";
pub(super) const SAFE_FORMULA_NESTING_DEPTH: u64 = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CalculationLimits {
max_formula_tokens: u64,
max_formula_source_bytes: u64,
max_formula_ast_nodes: u64,
max_formula_nesting_depth: u64,
max_dependency_edges: u64,
max_reference_areas: u64,
max_array_cells: u64,
max_text_bytes: u64,
max_function_iterations: u64,
max_let_bindings: u64,
max_lambda_depth: u64,
max_lambda_invocations: u64,
}
impl CalculationLimits {
pub const fn max_formula_tokens(self) -> u64 {
self.max_formula_tokens
}
pub const fn max_formula_source_bytes(self) -> u64 {
self.max_formula_source_bytes
}
pub const fn max_formula_ast_nodes(self) -> u64 {
self.max_formula_ast_nodes
}
pub const fn max_formula_nesting_depth(self) -> u64 {
self.max_formula_nesting_depth
}
pub const fn max_dependency_edges(self) -> u64 {
self.max_dependency_edges
}
pub const fn max_reference_areas(self) -> u64 {
self.max_reference_areas
}
pub const fn max_array_cells(self) -> u64 {
self.max_array_cells
}
pub const fn max_text_bytes(self) -> u64 {
self.max_text_bytes
}
pub const fn max_function_iterations(self) -> u64 {
self.max_function_iterations
}
pub const fn max_let_bindings(self) -> u64 {
self.max_let_bindings
}
pub const fn max_lambda_depth(self) -> u64 {
self.max_lambda_depth
}
pub const fn max_lambda_invocations(self) -> u64 {
self.max_lambda_invocations
}
pub fn with_max_formula_tokens(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_formula_tokens = nonzero(MAX_FORMULA_TOKENS, value)?;
Ok(self)
}
pub fn with_max_formula_source_bytes(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_formula_source_bytes = nonzero(MAX_FORMULA_SOURCE_BYTES, value)?;
Ok(self)
}
pub fn with_max_formula_ast_nodes(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_formula_ast_nodes = nonzero(MAX_FORMULA_AST_NODES, value)?;
Ok(self)
}
pub fn with_max_formula_nesting_depth(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_formula_nesting_depth = nonzero(MAX_FORMULA_NESTING_DEPTH, value)?;
Ok(self)
}
pub fn with_max_dependency_edges(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_dependency_edges = nonzero(MAX_DEPENDENCY_EDGES, value)?;
Ok(self)
}
pub fn with_max_reference_areas(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_reference_areas = nonzero(MAX_REFERENCE_AREAS, value)?;
Ok(self)
}
pub fn with_max_array_cells(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_array_cells = nonzero(MAX_ARRAY_CELLS, value)?;
Ok(self)
}
pub fn with_max_text_bytes(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_text_bytes = nonzero(MAX_TEXT_BYTES, value)?;
Ok(self)
}
pub fn with_max_function_iterations(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_function_iterations = nonzero(MAX_FUNCTION_ITERATIONS, value)?;
Ok(self)
}
pub fn with_max_let_bindings(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_let_bindings = nonzero(MAX_LET_BINDINGS, value)?;
Ok(self)
}
pub fn with_max_lambda_depth(mut self, value: u64) -> Result<Self, CalculationOptionsError> {
self.max_lambda_depth = nonzero(MAX_LAMBDA_DEPTH, value)?;
Ok(self)
}
pub fn with_max_lambda_invocations(
mut self,
value: u64,
) -> Result<Self, CalculationOptionsError> {
self.max_lambda_invocations = nonzero(MAX_LAMBDA_INVOCATIONS, value)?;
Ok(self)
}
}
impl Default for CalculationLimits {
fn default() -> Self {
Self {
max_formula_tokens: 8_192,
max_formula_source_bytes: 1024 * 1024,
max_formula_ast_nodes: 8_192,
max_formula_nesting_depth: 256,
max_dependency_edges: 10_000_000,
max_reference_areas: 8_192,
max_array_cells: 1_000_000,
max_text_bytes: 32_767,
max_function_iterations: 1_000_000,
max_let_bindings: 126,
max_lambda_depth: 256,
max_lambda_invocations: 1_000_000,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CalculationOptionsError {
ZeroLimit {
name: &'static str,
},
}
impl fmt::Display for CalculationOptionsError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ZeroLimit { name } => write!(formatter, "{MESSAGE_ZERO_LIMIT}: {name}"),
}
}
}
impl Error for CalculationOptionsError {}
fn nonzero(name: &'static str, value: u64) -> Result<u64, CalculationOptionsError> {
if value == 0 {
return Err(CalculationOptionsError::ZeroLimit { name });
}
Ok(value)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(super) enum CalculationLimitKind {
FormulaTokens,
FormulaSourceBytes,
FormulaAstNodes,
FormulaNestingDepth,
DependencyEdges,
ReferenceAreas,
ArrayCells,
TextBytes,
FunctionIterations,
LetBindings,
LambdaDepth,
LambdaInvocations,
}
impl CalculationLimitKind {
pub(super) const fn detail(self) -> &'static str {
match self {
Self::FormulaTokens => MAX_FORMULA_TOKENS,
Self::FormulaSourceBytes => MAX_FORMULA_SOURCE_BYTES,
Self::FormulaAstNodes => MAX_FORMULA_AST_NODES,
Self::FormulaNestingDepth => MAX_FORMULA_NESTING_DEPTH,
Self::DependencyEdges => MAX_DEPENDENCY_EDGES,
Self::ReferenceAreas => MAX_REFERENCE_AREAS,
Self::ArrayCells => MAX_ARRAY_CELLS,
Self::TextBytes => MAX_TEXT_BYTES,
Self::FunctionIterations => MAX_FUNCTION_ITERATIONS,
Self::LetBindings => MAX_LET_BINDINGS,
Self::LambdaDepth => MAX_LAMBDA_DEPTH,
Self::LambdaInvocations => MAX_LAMBDA_INVOCATIONS,
}
}
pub(super) fn from_detail(value: &str) -> Option<Self> {
match value {
MAX_FORMULA_TOKENS => Some(Self::FormulaTokens),
MAX_FORMULA_SOURCE_BYTES => Some(Self::FormulaSourceBytes),
MAX_FORMULA_AST_NODES => Some(Self::FormulaAstNodes),
MAX_FORMULA_NESTING_DEPTH => Some(Self::FormulaNestingDepth),
MAX_DEPENDENCY_EDGES => Some(Self::DependencyEdges),
MAX_REFERENCE_AREAS => Some(Self::ReferenceAreas),
MAX_ARRAY_CELLS => Some(Self::ArrayCells),
MAX_TEXT_BYTES => Some(Self::TextBytes),
MAX_FUNCTION_ITERATIONS => Some(Self::FunctionIterations),
MAX_LET_BINDINGS => Some(Self::LetBindings),
MAX_LAMBDA_DEPTH => Some(Self::LambdaDepth),
MAX_LAMBDA_INVOCATIONS => Some(Self::LambdaInvocations),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn every_limit_kind_round_trips_through_its_detail() {
let kinds = [
CalculationLimitKind::FormulaTokens,
CalculationLimitKind::FormulaSourceBytes,
CalculationLimitKind::FormulaAstNodes,
CalculationLimitKind::FormulaNestingDepth,
CalculationLimitKind::DependencyEdges,
CalculationLimitKind::ReferenceAreas,
CalculationLimitKind::ArrayCells,
CalculationLimitKind::TextBytes,
CalculationLimitKind::FunctionIterations,
CalculationLimitKind::LetBindings,
CalculationLimitKind::LambdaDepth,
CalculationLimitKind::LambdaInvocations,
];
for kind in kinds {
assert_eq!(CalculationLimitKind::from_detail(kind.detail()), Some(kind));
}
}
#[test]
fn nesting_configuration_remains_source_compatible_above_internal_safe_depth() {
let limits = CalculationLimits::default()
.with_max_formula_nesting_depth(SAFE_FORMULA_NESTING_DEPTH + 1)
.expect("existing nonzero configuration remains accepted");
assert_eq!(
limits.max_formula_nesting_depth(),
SAFE_FORMULA_NESTING_DEPTH + 1
);
}
}