use std::time::Instant;
use serde::{Deserialize, Serialize};
use crate::error::{EngineError, ErrorCode};
use crate::limits::{CancellationToken, Limits};
pub use crate::number::NumericContext;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum Budget {
Fast,
Balanced,
Precise,
}
impl Budget {
pub fn precision(self) -> u32 {
match self {
Budget::Fast => 15,
Budget::Balanced => 34,
Budget::Precise => 100,
}
}
pub fn as_str(self) -> &'static str {
match self {
Budget::Fast => "fast",
Budget::Balanced => "balanced",
Budget::Precise => "precise",
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TraceLevel {
#[default]
Off,
Summary,
Full,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Conversion {
pub from: String,
pub to: String,
pub reason: String,
pub exact: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct AppliedLimits {
pub request_timeout_ms: u64,
pub max_operations: u64,
pub max_iterations: u64,
pub max_array_len: usize,
pub max_matrix_elements: usize,
pub max_batch_nodes: usize,
pub max_output_bytes: usize,
}
impl From<&Limits> for AppliedLimits {
fn from(limits: &Limits) -> Self {
AppliedLimits {
request_timeout_ms: limits.request_timeout_ms,
max_operations: limits.max_operations,
max_iterations: limits.max_iterations,
max_array_len: limits.max_array_len,
max_matrix_elements: limits.max_matrix_elements,
max_batch_nodes: limits.max_batch_nodes,
max_output_bytes: limits.max_output_bytes,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct EffectiveContext {
pub numeric: NumericContext,
#[serde(skip_serializing_if = "Option::is_none")]
pub budget: Option<Budget>,
#[serde(skip_serializing_if = "Option::is_none")]
pub seed: Option<u64>,
pub limits: AppliedLimits,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub conversions: Vec<Conversion>,
}
#[derive(Clone, Debug)]
pub struct ExecContext {
pub numeric: NumericContext,
pub budget: Option<Budget>,
pub limits: Limits,
pub cancellation: CancellationToken,
pub deadline: Option<Instant>,
pub seed: Option<u64>,
pub trace: TraceLevel,
}
impl Default for ExecContext {
fn default() -> Self {
ExecContext::conservative()
}
}
impl ExecContext {
pub fn conservative() -> ExecContext {
let limits = Limits::conservative();
ExecContext {
deadline: limits.deadline(),
numeric: NumericContext::default(),
budget: None,
limits,
cancellation: CancellationToken::new(),
seed: None,
trace: TraceLevel::Off,
}
}
pub fn with_numeric(mut self, numeric: NumericContext) -> ExecContext {
self.numeric = numeric;
self
}
pub fn with_limits(mut self, limits: Limits) -> ExecContext {
self.deadline = limits.deadline();
self.limits = limits;
self
}
pub fn with_seed(mut self, seed: Option<u64>) -> ExecContext {
self.seed = seed;
self
}
pub fn with_trace(mut self, trace: TraceLevel) -> ExecContext {
self.trace = trace;
self
}
pub fn with_budget(mut self, budget: Option<Budget>) -> ExecContext {
self.budget = budget;
if let Some(budget) = budget {
self.numeric.precision = budget.precision();
}
self
}
pub fn exact() -> ExecContext {
ExecContext {
numeric: NumericContext::exact(),
..ExecContext::conservative()
}
}
pub fn scientific() -> ExecContext {
ExecContext {
numeric: NumericContext::scientific(),
..ExecContext::conservative()
}
}
pub fn check(&self) -> Result<(), EngineError> {
self.cancellation.check()?;
if let Some(deadline) = self.deadline
&& Instant::now() > deadline
{
return Err(EngineError::new(
ErrorCode::ResourceLimit,
format!(
"request exceeded the {} ms time budget",
self.limits.request_timeout_ms
),
));
}
Ok(())
}
pub fn effective(&self) -> EffectiveContext {
EffectiveContext {
numeric: self.numeric.clone(),
budget: self.budget,
seed: self.seed,
limits: AppliedLimits::from(&self.limits),
conversions: Vec::new(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn effective_context_serializes() {
let ctx = ExecContext::conservative();
let effective = ctx.effective();
let json = serde_json::to_string(&effective).unwrap();
assert!(json.contains("\"mode\":\"auto\""));
}
}