{-# LANGUAGE OverloadedStrings, DeriveFunctor, TypeSynonymInstances, FlexibleInstances #-} module HsDev.Server.Message ( Message(..), messagesById, Request(..), requestToArgs, withOpts, withoutOpts, Notification(..), Result(..), ResultPart(..), Response, isNotification, notification, result, responseError, resultPart, groupResponses, responsesById ) where import Control.Arrow (first) import Control.Applicative import Control.Monad (join) import Data.Aeson hiding (Error, Result) import Data.Aeson.Types (Pair) import Data.Either (lefts, isRight) import Data.List (unfoldr) import Data.Maybe import Data.Map (Map) import qualified Data.Map as M import Data.Monoid (mempty, mconcat) import Data.Foldable (Foldable(foldMap)) import Data.Text (unpack) import Data.Traversable (Traversable(traverse)) import System.Console.Args hiding (withOpts) import HsDev.Util ((.::), (.::?), objectUnion) -- | Message with id to link request and response data Message a = Message { messageId :: Maybe String, message :: a } deriving (Eq, Ord, Functor) instance ToJSON a => ToJSON (Message a) where toJSON (Message i m) = object ["id" .= i] `objectUnion` toJSON m instance FromJSON a => FromJSON (Message a) where parseJSON = withObject "message" $ \v -> Message <$> fmap join (v .::? "id") <*> parseJSON (Object v) instance Foldable Message where foldMap f (Message _ m) = f m instance Traversable Message where traverse f (Message i m) = Message i <$> f m -- | Get messages by id messagesById :: Maybe String -> [Message a] -> [a] messagesById i = map message . filter ((== i) . messageId) -- | Request from client data Request = Request { requestCommand :: String, requestArgs :: [String], requestOpts :: Opts String } requestToArgs :: Request -> Args requestToArgs (Request c as opts) = Args (words c ++ as) opts instance ToJSON Request where toJSON (Request c as os) = object [ "command" .= c, "args" .= as, "opts" .= os] instance FromJSON Request where parseJSON = withObject "request" $ \v -> Request <$> v .:: "command" <*> (fromMaybe [] <$> v .::? "args") <*> (fromMaybe mempty <$> v .::? "opts") -- | Add options to request withOpts :: Request -> [Opts String] -> Request withOpts r os = r { requestOpts = mconcat (requestOpts r : os) } -- | Remove options from request withoutOpts :: Request -> [String] -> Request withoutOpts r os = r { requestOpts = Opts $ foldr (.) id (map M.delete os) $ getOpts (requestOpts r) } -- | Notification from server data Notification = Notification Value instance ToJSON Notification where toJSON (Notification v) = object ["notify" .= v] instance FromJSON Notification where parseJSON = withObject "notification" $ \v -> Notification <$> v .:: "notify" -- | Result from server data Result = Result Value | -- ^ Result Error String (Map String Value) -- ^ Error instance ToJSON Result where toJSON (Result r) = object [ "result" .= r] toJSON (Error msg rs) = object [ "error" .= msg, "details" .= toJSON rs] instance FromJSON Result where parseJSON = withObject "result" $ \v -> foldr1 (<|>) [ Result <$> v .:: "result", Error <$> v .:: "error" <*> v .:: "details"] -- | Part of result list, returns via notification data ResultPart = ResultPart Value instance ToJSON ResultPart where toJSON (ResultPart r) = object ["result-part" .= r] instance FromJSON ResultPart where parseJSON = withObject "result-part" $ \v -> ResultPart <$> v .:: "result-part" type Response = Either Notification Result isNotification :: Response -> Bool isNotification = either (const True) (const False) notification :: ToJSON a => a -> Response notification = Left . Notification . toJSON result :: ToJSON a => a -> Response result = Right . Result . toJSON responseError :: String -> [Pair] -> Response responseError e ds = Right $ Error e $ M.fromList $ map (first unpack) ds resultPart :: ToJSON a => a -> Notification resultPart = Notification . toJSON . ResultPart . toJSON instance ToJSON Response where toJSON (Left n) = toJSON n toJSON (Right r) = toJSON r instance FromJSON Response where parseJSON v = (Left <$> parseJSON v) <|> (Right <$> parseJSON v) groupResponses :: [Response] -> [([Notification], Result)] groupResponses = unfoldr break' where break' :: [Response] -> Maybe (([Notification], Result), [Response]) break' [] = Nothing break' cs = Just ((lefts ns, r), drop 1 cs') where (ns, cs') = break isRight cs r = case cs' of (Right r' : _) -> r' [] -> Error "groupResponses: no result" mempty _ -> error "groupResponses: impossible happened" responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)] responsesById i = groupResponses . messagesById i