thrift/lib/hs/Thrift/Protocol/SimpleJSON.hs (174 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.SimpleJSON
( module Thrift.Protocol
, SimpleJSONProtocol(..)
) where
import Control.Applicative
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.Functor
import Data.Int
import Data.List
import Data.Maybe (catMaybes)
#if __GLASGOW_HASKELL__ < 804
import Data.Monoid
#endif
import Data.Text.Lazy.Encoding
import qualified Data.HashMap.Strict as Map
import qualified Data.Text.Lazy as LT
import Thrift.Protocol
import Thrift.Protocol.JSONUtils
import Thrift.Transport
import Thrift.Types
-- | The Simple JSON Protocol data uses the standard 'TSimpleJSONProtocol'.
-- Data is encoded as a JSON 'ByteString'
data SimpleJSONProtocol t = SimpleJSONProtocol t
-- ^ Construct a 'JSONProtocol' with a 'Transport'
version :: Int32
version = 1
instance Protocol SimpleJSONProtocol where
mkProtocol = SimpleJSONProtocol
getTransport (SimpleJSONProtocol t) = t
writeMessage (SimpleJSONProtocol t) (s, ty, sq) =
bracket writeMessageBegin writeMessageEnd . const
where
writeMessageBegin = tWrite t $ toLazyByteString $
"[" <> int32Dec version <>
",\"" <> 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 ',') *> signed decimal
return (str, ty, seqNum)
readMessageEnd _ = runParser p $ skipSpace *> 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 _ _ entries) = "{" <> buildJSONMap entries <> "}"
buildJSONValue (TList _ entries) = "[" <> buildJSONList entries <> "]"
buildJSONValue (TSet _ entries) = "[" <> buildJSONList entries <> "]"
buildJSONValue (TBool b) = if b then "true" else "false"
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.elems . Map.map (\(str,val) ->
"\"" <> B.lazyByteString (encodeUtf8 str) <> "\":" <> 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 kt vt) =
TMap kt vt <$> between '{' '}' (parseJSONMap kt vt)
parseJSONValue (T_LIST ty) =
TList ty <$> between '[' ']' (parseJSONList ty)
parseJSONValue (T_SET ty) =
TSet ty <$> between '[' ']' (parseJSONList ty)
parseJSONValue T_BOOL =
(TBool True <$ string "true") <|> (TBool False <$ string "false")
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"
parseAnyValue :: Parser ()
parseAnyValue = choice $
skipBetween '{' '}' :
skipBetween '[' ']' :
map (void . parseJSONValue)
[ T_BOOL
, T_I16
, T_I32
, T_I64
, T_FLOAT
, T_DOUBLE
, T_STRING
]
where
skipBetween :: Char -> Char -> Parser ()
skipBetween a b = between a b $ void $ many $
void (PC.takeWhile1 $ \c -> c /= a && c /= b)
<|> skipBetween a b
parseJSONStruct :: TypeMap -> Parser (Map.HashMap Int16 (LT.Text, ThriftVal))
parseJSONStruct tmap = Map.fromList . catMaybes <$> parseField
`sepBy` lexeme (PC.char8 ',')
where
parseField = do
bs <- lexeme escapedString <* lexeme (PC.char8 ':')
case decodeUtf8' bs of
Left _ -> fail "parseJSONStruct: invalid key encoding"
Right str -> case Map.lookup str tmap of
Just (fid, ftype) -> do
val <- lexeme (parseJSONValue ftype)
return $ Just (fid, (str, val))
Nothing -> lexeme parseAnyValue *> return Nothing
parseJSONMap :: ThriftType -> ThriftType -> Parser [(ThriftVal, ThriftVal)]
parseJSONMap kt@T_STRING vt =
((,) <$> lexeme (parseJSONValue kt) <*>
(lexeme (PC.char8 ':') *> lexeme (parseJSONValue vt))) `sepBy`
lexeme (PC.char8 ',')
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 ',')