harn-vm 0.10.162

Async bytecode virtual machine for the Harn programming language
Documentation
use std::time::{Duration, SystemTime};

use super::MAX_RETRY_DELAY_MS;
use crate::stdlib::macros::harn_builtin;
use crate::value::{VmError, VmValue};
use crate::vm::Vm;

pub(super) fn requested_at(value: &str, now: SystemTime) -> Option<Duration> {
    let value = value.trim();
    if value.is_empty() {
        return None;
    }
    if let Ok(secs) = value.parse::<f64>() {
        if !secs.is_finite() || secs < 0.0 {
            return Some(Duration::ZERO);
        }
        return Some(Duration::from_millis((secs * 1_000.0) as u64));
    }
    httpdate::parse_http_date(value)
        .ok()
        .map(|target| target.duration_since(now).unwrap_or(Duration::ZERO))
}

pub(super) fn parse_retry_after_value_at(value: &str, now: SystemTime) -> Option<Duration> {
    requested_at(value, now).map(|delay| delay.min(Duration::from_millis(MAX_RETRY_DELAY_MS)))
}

pub(super) fn parse_retry_after_value(value: &str) -> Option<Duration> {
    parse_retry_after_value_at(value, SystemTime::now())
}

pub(crate) fn hint(value: &str) -> Option<(u64, bool)> {
    let millis = requested_at(value, SystemTime::now())?.as_millis();
    Some((
        millis.min(u128::from(MAX_RETRY_DELAY_MS)) as u64,
        millis > u128::from(MAX_RETRY_DELAY_MS),
    ))
}

pub(super) fn register(vm: &mut Vm) {
    vm.register_builtin_def(&HTTP_RETRY_AFTER_MS_AT_IMPL_DEF);
}

#[harn_builtin(
    exposure = "stdlib_internal",
    effects = [],
    sig = "__http_retry_after_ms_at(value: string, now_seconds: float) -> int?",
    category = "network"
)]
fn http_retry_after_ms_at_impl(args: &[VmValue], _out: &mut String) -> Result<VmValue, VmError> {
    let Some(VmValue::String(value)) = args.first() else {
        return Err(VmError::Runtime(
            "retry-after value must be a string".to_string(),
        ));
    };
    let seconds = match args.get(1) {
        Some(VmValue::Float(value)) => *value,
        Some(VmValue::Int(value)) => *value as f64,
        _ => {
            return Err(VmError::Runtime(
                "retry-after clock must be numeric".to_string(),
            ))
        }
    };
    let delta = Duration::try_from_secs_f64(seconds.abs())
        .map_err(|_| VmError::Runtime("retry-after clock is out of range".to_string()))?;
    let now = if seconds >= 0.0 {
        SystemTime::UNIX_EPOCH.checked_add(delta)
    } else {
        SystemTime::UNIX_EPOCH.checked_sub(delta)
    }
    .ok_or_else(|| VmError::Runtime("retry-after clock is out of range".to_string()))?;
    Ok(requested_at(value, now)
        .map(|delay| VmValue::Int(delay.as_millis().min(i64::MAX as u128) as i64))
        .unwrap_or(VmValue::Nil))
}