{- Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved. This source code is licensed under the BSD-style license found in the LICENSE file in the root directory of this source tree. -} {-# LANGUAGE ConstraintKinds, CPP #-} module Thrift.Protocol ( ThriftSerializable, serializeGen, deserializeGen , ThriftStruct(..), ProtocolException(..) , Protocol(..), parseStop, parseEnum, parseEnumNoUnknown , FieldId, Length, Type, MsgType, SeqNum , FieldBegin(..), MsgBegin(..) , genByte, genI16, genI32, genI64, genBool , ThriftEnum(..) , unknownThriftEnumErrorMsg ) where import Control.Exception import Data.Aeson hiding (Bool, String) import Thrift.Binary.Parser import Data.ByteString (ByteString) import Data.ByteString.Builder import Data.ByteString.Builder.Prim import Data.HashMap.Strict as HashMap import Data.Int import Data.Proxy import Data.Text (Text) import Data.Text.Encoding import Data.Typeable import Data.Word import qualified Data.ByteString.Lazy as LBS #if __GLASGOW_HASKELL__ == 806 import Prelude hiding (fail) import Control.Monad.Fail (fail) #endif -- Constraint Kind that every Thrift generated datatype must satisfy type ThriftSerializable a = (ToJSON a, ThriftStruct a) class ThriftStruct a where buildStruct :: Protocol p => Proxy p -> a -> Builder parseStruct :: Protocol p => Proxy p -> Parser a class ThriftEnum a where toThriftEnum :: Int -> a fromThriftEnum :: a -> Int allThriftEnumValues :: [a] toThriftEnumEither :: Int -> Either String a instance ThriftEnum Bool where toThriftEnum n | n == 0 = False | n == 1 = True | otherwise = errorWithoutStackTrace "Thrift.Protocol.ThriftEnum.Bool.toThriftEnum: bad argument" fromThriftEnum False = 0 fromThriftEnum True = 1 allThriftEnumValues = [False, True] toThriftEnumEither n | n == 0 = Right False | n == 1 = Right True | otherwise = Left "Thrift.Protocol.ThriftEnum.Bool.toThriftEnumEither: bad argument" {-# INLINE serializeGen #-} serializeGen :: (Protocol p, ThriftStruct a) => Proxy p -> a -> ByteString serializeGen proxy = LBS.toStrict . toLazyByteString . buildStruct proxy {-# INLINE deserializeGen #-} deserializeGen :: (Protocol p, ThriftStruct a) => Proxy p -> ByteString -> Either String a deserializeGen proxy input = parse (parseStruct proxy) input newtype ProtocolException = ProtocolException String deriving (Show, Eq) instance Exception ProtocolException -- Binary Protocol type class -------------------------------------------------- type FieldId = Int16 type Length = Word32 type Type = Word8 type IdMap = HashMap Text FieldId type MsgType = Word8 type SeqNum = Int32 data FieldBegin = FieldBegin {-# UNPACK #-} !Type {-# UNPACK #-} !FieldId !Bool | FieldEnd data MsgBegin = MsgBegin !Text {-# UNPACK #-} !MsgType {-# UNPACK #-} !SeqNum deriving (Show, Eq) class Protocol p where -- Generators for Types getByteType :: Proxy p -> Type getI16Type :: Proxy p -> Type getI32Type :: Proxy p -> Type getI64Type :: Proxy p -> Type getFloatType :: Proxy p -> Type getDoubleType :: Proxy p -> Type getBoolType :: Proxy p -> Type getStringType :: Proxy p -> Type getListType :: Proxy p -> Type getSetType :: Proxy p -> Type getMapType :: Proxy p -> Type getStructType :: Proxy p -> Type -- Generators for tokens genMsgBegin :: Proxy p -> Text -> MsgType -> SeqNum -> Builder genStruct :: Proxy p -> [Builder] -> Builder genField :: Proxy p -> Text -> Type -> FieldId -> FieldId -> Builder -> Builder genFieldPrim :: Proxy p -> Text -> Type -> FieldId -> FieldId -> BoundedPrim a -> a -> Builder -- Only Compact protocol needs a custom implementation for this genFieldBool :: Proxy p -> Text -> FieldId -> FieldId -> Bool -> Builder genFieldBool proxy name fid lastId = genFieldPrim proxy name (getBoolType proxy) fid lastId (genBoolPrim proxy) genList :: Proxy p -> Type -> (a -> Builder) -> [a] -> Builder genListPrim :: Proxy p -> Type -> BoundedPrim a -> [a] -> Builder genMap :: Proxy p -> Type -> Type -> Bool -> (k -> Builder) -> (v -> Builder) -> [(k, v)] -> Builder genMapPrim :: Proxy p -> Type -> Type -> Bool -> BoundedPrim k -> BoundedPrim v -> [(k, v)] -> Builder genMsgEnd :: Proxy p -> Builder genMsgEnd _ = mempty -- Primitive Generators (for base types) genBytePrim :: Proxy p -> BoundedPrim Int8 genI16Prim :: Proxy p -> BoundedPrim Int16 genI32Prim :: Proxy p -> BoundedPrim Int32 genI64Prim :: Proxy p -> BoundedPrim Int64 genBoolPrim :: Proxy p -> BoundedPrim Bool -- Generators for string types (implement 1) genText :: Proxy p -> Text -> Builder genText p = genByteString p . encodeUtf8 genByteString :: Proxy p -> ByteString -> Builder genByteString p = genText p . decodeUtf8 -- Generator for binary genBytes :: Proxy p -> ByteString -> Builder genBytes = genByteString -- Generators for other base types genFloat :: Proxy p -> Float -> Builder genDouble :: Proxy p -> Double -> Builder -- Parsers for tokens parseMsgBegin :: Proxy p -> Parser MsgBegin parseMsgEnd :: Proxy p -> Parser () parseMsgEnd _ = return () parseFieldBegin :: Proxy p -> FieldId -> IdMap -> Parser FieldBegin parseList :: Proxy p -> Parser a -> Parser (Int,[a]) parseMap :: Proxy p -> Parser k -> Parser v -> Bool -> Parser [(k, v)] -- Parser for base types parseByte :: Proxy p -> Parser Int8 parseI16 :: Proxy p -> Parser Int16 parseI32 :: Proxy p -> Parser Int32 parseI64 :: Proxy p -> Parser Int64 parseFloat :: Proxy p -> Parser Float parseDouble :: Proxy p -> Parser Double parseBool :: Proxy p -> Parser Bool parseBoolF :: Proxy p -> Bool -> Parser Bool -- Parsers for strings parseText :: Proxy p -> Parser Text parseText p = decodeUtf8 <$> parseByteString p parseByteString :: Proxy p -> Parser ByteString parseByteString p = encodeUtf8 <$> parseText p -- Parser for binary parseBytes :: Proxy p -> Parser ByteString parseBytes = parseByteString parseSkip :: Proxy p -> Type -> Maybe Bool -> Parser () genByte :: Protocol p => Proxy p -> Int8 -> Builder genByte p = primBounded (genBytePrim p) genI16 :: Protocol p => Proxy p -> Int16 -> Builder genI16 p = primBounded (genI16Prim p) genI32 :: Protocol p => Proxy p -> Int32 -> Builder genI32 p = primBounded (genI32Prim p) genI64 :: Protocol p => Proxy p -> Int64 -> Builder genI64 p = primBounded (genI64Prim p) genBool :: Protocol p => Proxy p -> Bool -> Builder genBool p = primBounded (genBoolPrim p) parseStop :: Protocol p => Proxy p -> Parser () parseStop token = do end <- parseFieldBegin token 0 HashMap.empty case end of FieldBegin{} -> fail "parseStop: expected end of struct" FieldEnd -> return () parseEnum :: (Protocol p, ThriftEnum a) => Proxy p -> String -> Parser a parseEnum proxy _ = do n <- parseI32 proxy return $ toThriftEnum (fromIntegral n) parseEnumNoUnknown :: (Protocol p, ThriftEnum a) => Proxy p -> String -> Parser a parseEnumNoUnknown proxy name = do n <- parseI32 proxy case toThriftEnumEither (fromIntegral n) of Left _ -> fail $ "parseEnum: not a valid identifier for thrift enum '" ++ name ++ "': " ++ show n Right enum -> return enum unknownThriftEnumErrorMsg :: (Typeable a, Show a) => a -> String unknownThriftEnumErrorMsg x = "Unknown value " ++ show x ++ " for enum " ++ show (tyConModule $ typeRepTyCon $ typeOf x) ++ "." ++ show (typeOf x)