-- | Simple JSON-RPC stuff, and JSON helper functions {-# LANGUAGE CPP #-} module Bitcoin.RPC.JSON where -------------------------------------------------------------------------------- import Data.List ( sort ) import Control.Applicative import Text.JSON import qualified Data.ByteString as B import Bitcoin.Misc.OctetStream import Bitcoin.Misc.HexString import Bitcoin.Misc.UnixTime import Bitcoin.Protocol.Hash import Bitcoin.Protocol.Address import Bitcoin.Protocol.Amount import Bitcoin.Protocol.Base64 import Bitcoin.Protocol.Hash import Bitcoin.Protocol.Key import Bitcoin.Protocol.Signature import Bitcoin.Script.Base import Bitcoin.BlockChain.Base -------------------------------------------------------------------------------- -- * JSON-RPC type RequestId = String data Request a = Request { requestMethod :: String , requestParams :: [a] , requestId :: RequestId } deriving Show data Response = Response { responseResult :: JSValue , responseError :: Maybe JSValue , responseId :: RequestId } deriving Show data Notification a = Notification { notifMethod :: String , notifParams :: [a] } deriving Show -------------------------------------------------------------------------------- encodeRequest :: JSON a => Request a -> JSValue encodeRequest (Request method params rqid) = JSObject $ toJSObject [ ( "method" , jsString method ) , ( "params" , JSArray (map showJSON params) ) -- , ( "id" , jsNumber rqid ) , ( "id" , jsString rqid ) ] encodeNotification :: JSON a => Notification a -> JSValue encodeNotification (Notification method params) = JSObject $ toJSObject [ ( "method" , jsString method ) , ( "params" , JSArray (map showJSON params) ) ] decodeResponse :: JSValue -> Maybe Response decodeResponse jv = case jv of JSObject obj -> case (sort keys) of [ "error" , "id" , "result" ] -> case lkp "id" of JSString rqid -> Just $ Response (lkp "result") mberr rqid where rqid = case lkp "id" of -- JSRational _ r -> round r JSString s -> fromJSString s _ -> error "decodeResponse: request id is not a string" mberr = case lkp "error" of JSNull -> Nothing err -> Just err _ -> Nothing _ -> Nothing where kvs = fromJSObject obj keys = map fst kvs lkp k = case lookup k kvs of Just x -> x Nothing -> error "decodeResponse: shouldn't happen" _ -> Nothing -------------------------------------------------------------------------------- -- * misc helper functions jsString :: String -> JSValue jsString = JSString . toJSString jsNumber :: Int -> JSValue jsNumber i = JSRational False (fromIntegral i) myReadJSON :: JSON a => JSValue -> Maybe a myReadJSON js = case readJSON js of Ok y -> Just y Error _ -> Nothing mbJSObject :: JSValue -> Maybe (JSObject JSValue) mbJSObject js = case js of JSObject obj -> Just obj _ -> Nothing eiShowJSON :: (JSON a, JSON b) => Either a b -> JSValue eiShowJSON (Left x) = showJSON x eiShowJSON (Right y) = showJSON y -------------------------------------------------------------------------------- -- * JSON parsing for special types {- -- already defined in Text.JSON instance Applicative Result where pure x = Ok x rf <*> rx = case rf of Error e1 -> Error e1 Ok f -> case rx of Error e2 -> Error e2 Ok x -> Ok (f x) -} -------------------------------------------------------------------------------- -- * JSON instances instance JSON Hash256 where showJSON hash = showJSON $ unsafeReverseHexString $ toHexStringChars hash readJSON jsv = case (readJSON jsv :: Result String) of Ok s | even (length s) -> case safeHexDecode (unsafeReverseHexString s) of Just x -> Ok (fromWord8List x) Nothing -> Error "invalid characters in hex string" | otherwise -> Error $ "hex string of odd length" Error s -> Error s instance JSON UnixTimeStamp where showJSON (UnixTimeStamp x) = showJSON x readJSON jsv = UnixTimeStamp <$> readJSON jsv instance JSON Address where showJSON (Address x) = showJSON x readJSON jsv = Address <$> readJSON jsv instance JSON Amount where showJSON x = showJSON (doubleAmount x) readJSON jsv = amountFromDouble <$> readJSON jsv -- | Unfortunately, Text.JSON already have a ByteString instance, which is different from what we need; hence this newtype newtype BS = BS { unBS :: B.ByteString } instance JSON BS where showJSON (BS bs) = showJSON (toHexStringChars bs) readJSON hex = case readJSON hex of Ok s | even (length s) -> case safeHexDecode s of Just x -> Ok (BS (fromWord8List x)) Nothing -> Error "invalid characters in hex string" | otherwise -> Error $ "hex string of odd length" Error s -> Error s instance JSON PubKey where showJSON pubkey = showJSON $ BS (encodePubKeyNative pubkey :: B.ByteString) readJSON jsv = case (readJSON jsv :: Result BS) of Error err -> Error err Ok bs -> case decodePubKey (unBS bs) of Nothing -> Error "readJSON/PubKey: cannot decode pubkey" Just pk -> Ok pk instance JSON RawScript where showJSON (RawScript bs) = showJSON (BS bs) readJSON jsv = (RawScript . unBS) <$> readJSON jsv instance JSON RawTx where showJSON (RawTx bs) = showJSON (BS bs) readJSON jsv = (RawTx . unBS) <$> readJSON jsv instance JSON WIF where showJSON (WIF wif) = showJSON wif readJSON jsv = WIF <$> readJSON jsv --------------------------------------------------------------------------------