packages feed

grpc-spec-1.0.0: src/Network/GRPC/Spec/Serialization/Timeout.hs

{-# LANGUAGE OverloadedStrings #-}

module Network.GRPC.Spec.Serialization.Timeout (
    buildTimeout
  , parseTimeout
  ) where

import Control.Monad.Except
import Data.ByteString qualified as BS.Strict
import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString.Char8 qualified as BS.Strict.C8
import Data.Char (isDigit)

import Network.GRPC.Spec

{-------------------------------------------------------------------------------
  Serialization

  > Timeout      → "grpc-timeout" TimeoutValue TimeoutUnit
  > TimeoutValue → {positive integer as ASCII string of at most 8 digits}
  > TimeoutUnit  → Hour / Minute / Second / Millisecond / Microsecond / Nanosecond
  > Hour         → "H"
  > Minute       → "M"
  > Second       → "S"
  > Millisecond  → "m"
  > Microsecond  → "u"
  > Nanosecond   → "n"
-------------------------------------------------------------------------------}

-- | Serialize t'Timeout'
buildTimeout :: Timeout -> Strict.ByteString
buildTimeout (Timeout unit val) = mconcat [
      BS.Strict.C8.pack $ show $ getTimeoutValue val
    , case unit of
        Hour        -> "H"
        Minute      -> "M"
        Second      -> "S"
        Millisecond -> "m"
        Microsecond -> "u"
        Nanosecond  -> "n"
    ]

-- | Parse t'Timeout'
parseTimeout :: forall m. MonadError String m => Strict.ByteString -> m Timeout
parseTimeout bs = do
    let (bsVal, bsUnit) = BS.Strict.C8.span isDigit bs

    val <-
      if BS.Strict.length bsVal < 1 || BS.Strict.length bsVal > 8
        then invalid
        else return . TimeoutValue $ read (BS.Strict.C8.unpack bsVal)

    charUnit <-
      case BS.Strict.C8.uncons bsUnit of
        Nothing ->
          invalid
        Just (u, remainder) ->
          if BS.Strict.null remainder
            then return u
            else invalid

    unit <-
      case charUnit of
        'H' -> return Hour
        'M' -> return Minute
        'S' -> return Second
        'm' -> return Millisecond
        'u' -> return Microsecond
        'n' -> return Nanosecond
        _   -> invalid

    return $ Timeout unit val
  where
    invalid :: m a
    invalid = throwError $ "Could not parse timeout " ++ show bs