-module(livery_grpc_timeout). -moduledoc """ The `grpc-timeout` header: a call deadline on the wire. The value is an ASCII integer (up to 8 digits) followed by a unit: `H` hours, `M` minutes, `S` seconds, `m` milliseconds, `u` microseconds, `n` nanoseconds. This module converts between that wire form and a millisecond timeout (the unit Erlang's `receive ... after` and timers use). Sub-millisecond units round up to 1 ms so a deadline is never 0. """. -export([parse/1, encode/1]). -doc """ Parse a `grpc-timeout` value into milliseconds. `undefined` (header absent) yields `infinity`. A malformed value also yields `infinity`, so a bad header relaxes the deadline rather than failing the call. """. -spec parse(binary() | undefined) -> timeout(). parse(undefined) -> infinity; parse(Bin) when is_binary(Bin) -> case split_unit(Bin) of {Digits, Unit} -> try to_ms(binary_to_integer(Digits), Unit) catch error:_ -> infinity end; error -> infinity end. -doc """ Encode a millisecond timeout as a `grpc-timeout` value. `infinity` returns `undefined` (no header). Values that fit in 8 digits of milliseconds use the `m` unit; larger deadlines fall back to whole seconds. """. -spec encode(timeout()) -> binary() | undefined. encode(infinity) -> undefined; encode(Ms) when is_integer(Ms), Ms >= 0, Ms =< 99999999 -> <<(integer_to_binary(Ms))/binary, "m">>; encode(Ms) when is_integer(Ms), Ms > 99999999 -> Secs = (Ms + 999) div 1000, <<(integer_to_binary(Secs))/binary, "S">>. %%==================================================================== %% Internals %%==================================================================== -spec split_unit(binary()) -> {binary(), byte()} | error. split_unit(<<>>) -> error; split_unit(Bin) -> Size = byte_size(Bin) - 1, case Bin of <> when Digits =/= <<>> -> {Digits, Unit}; _ -> error end. %% Convert an integer count in the given unit to milliseconds, rounding %% sub-millisecond units up so a positive timeout never collapses to 0. -spec to_ms(non_neg_integer(), byte()) -> non_neg_integer(). to_ms(N, $H) -> N * 3600000; to_ms(N, $M) -> N * 60000; to_ms(N, $S) -> N * 1000; to_ms(N, $m) -> N; to_ms(N, $u) -> ceil_div(N, 1000); to_ms(N, $n) -> ceil_div(N, 1000000). -spec ceil_div(non_neg_integer(), pos_integer()) -> non_neg_integer(). ceil_div(0, _) -> 0; ceil_div(N, D) -> (N + D - 1) div D.