grpc-spec-1.0.0: src/Network/GRPC/Spec/Serialization/TraceContext.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Network.GRPC.Spec.Serialization.TraceContext (
buildTraceContext
, parseTraceContext
) where
import Control.Applicative (many)
import Control.Monad.Except
import Data.Binary (Binary(..))
import Data.Binary qualified as Binary
import Data.Binary.Get qualified as Get
import Data.Binary.Put qualified as Put
import Data.ByteString qualified as Strict (ByteString)
import Data.ByteString.Lazy qualified as BS.Lazy
import Data.Default
import Data.Maybe (maybeToList)
import Data.Word
import Network.GRPC.Spec
{-------------------------------------------------------------------------------
Serialization
-------------------------------------------------------------------------------}
-- | Serialize t'TraceContext'
buildTraceContext :: TraceContext -> Strict.ByteString
buildTraceContext = BS.Lazy.toStrict . Binary.encode
-- | Parse t'TraceContext'
parseTraceContext :: MonadError String m => Strict.ByteString -> m TraceContext
parseTraceContext bs =
case Binary.decodeOrFail (BS.Lazy.fromStrict bs) of
Right (_, _, ctxt) -> return ctxt
Left (_, _, err) -> throwError err
{-------------------------------------------------------------------------------
Internal auxiliary: parsing
<https://github.com/census-instrumentation/opencensus-specs/blob/master/encodings/BinaryEncoding.md>
-------------------------------------------------------------------------------}
instance Binary TraceId where
put = Put.putByteString . getTraceId
get = TraceId <$> Get.getByteString 16
instance Binary SpanId where
put = Put.putByteString . getSpanId
get = SpanId <$> Get.getByteString 8
instance Binary TraceOptions where
put = Put.putWord8 . traceOptionsToWord8
get = traceOptionsFromWord8 =<< Get.getWord8
instance Binary Field where
put (FieldTraceId tid) = Put.putWord8 0 <> put tid
put (FieldSpanId sid) = Put.putWord8 1 <> put sid
put (FieldOptions opts) = Put.putWord8 2 <> put opts
get = do
fieldId <- Get.getWord8
case fieldId of
0 -> FieldTraceId <$> get
1 -> FieldSpanId <$> get
2 -> FieldOptions <$> get
_ -> fail $ "Invalid fieldId " ++ show fieldId
instance Binary TraceContext where
put ctxt = mconcat [
Put.putWord8 0 -- Version 0
, foldMap put (traceContextToFields ctxt)
]
get = do
version <- Get.getWord8
case version of
0 -> traceContextFromFields =<< many get
_ -> fail $ "Invalid version " ++ show version
{-------------------------------------------------------------------------------
Internal: fields
-------------------------------------------------------------------------------}
data Field =
FieldTraceId TraceId
| FieldSpanId SpanId
| FieldOptions TraceOptions
traceContextToFields :: TraceContext -> [Field]
traceContextToFields (TraceContext tid sid opts) = concat [
FieldTraceId <$> maybeToList tid
, FieldSpanId <$> maybeToList sid
, FieldOptions <$> maybeToList opts
]
traceContextFromFields :: forall m. MonadFail m => [Field] -> m TraceContext
traceContextFromFields = flip go def
where
go :: [Field] -> TraceContext -> m TraceContext
go [] acc = return acc
go (f:fs) acc =
case f of
FieldTraceId tid ->
case traceContextTraceId acc of
Nothing -> go fs $ acc{traceContextTraceId = Just tid}
Just _ -> fail "Multiple TraceId fields"
FieldSpanId sid ->
case traceContextSpanId acc of
Nothing -> go fs $ acc{traceContextSpanId = Just sid}
Just _ -> fail "Multiple SpanId fields"
FieldOptions opts ->
case traceContextOptions acc of
Nothing -> go fs $ acc{traceContextOptions = Just opts}
Just _ -> fail "Multiple TraceOptions fields"
{-------------------------------------------------------------------------------
Internal: dealing with 'TraceOptions'
We take advantage of the fact that currently only a single option is defined.
Once we have more than one, this code will be a bit more complicated.
-------------------------------------------------------------------------------}
traceOptionsToWord8 :: TraceOptions -> Word8
traceOptionsToWord8 (TraceOptions False) = 0
traceOptionsToWord8 (TraceOptions True) = 1
traceOptionsFromWord8 :: MonadFail m => Word8 -> m TraceOptions
traceOptionsFromWord8 0 = return $ TraceOptions False
traceOptionsFromWord8 1 = return $ TraceOptions True
traceOptionsFromWord8 n = fail $ "Invalid TraceOptions " ++ show n