blob: ba19ad7b61d10f2f15006e9a26c88adade73097c [file] [log] [blame]
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -07001--
2-- Licensed to the Apache Software Foundation (ASF) under one
3-- or more contributor license agreements. See the NOTICE file
4-- distributed with this work for additional information
5-- regarding copyright ownership. The ASF licenses this file
6-- to you under the Apache License, Version 2.0 (the
7-- "License"); you may not use this file except in compliance
8-- with the License. You may obtain a copy of the License at
9--
10-- http://www.apache.org/licenses/LICENSE-2.0
11--
12-- Unless required by applicable law or agreed to in writing,
13-- software distributed under the License is distributed on an
14-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15-- KIND, either express or implied. See the License for the
16-- specific language governing permissions and limitations
17-- under the License.
18--
19
20{-# LANGUAGE CPP #-}
21{-# LANGUAGE ExistentialQuantification #-}
22{-# LANGUAGE OverloadedStrings #-}
23{-# LANGUAGE ScopedTypeVariables #-}
24{-# LANGUAGE TupleSections #-}
25
26module Thrift.Protocol.JSON
27 ( module Thrift.Protocol
28 , JSONProtocol(..)
29 ) where
30
31import Control.Applicative
32import Control.Monad
33import Data.Attoparsec.ByteString as P
34import Data.Attoparsec.ByteString.Char8 as PC
35import Data.Attoparsec.ByteString.Lazy as LP
36import Data.ByteString.Lazy.Builder as B
37import Data.ByteString.Internal (c2w, w2c)
38import Data.Functor
39import Data.Int
40import Data.List
41import Data.Maybe (catMaybes)
42import Data.Monoid
43import Data.Text.Lazy.Encoding
44import Data.Word
45import qualified Data.HashMap.Strict as Map
46
47import Thrift.Protocol
48import Thrift.Transport
49import Thrift.Types
50
51import qualified Data.ByteString.Lazy as LBS
52import qualified Data.Text.Lazy as LT
53
54-- | The JSON Protocol data uses the standard 'TSimpleJSONProtocol'. Data is
55-- encoded as a JSON 'ByteString'
56data JSONProtocol t = JSONProtocol t
57 -- ^ Construct a 'JSONProtocol' with a 'Transport'
58
59instance Protocol JSONProtocol where
60 getTransport (JSONProtocol t) = t
61
62 writeMessageBegin (JSONProtocol t) (s, ty, sq) = tWrite t $ toLazyByteString $
63 B.char8 '[' <> buildShowable (1 :: Int32) <>
64 B.string8 ",\"" <> escape (encodeUtf8 s) <> B.char8 '\"' <>
65 B.char8 ',' <> buildShowable (fromEnum ty) <>
66 B.char8 ',' <> buildShowable sq <>
67 B.char8 ','
68 writeMessageEnd (JSONProtocol t) = tWrite t "]"
69 readMessageBegin p = runParser p $ skipSpace *> do
70 _ver :: Int32 <- lexeme (PC.char8 '[') *> lexeme (signed decimal)
71 bs <- lexeme (PC.char8 ',') *> lexeme escapedString
72 case decodeUtf8' bs of
73 Left _ -> fail "readMessage: invalid text encoding"
74 Right str -> do
75 ty <- toEnum <$> (lexeme (PC.char8 ',') *> lexeme (signed decimal))
76 seqNum <- lexeme (PC.char8 ',') *> lexeme (signed decimal)
77 _ <- PC.char8 ','
78 return (str, ty, seqNum)
79 readMessageEnd p = void $ runParser p (PC.char8 ']')
80
81 serializeVal _ = toLazyByteString . buildJSONValue
82 deserializeVal _ ty bs =
83 case LP.eitherResult $ LP.parse (parseJSONValue ty) bs of
84 Left s -> error s
85 Right val -> val
86
87 readVal p ty = runParser p $ skipSpace *> parseJSONValue ty
88
89
90-- Writing Functions
91
92buildJSONValue :: ThriftVal -> Builder
93buildJSONValue (TStruct fields) = B.char8 '{' <> buildJSONStruct fields <> B.char8 '}'
94buildJSONValue (TMap kt vt entries) =
95 B.char8 '[' <> B.char8 '"' <> getTypeName kt <> B.char8 '"' <>
96 B.char8 ',' <> B.char8 '"' <> getTypeName vt <> B.char8 '"' <>
97 B.char8 ',' <> buildShowable (length entries) <>
98 B.char8 ',' <> B.char8 '{' <> buildJSONMap entries <> B.char8 '}' <>
99 B.char8 ']'
100buildJSONValue (TList ty entries) =
101 B.char8 '[' <> B.char8 '"' <> getTypeName ty <> B.char8 '"' <>
102 B.char8 ',' <> buildShowable (length entries) <>
103 (if length entries > 0
104 then B.char8 ',' <> buildJSONList entries
105 else mempty) <>
106 B.char8 ']'
107buildJSONValue (TSet ty entries) = buildJSONValue (TList ty entries)
108buildJSONValue (TBool b) = if b then B.string8 "true" else B.string8 "false"
109buildJSONValue (TByte b) = buildShowable b
110buildJSONValue (TI16 i) = buildShowable i
111buildJSONValue (TI32 i) = buildShowable i
112buildJSONValue (TI64 i) = buildShowable i
113buildJSONValue (TDouble d) = buildShowable d
114buildJSONValue (TString s) = B.char8 '\"' <> escape s <> B.char8 '\"'
115
116buildJSONStruct :: Map.HashMap Int16 (LT.Text, ThriftVal) -> Builder
117buildJSONStruct = mconcat . intersperse (B.char8 ',') . Map.foldrWithKey buildField []
118 where
119 buildField fid (_,val) = (:) $
120 B.char8 '"' <> buildShowable fid <> B.string8 "\":" <>
121 B.char8 '{' <>
122 B.char8 '"' <> getTypeName (getTypeOf val) <> B.string8 "\":" <>
123 buildJSONValue val <>
124 B.char8 '}'
125
126buildJSONMap :: [(ThriftVal, ThriftVal)] -> Builder
127buildJSONMap = mconcat . intersperse (B.char8 ',') . map buildKV
128 where
129 buildKV (key@(TString _), val) =
130 buildJSONValue key <> B.char8 ':' <> buildJSONValue val
131 buildKV (key, val) =
132 B.char8 '\"' <> buildJSONValue key <> B.string8 "\":" <> buildJSONValue val
133buildJSONList :: [ThriftVal] -> Builder
134buildJSONList = mconcat . intersperse (B.char8 ',') . map buildJSONValue
135
136buildShowable :: Show a => a -> Builder
137buildShowable = B.string8 . show
138
139-- Reading Functions
140
141parseJSONValue :: ThriftType -> Parser ThriftVal
142parseJSONValue (T_STRUCT tmap) =
143 TStruct <$> (lexeme (PC.char8 '{') *> parseJSONStruct tmap <* PC.char8 '}')
144parseJSONValue (T_MAP kt vt) = fmap (TMap kt vt) $
145 between '[' ']' $
146 lexeme escapedString *> lexeme (PC.char8 ',') *>
147 lexeme escapedString *> lexeme (PC.char8 ',') *>
148 lexeme decimal *> lexeme (PC.char8 ',') *>
149 between '{' '}' (parseJSONMap kt vt)
150parseJSONValue (T_LIST ty) = fmap (TList ty) $
151 between '[' ']' $ do
Rhys Adamsf48e3392015-05-12 09:51:00 +0900152 len <- lexeme escapedString *> lexeme (PC.char8 ',') *> lexeme decimal
153 if len > 0
154 then lexeme (PC.char8 ',') *> parseJSONList ty
155 else return []
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700156parseJSONValue (T_SET ty) = fmap (TSet ty) $
157 between '[' ']' $ do
Rhys Adamsf48e3392015-05-12 09:51:00 +0900158 len <- lexeme escapedString *> lexeme (PC.char8 ',') *> lexeme decimal
159 if len > 0
160 then lexeme (PC.char8 ',') *> parseJSONList ty
161 else return []
Noam Zilbersteinaf5d64a2014-07-31 15:44:13 -0700162parseJSONValue T_BOOL =
163 (TBool True <$ string "true") <|> (TBool False <$ string "false")
164parseJSONValue T_BYTE = TByte <$> signed decimal
165parseJSONValue T_I16 = TI16 <$> signed decimal
166parseJSONValue T_I32 = TI32 <$> signed decimal
167parseJSONValue T_I64 = TI64 <$> signed decimal
168parseJSONValue T_DOUBLE = TDouble <$> double
169parseJSONValue T_STRING = TString <$> escapedString
170parseJSONValue T_STOP = fail "parseJSONValue: cannot parse type T_STOP"
171parseJSONValue T_VOID = fail "parseJSONValue: cannot parse type T_VOID"
172
173parseAnyValue :: Parser ()
174parseAnyValue = choice $
175 skipBetween '{' '}' :
176 skipBetween '[' ']' :
177 map (void . parseJSONValue)
178 [ T_BOOL
179 , T_I16
180 , T_I32
181 , T_I64
182 , T_DOUBLE
183 , T_STRING
184 ]
185 where
186 skipBetween :: Char -> Char -> Parser ()
187 skipBetween a b = between a b $ void (PC.satisfy (\c -> c /= a && c /= b))
188 <|> skipBetween a b
189
190parseJSONStruct :: TypeMap -> Parser (Map.HashMap Int16 (LT.Text, ThriftVal))
191parseJSONStruct tmap = Map.fromList . catMaybes <$> parseField
192 `sepBy` lexeme (PC.char8 ',')
193 where
194 parseField = do
195 fid <- lexeme (between '"' '"' decimal) <* lexeme (PC.char8 ':')
196 case Map.lookup fid tmap of
197 Just (str, ftype) -> between '{' '}' $ do
198 _ <- lexeme (escapedString) *> lexeme (PC.char8 ':')
199 val <- lexeme (parseJSONValue ftype)
200 return $ Just (fid, (str, val))
201 Nothing -> lexeme parseAnyValue *> return Nothing
202
203parseJSONMap :: ThriftType -> ThriftType -> Parser [(ThriftVal, ThriftVal)]
204parseJSONMap kt vt =
205 ((,) <$> lexeme (PC.char8 '"' *> parseJSONValue kt <* PC.char8 '"') <*>
206 (lexeme (PC.char8 ':') *> lexeme (parseJSONValue vt))) `sepBy`
207 lexeme (PC.char8 ',')
208
209parseJSONList :: ThriftType -> Parser [ThriftVal]
210parseJSONList ty = lexeme (parseJSONValue ty) `sepBy` lexeme (PC.char8 ',')
211
212escapedString :: Parser LBS.ByteString
213escapedString = PC.char8 '"' *>
214 (LBS.pack <$> P.many' (escapedChar <|> notChar8 '"')) <*
215 PC.char8 '"'
216
217escapedChar :: Parser Word8
218escapedChar = PC.char8 '\\' *> (c2w <$> choice
219 [ '\SOH' <$ P.string "u0001"
220 , '\STX' <$ P.string "u0002"
221 , '\ETX' <$ P.string "u0003"
222 , '\EOT' <$ P.string "u0004"
223 , '\ENQ' <$ P.string "u0005"
224 , '\ACK' <$ P.string "u0006"
225 , '\BEL' <$ P.string "u0007"
226 , '\BS' <$ P.string "u0008"
227 , '\VT' <$ P.string "u000b"
228 , '\FF' <$ P.string "u000c"
229 , '\CR' <$ P.string "u000d"
230 , '\SO' <$ P.string "u000e"
231 , '\SI' <$ P.string "u000f"
232 , '\DLE' <$ P.string "u0010"
233 , '\DC1' <$ P.string "u0011"
234 , '\DC2' <$ P.string "u0012"
235 , '\DC3' <$ P.string "u0013"
236 , '\DC4' <$ P.string "u0014"
237 , '\NAK' <$ P.string "u0015"
238 , '\SYN' <$ P.string "u0016"
239 , '\ETB' <$ P.string "u0017"
240 , '\CAN' <$ P.string "u0018"
241 , '\EM' <$ P.string "u0019"
242 , '\SUB' <$ P.string "u001a"
243 , '\ESC' <$ P.string "u001b"
244 , '\FS' <$ P.string "u001c"
245 , '\GS' <$ P.string "u001d"
246 , '\RS' <$ P.string "u001e"
247 , '\US' <$ P.string "u001f"
248 , '\DEL' <$ P.string "u007f"
249 , '\0' <$ PC.char '0'
250 , '\a' <$ PC.char 'a'
251 , '\b' <$ PC.char 'b'
252 , '\f' <$ PC.char 'f'
253 , '\n' <$ PC.char 'n'
254 , '\r' <$ PC.char 'r'
255 , '\t' <$ PC.char 't'
256 , '\v' <$ PC.char 'v'
257 , '\"' <$ PC.char '"'
258 , '\'' <$ PC.char '\''
259 , '\\' <$ PC.char '\\'
260 , '/' <$ PC.char '/'
261 ])
262
263escape :: LBS.ByteString -> Builder
264escape = LBS.foldl' escapeChar mempty
265 where
266 escapeChar b w = b <> (B.lazyByteString $ case w2c w of
267 '\0' -> "\\0"
268 '\b' -> "\\b"
269 '\f' -> "\\f"
270 '\n' -> "\\n"
271 '\r' -> "\\r"
272 '\t' -> "\\t"
273 '\"' -> "\\\""
274 '\\' -> "\\\\"
275 '\SOH' -> "\\u0001"
276 '\STX' -> "\\u0002"
277 '\ETX' -> "\\u0003"
278 '\EOT' -> "\\u0004"
279 '\ENQ' -> "\\u0005"
280 '\ACK' -> "\\u0006"
281 '\BEL' -> "\\u0007"
282 '\VT' -> "\\u000b"
283 '\SO' -> "\\u000e"
284 '\SI' -> "\\u000f"
285 '\DLE' -> "\\u0010"
286 '\DC1' -> "\\u0011"
287 '\DC2' -> "\\u0012"
288 '\DC3' -> "\\u0013"
289 '\DC4' -> "\\u0014"
290 '\NAK' -> "\\u0015"
291 '\SYN' -> "\\u0016"
292 '\ETB' -> "\\u0017"
293 '\CAN' -> "\\u0018"
294 '\EM' -> "\\u0019"
295 '\SUB' -> "\\u001a"
296 '\ESC' -> "\\u001b"
297 '\FS' -> "\\u001c"
298 '\GS' -> "\\u001d"
299 '\RS' -> "\\u001e"
300 '\US' -> "\\u001f"
301 '\DEL' -> "\\u007f"
302 _ -> LBS.singleton w)
303
304lexeme :: Parser a -> Parser a
305lexeme = (<* skipSpace)
306
307notChar8 :: Char -> Parser Word8
308notChar8 c = P.satisfy (/= c2w c)
309
310between :: Char -> Char -> Parser a -> Parser a
311between a b p = lexeme (PC.char8 a) *> lexeme p <* lexeme (PC.char8 b)
312
313getTypeName :: ThriftType -> Builder
314getTypeName ty = B.string8 $ case ty of
315 T_STRUCT _ -> "rec"
316 T_MAP _ _ -> "map"
317 T_LIST _ -> "lst"
318 T_SET _ -> "set"
319 T_BOOL -> "tf"
320 T_BYTE -> "i8"
321 T_I16 -> "i16"
322 T_I32 -> "i32"
323 T_I64 -> "i64"
324 T_DOUBLE -> "dbl"
325 T_STRING -> "str"
326 _ -> error "Unrecognized Type"
327