module Network.JsonRpc
( -- * Introduction
  -- $introduction

  -- ** Server Example
  -- $server

  -- ** Client Example
  -- $client

  module Network.JsonRpc.Conduit
, module Network.JsonRpc.Data
) where

import Network.JsonRpc.Conduit
import Network.JsonRpc.Data

-- $introduction
--
-- This JSON-RPC library is fully-compatible with JSON-RPC 2.0 and
-- partially-compatible with JSON-RPC 1.0. It provides an interface that
-- combines a JSON-RPC client and server. It can set and keep track of request
-- ids to parse responses.  There is support for sending and receiving
-- notifications. You may use any underlying transport.  Basic TCP client and
-- server provided.
--
-- The recommended interface to this library is provided as conduits that
-- encode outgoing messages, and decode incoming messages.  Incoming messages
-- are delivered as an 'IncomingMsg' data structure, while outgoing messages
-- are sent in a 'Message' data structure.  The former packs responses and
-- errors with their corresponding request, and has a separate constructor for
-- decoding errors.
--
-- A JSON-RPC application using this interface is considered to be
-- peer-to-peer, as it can send and receive all types of JSON-RPC message
-- independent of whether it originated the connection.
--
-- Type classes 'ToRequest', 'ToNotif' are for data that can be converted into
-- JSON-RPC requests and notifications respectively. An instance of aeson's
-- 'ToJSON' class is also required to serialize these data structures.  Make
-- sure that they serialize as a structured JSON value (array or object) that
-- can go into the params field of the JSON-RPC object.  Type classes
-- 'FromRequest', 'FromNotif' and 'FromResult' are for deserializing JSON-RPC
-- messages.
--
-- Errors are deserialized to the 'ErrorObj' data type. Only a string is
-- supported as contents inside a JSON-RPC 1.0 error. JSON-RPC 2.0 errors also
-- have a code, and possibly additional data as an aeson 'Value'.


-- $server
--
-- This server returns the current time.
--
-- >{-# LANGUAGE OverloadedStrings #-}
-- >import Data.Aeson.Types
-- >import Data.Conduit
-- >import qualified Data.Conduit.List as CL
-- >import Data.Conduit.Network
-- >import Data.Time.Clock
-- >import Data.Time.Format
-- >import Network.JsonRpc
-- >import System.Locale
-- >
-- >data TimeReq = TimeReq
-- >data TimeRes = TimeRes UTCTime
-- >
-- >instance FromRequest TimeReq where
-- >    paramsParser "time" = Just $ const $ return TimeReq 
-- >    paramsParser _ = Nothing
-- >
-- >instance ToJSON TimeRes where
-- >    toJSON (TimeRes t) = toJSON $ formatTime defaultTimeLocale "%c" t
-- >
-- >srv :: AppConduits () () TimeRes TimeReq () () IO -> IO ()
-- >srv (src, snk) = src $= CL.mapM respond $$ snk
-- >
-- >respond :: IncomingMsg () TimeReq () ()
-- >        -> IO (Message () () TimeRes)
-- >respond (IncomingMsg (MsgRequest (Request ver _ TimeReq i)) Nothing) = do    
-- >    t <- getCurrentTime
-- >    return $ MsgResponse (Response ver (TimeRes t) i)
-- >
-- >respond (IncomingError e) = return $ MsgError e
-- >respond (IncomingMsg (MsgError e) _) = return $ MsgError $ e
-- >respond _ = undefined
-- >
-- >main :: IO ()
-- >main = tcpServer V2 (serverSettings 31337 "127.0.0.1") srv


-- $client
--
-- Corresponding TCP client to get time from server.
--
-- >{-# LANGUAGE OverloadedStrings #-}
-- >import Data.Aeson.Types hiding (Error)
-- >import Data.Conduit
-- >import qualified Data.Conduit.List as CL
-- >import Data.Conduit.Network
-- >import qualified Data.Text as T
-- >import Data.Time.Clock
-- >import Data.Time.Format
-- >import Network.JsonRpc
-- >import System.Locale
-- >
-- >data TimeReq = TimeReq
-- >data TimeRes = TimeRes UTCTime
-- >
-- >instance ToRequest TimeReq where
-- >    requestMethod TimeReq = "time"
-- >
-- >instance ToJSON TimeReq where
-- >    toJSON TimeReq = emptyArray
-- >
-- >instance FromResponse TimeRes where
-- >    parseResult "time" = withText "time" $ \t -> case f t of
-- >        Nothing -> fail "Could not parse time"
-- >        Just t' -> return $ TimeRes t'
-- >      where
-- >        f t = parseTime defaultTimeLocale "%c" (T.unpack t)
-- >
-- >cli :: AppConduits TimeReq () () () () TimeRes IO
-- >    -> IO UTCTime
-- >cli (src, snk) = do
-- >    CL.sourceList [MsgRequest $ buildRequest V2 TimeReq] $$ snk
-- >    ts <- src $$ CL.consume
-- >    case ts of
-- >        [] -> error "No response received"
-- >        [IncomingError (ErrorObj _ m _ _ _)] -> error $ "Unknown: " ++ m
-- >        [IncomingMsg (MsgError (ErrorObj _ m _ _ _)) _] -> error m
-- >        [IncomingMsg (MsgResponse (Response _ (TimeRes t) _)) _] -> return t
-- >        _ -> undefined
-- >
-- >main :: IO ()
-- >main = tcpClient V2 True (clientSettings 31337 "127.0.0.1") cli >>= print