packages feed

haste-app-0.1.0.0: src/Haste/App/Protocol.hs

{-# LANGUAGE OverloadedStrings #-}
-- | Haste.App client-server protocol.
module Haste.App.Protocol
  ( module Haste.App.Protocol.Types
  , ServerCall (..), ServerReply (..)
  , ServerException (..), NetworkException (..)
  ) where
import Control.Exception
import Control.Monad
import Data.Typeable
import GHC.StaticPtr
import Haste.Serialize
import Haste.JSON
import qualified Haste.Foreign as HF
import Haste.App.Protocol.Types
import GHC.Fingerprint.Type
import Data.Bits
import Data.Word
import Haste (JSString)

-- | A method call to the server.
data ServerCall = ServerCall
  { scNonce  :: !Nonce
  , scMethod :: !StaticKey
  , scArgs   :: ![JSON]
  }

-- | A reply to a ServerCall.
data ServerReply = ServerReply
  { srNonce  :: !Nonce
  , srResult :: !JSON
  } | ServerEx
  { seNonce :: !Nonce
  , seMessage :: !String
  } deriving (Typeable, Show)

-- | Throw a server exception to the client.
data ServerException = ServerException String
  deriving (Typeable, Show)
instance Exception ServerException

-- | Throw an exception when there's network trouble.
data NetworkException = NetworkException String
  deriving (Typeable, Show)
instance Exception NetworkException

instance Serialize ServerCall where
  parseJSON x = do
    tag <- x .: "tag"
    case tag :: JSString of
      "call" -> ServerCall <$> x .: "nonce" <*> x .: "method" <*> x .: "args"
      _      -> fail $ "No such ServerCall constructor: " ++ show tag
  toJSON (ServerCall n c as) = Dict
    [ ("tag", "call")
    , ("nonce", toJSON n)
    , ("method", toJSON c)
    , ("args", toJSON as)
    ]

instance Serialize ServerReply where
  parseJSON x = do
    t <- x .: "status"
    case t :: JSString of
      "error" -> ServerEx <$> x .: "nonce" <*> x .: "error"
      "ok"    -> ServerReply <$> x .: "nonce" <*> x .: "result"
  toJSON (ServerReply n r) = Dict [("nonce", toJSON n), ("result", r), ("status", "ok")]
  toJSON (ServerEx n m)    = Dict [("nonce", toJSON n), ("error", toJSON m), ("status", "error")]

instance Serialize Word64 where
  parseJSON x = do
    lo <- x .: "lo" :: Parser Double
    hi <- x .: "hi" :: Parser Double
    return $ round lo .|. shiftL (round hi) 32
  toJSON x = Dict
    [ ("lo", toJSON (fromIntegral (x .&. 0xffffffff) :: Double))
    , ("hi", toJSON (fromIntegral (shiftR x 32 .&. 0xffffffff) :: Double))
    ]

instance Serialize Fingerprint where
  parseJSON x = Fingerprint <$> x .: "lo" <*> x .: "hi"
  toJSON (Fingerprint lo hi) = Dict
    [ ("lo", toJSON lo)
    , ("hi", toJSON hi)
    ]