thrift/lib/hs/Thrift/Protocol/JSON.hs (204 lines of code) (raw):

-- -- Licensed to the Apache Software Foundation (ASF) under one -- or more contributor license agreements. See the NOTICE file -- distributed with this work for additional information -- regarding copyright ownership. The ASF licenses this file -- to you under the Apache License, Version 2.0 (the -- "License"); you may not use this file except in compliance -- with the License. You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, -- software distributed under the License is distributed on an -- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -- KIND, either express or implied. See the License for the -- specific language governing permissions and limitations -- under the License. -- {-# LANGUAGE CPP #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} module Thrift.Protocol.JSON ( module Thrift.Protocol , JSONProtocol(..) ) where #if __GLASGOW_HASKELL__ < 710 import Control.Applicative #endif import Control.Exception import Data.Attoparsec.ByteString as P import Data.Attoparsec.ByteString.Char8 as PC import Data.Attoparsec.ByteString.Lazy as LP import Data.ByteString.Builder as B import Data.Int import Data.List #if __GLASGOW_HASKELL__ < 804 import Data.Monoid #endif import Data.Text.Lazy.Encoding import qualified Data.HashMap.Strict as Map import Thrift.Protocol import Thrift.Protocol.JSONUtils import Thrift.Transport import Thrift.Types import qualified Data.ByteString.Lazy as LBS import qualified Data.Text.Lazy as LT -- | The JSON Protocol data uses the standard 'TJSONProtocol'. Data is -- encoded as a JSON 'ByteString' data JSONProtocol t = JSONProtocol t -- ^ Construct a 'JSONProtocol' with a 'Transport' instance Protocol JSONProtocol where mkProtocol = JSONProtocol getTransport (JSONProtocol t) = t writeMessage (JSONProtocol t) (s, ty, sq) = bracket writeMessageBegin writeMessageEnd . const where writeMessageBegin = tWrite t $ toLazyByteString $ "[" <> int32Dec 1 <> ",\"" <> escape (encodeUtf8 s) <> "\"" <> "," <> intDec (fromEnum ty) <> "," <> int32Dec sq <> "," writeMessageEnd _ = tWrite t "]" readMessage p = bracket readMessageBegin readMessageEnd where readMessageBegin = runParser p $ skipSpace *> do _ver :: Int32 <- lexeme (PC.char8 '[') *> lexeme (signed decimal) bs <- lexeme (PC.char8 ',') *> lexeme escapedString case decodeUtf8' bs of Left _ -> fail "readMessage: invalid text encoding" Right str -> do ty <- toEnum <$> (lexeme (PC.char8 ',') *> lexeme (signed decimal)) seqNum <- lexeme (PC.char8 ',') *> lexeme (signed decimal) _ <- PC.char8 ',' return (str, ty, seqNum) readMessageEnd _ = runParser p (PC.char8 ']') serializeVal _ = toLazyByteString . buildJSONValue deserializeVal _ ty bs = case LP.eitherResult $ LP.parse (parseJSONValue ty) bs of Left s -> error s Right val -> val readVal p ty = runParser p $ skipSpace *> parseJSONValue ty -- Writing Functions buildJSONValue :: ThriftVal -> Builder buildJSONValue (TStruct fields) = "{" <> buildJSONStruct fields <> "}" buildJSONValue (TMap kt vt entries) = "[\"" <> getTypeName kt <> "\"" <> ",\"" <> getTypeName vt <> "\"" <> "," <> intDec (length entries) <> ",{" <> buildJSONMap entries <> "}" <> "]" buildJSONValue (TList ty entries) = "[\"" <> getTypeName ty <> "\"" <> "," <> intDec (length entries) <> (if null entries then mempty else "," <> buildJSONList entries) <> B.char8 ']' buildJSONValue (TSet ty entries) = buildJSONValue (TList ty entries) buildJSONValue (TBool b) = intDec $ fromEnum b buildJSONValue (TByte b) = int8Dec b buildJSONValue (TI16 i) = int16Dec i buildJSONValue (TI32 i) = int32Dec i buildJSONValue (TI64 i) = int64Dec i buildJSONValue (TFloat f) = floatDec f buildJSONValue (TDouble d) = doubleDec d buildJSONValue (TString s) = "\"" <> escape s <> "\"" buildJSONStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder buildJSONStruct = mconcat . intersperse "," . Map.foldrWithKey buildField [] where buildField fid (_,val) = (:) $ "\"" <> int16Dec fid <> "\":" <> "{\"" <> getTypeName (getTypeOf val) <> "\":" <> buildJSONValue val <> "}" buildJSONMap :: [(ThriftVal, ThriftVal)] -> Builder buildJSONMap = mconcat . intersperse "," . map buildKV where buildKV (key@(TString _), val) = buildJSONValue key <> ":" <> buildJSONValue val buildKV (key, val) = "\"" <> buildJSONValue key <> "\":" <> buildJSONValue val buildJSONList :: [ThriftVal] -> Builder buildJSONList = mconcat . intersperse "," . map buildJSONValue -- Reading Functions parseJSONValue :: ThriftType -> Parser ThriftVal parseJSONValue (T_STRUCT tmap) = TStruct <$> (lexeme (PC.char8 '{') *> parseJSONStruct tmap <* PC.char8 '}') parseJSONValue (T_MAP _ _) = between '[' ']' $ do kt <- fromTypeName <$> lexeme escapedString <* lexeme (PC.char8 ',') vt <- fromTypeName <$> lexeme escapedString <* lexeme (PC.char8 ',') TMap kt vt <$> ((lexeme decimal :: Parser Int) *> lexeme (PC.char8 ',') *> between '{' '}' (parseJSONMap kt vt)) parseJSONValue (T_LIST _) = between '[' ']' $ do ty <- fromTypeName <$> lexeme escapedString <* lexeme (PC.char8 ',') len :: Int <- lexeme decimal TList ty <$> if len > 0 then lexeme (PC.char8 ',') *> parseJSONList ty else return [] parseJSONValue (T_SET _) = between '[' ']' $ do ty <- fromTypeName <$> lexeme escapedString <* lexeme (PC.char8 ',') len :: Int <- lexeme decimal TSet ty <$> if len > 0 then lexeme (PC.char8 ',') *> parseJSONList ty else return [] parseJSONValue T_BOOL = TBool . toEnum <$> decimal parseJSONValue T_BYTE = TByte <$> signed decimal parseJSONValue T_I16 = TI16 <$> signed decimal parseJSONValue T_I32 = TI32 <$> signed decimal parseJSONValue T_I64 = TI64 <$> signed decimal parseJSONValue T_FLOAT = TFloat . realToFrac <$> double parseJSONValue T_DOUBLE = TDouble <$> double parseJSONValue T_STRING = TString <$> escapedString parseJSONValue T_STOP = fail "parseJSONValue: cannot parse type T_STOP" parseJSONValue T_VOID = fail "parseJSONValue: cannot parse type T_VOID" parseJSONStruct :: TypeMap -> Parser (Map.HashMap Int16 (LT.Text, ThriftVal)) parseJSONStruct _ = Map.fromList <$> parseField `sepBy` lexeme (PC.char8 ',') where parseField = do fid <- lexeme (between '"' '"' decimal) <* lexeme (PC.char8 ':') between '{' '}' $ do ty <- fromTypeName <$> lexeme escapedString <* lexeme (PC.char8 ':') val <- lexeme (parseJSONValue ty) return (fid, ("", val)) parseJSONMap :: ThriftType -> ThriftType -> Parser [(ThriftVal, ThriftVal)] parseJSONMap kt vt = ((,) <$> lexeme (PC.char8 '"' *> parseJSONValue kt <* PC.char8 '"') <*> (lexeme (PC.char8 ':') *> lexeme (parseJSONValue vt))) `sepBy` lexeme (PC.char8 ',') parseJSONList :: ThriftType -> Parser [ThriftVal] parseJSONList ty = lexeme (parseJSONValue ty) `sepBy` lexeme (PC.char8 ',') getTypeName :: ThriftType -> Builder getTypeName ty = case ty of T_STRUCT _ -> "rec" T_MAP _ _ -> "map" T_LIST _ -> "lst" T_SET _ -> "set" T_BOOL -> "tf" T_BYTE -> "i8" T_I16 -> "i16" T_I32 -> "i32" T_I64 -> "i64" T_FLOAT -> "flt" T_DOUBLE -> "dbl" T_STRING -> "str" _ -> throw $ ProtocolExn PE_INVALID_DATA "Bad Type" fromTypeName :: LBS.ByteString -> ThriftType fromTypeName ty = case ty of "rec" -> T_STRUCT Map.empty "map" -> T_MAP T_VOID T_VOID "lst" -> T_LIST T_VOID "set" -> T_SET T_VOID "tf" -> T_BOOL "i8" -> T_BYTE "i16" -> T_I16 "i32" -> T_I32 "i64" -> T_I64 "flt" -> T_FLOAT "dbl" -> T_DOUBLE "str" -> T_STRING t -> throw $ ProtocolExn PE_INVALID_DATA ("Bad Type: " ++ show t)