use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Ident, LitInt, Result, Token};
pub struct TimeoutInput {
pub components: Vec<TimeoutComponent>,
}
pub struct TimeoutComponent {
pub value: u64,
pub unit: TimeoutUnit,
}
#[derive(Clone, Copy)]
pub enum TimeoutUnit {
Nanoseconds,
Microseconds,
Milliseconds,
Seconds,
Minutes,
Hours,
}
impl TimeoutUnit {
const fn to_nanos(self) -> u64 {
match self {
Self::Nanoseconds => 1,
Self::Microseconds => 1_000,
Self::Milliseconds => 1_000_000,
Self::Seconds => 1_000_000_000,
Self::Minutes => 60 * 1_000_000_000,
Self::Hours => 60 * 60 * 1_000_000_000,
}
}
}
impl Parse for TimeoutComponent {
fn parse(input: ParseStream) -> Result<Self> {
let value: LitInt = input.parse()?;
let value_u64: u64 = value.base10_parse()?;
let unit: Ident = input.parse()?;
let unit = match unit.to_string().as_str() {
"ns" | "nanos" | "nanoseconds" => TimeoutUnit::Nanoseconds,
"us" | "micros" | "microseconds" => TimeoutUnit::Microseconds,
"ms" | "millis" | "milliseconds" => TimeoutUnit::Milliseconds,
"s" | "sec" | "secs" | "seconds" => TimeoutUnit::Seconds,
"m" | "min" | "mins" | "minutes" => TimeoutUnit::Minutes,
"h" | "hr" | "hrs" | "hours" => TimeoutUnit::Hours,
other => {
return Err(syn::Error::new(
unit.span(),
format!("unknown time unit: {other}"),
));
}
};
Ok(Self {
value: value_u64,
unit,
})
}
}
impl Parse for TimeoutInput {
fn parse(input: ParseStream) -> Result<Self> {
let mut components = Vec::new();
components.push(input.parse()?);
while input.peek(Token![+]) {
let _: Token![+] = input.parse()?;
components.push(input.parse()?);
}
Ok(Self { components })
}
}
pub fn expand(input: TimeoutInput) -> TokenStream {
let total_nanos: u64 = input
.components
.iter()
.map(|c| c.value.saturating_mul(c.unit.to_nanos()))
.fold(0u64, u64::saturating_add);
let secs = total_nanos / 1_000_000_000;
let nanos = (total_nanos % 1_000_000_000) as u32;
quote! {
std::time::Duration::new(#secs, #nanos)
}
}
#[cfg(test)]
mod tests {
use syn::parse_quote;
use super::*;
#[test]
fn parse_simple_timeout() {
let input: TimeoutInput = parse_quote! {
5 s
};
assert_eq!(input.components.len(), 1);
assert_eq!(input.components[0].value, 5);
}
#[test]
fn parse_compound_timeout() {
let input: TimeoutInput = parse_quote! {
1 m + 30 s
};
assert_eq!(input.components.len(), 2);
}
}