use std::error::Error as StdError;
use std::time::Duration;
use crate::shared::error::{ConnectError, ConnectResult};
const MAX_VALUE: u128 = 99_999_999;
const UNITS: [(u128, char); 6] = [
(1, 'n'),
(1_000, 'u'),
(1_000_000, 'm'),
(1_000_000_000, 'S'),
(60_000_000_000, 'M'),
(3_600_000_000_000, 'H'),
];
#[must_use]
pub fn encode_grpc_timeout(duration: Duration) -> String {
let nanos = duration.as_nanos();
for (per_unit, suffix) in UNITS {
let value = nanos / per_unit;
if value <= MAX_VALUE {
return format!("{value}{suffix}");
}
}
format!("{MAX_VALUE}H")
}
#[derive(Debug)]
pub enum TimeoutError {
Empty,
TooManyDigits,
InvalidValue,
InvalidUnit(char),
}
impl core::fmt::Display for TimeoutError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
TimeoutError::Empty => write!(f, "empty grpc-timeout header"),
TimeoutError::TooManyDigits => {
write!(f, "grpc-timeout value exceeds 8 digits")
}
TimeoutError::InvalidValue => write!(f, "grpc-timeout value is not an integer"),
TimeoutError::InvalidUnit(c) => write!(f, "invalid grpc-timeout unit {c:?}"),
}
}
}
impl StdError for TimeoutError {}
impl From<TimeoutError> for ConnectError {
fn from(err: TimeoutError) -> Self {
ConnectError::invalid_argument(err.to_string())
}
}
pub fn decode_grpc_timeout(header: &str) -> Result<Duration, TimeoutError> {
let unit = header.chars().last().ok_or(TimeoutError::Empty)?;
let digits = &header[..header.len() - unit.len_utf8()];
if digits.is_empty() {
return Err(TimeoutError::Empty);
}
if digits.len() > 8 {
return Err(TimeoutError::TooManyDigits);
}
let value: u64 = digits.parse().map_err(|_| TimeoutError::InvalidValue)?;
let per_unit = UNITS
.iter()
.find(|(_, suffix)| *suffix == unit)
.map(|(per_unit, _)| *per_unit as u64)
.ok_or(TimeoutError::InvalidUnit(unit))?;
Ok(Duration::from_nanos(value.saturating_mul(per_unit)))
}
pub fn decode_grpc_timeout_connect(header: &str) -> ConnectResult<Duration> {
decode_grpc_timeout(header).map_err(|e| ConnectError::from(e).into())
}