{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} module Data.Morpheus.Types.Internal.Subscription.Apollo ( ApolloAction(..) , apolloFormat , acceptApolloRequest , toApolloResponse , Validation ) where import Data.Maybe ( maybe ) import Control.Monad.IO.Class ( MonadIO(..) ) import Data.Aeson ( FromJSON (..) , ToJSON (..) , Value (..) , eitherDecode , encode , pairs , withObject , (.:) , (.:?) , (.=) ) import Data.ByteString.Lazy.Char8 ( ByteString , pack ) import Data.Semigroup ( (<>) ) import Data.Text ( Text , unpack ) import GHC.Generics ( Generic ) import Network.WebSockets ( AcceptRequest (..) , RequestHead , getRequestSubprotocols , PendingConnection , Connection , acceptRequestWith , pendingRequest ) -- MORPHEUS import Data.Morpheus.Types.IO ( GQLResponse , GQLRequest (..) ) type ID = Text data ApolloSubscription payload = ApolloSubscription { apolloId :: Maybe ID , apolloType :: Text , apolloPayload :: Maybe payload } deriving (Show, Generic) instance FromJSON a => FromJSON (ApolloSubscription a) where parseJSON = withObject "ApolloSubscription" objectParser where objectParser o = ApolloSubscription <$> o .:? "id" <*> o .: "type" <*> o .:? "payload" data RequestPayload = RequestPayload { payloadOperationName :: Maybe Text , payloadQuery :: Maybe Text , payloadVariables :: Maybe Value } deriving (Show, Generic) instance FromJSON RequestPayload where parseJSON = withObject "ApolloPayload" objectParser where objectParser o = RequestPayload <$> o .:? "operationName" <*> o .:? "query" <*> o .:? "variables" instance ToJSON a => ToJSON (ApolloSubscription a) where toEncoding (ApolloSubscription id' type' payload') = pairs $ "id" .= id' <> "type" .= type' <> "payload" .= payload' acceptApolloRequest :: MonadIO m => PendingConnection -> m Connection acceptApolloRequest pending = liftIO $ acceptRequestWith pending (acceptApolloSubProtocol (pendingRequest pending)) acceptApolloSubProtocol :: RequestHead -> AcceptRequest acceptApolloSubProtocol reqHead = apolloProtocol (getRequestSubprotocols reqHead) where apolloProtocol ["graphql-subscriptions"] = AcceptRequest (Just "graphql-subscriptions") [] apolloProtocol ["graphql-ws"] = AcceptRequest (Just "graphql-ws") [] apolloProtocol _ = AcceptRequest Nothing [] toApolloResponse :: ID -> GQLResponse -> ByteString toApolloResponse sid val = encode $ ApolloSubscription (Just sid) "data" (Just val) data ApolloAction = SessionStop ID | SessionStart ID GQLRequest | ConnectionInit type Validation = Either ByteString apolloFormat :: ByteString -> Validation ApolloAction apolloFormat = validateReq . eitherDecode where validateReq :: Either String (ApolloSubscription RequestPayload) -> Validation ApolloAction validateReq = either (Left . pack) validateSub ------------------------------------- validateSub :: ApolloSubscription RequestPayload -> Validation ApolloAction validateSub ApolloSubscription { apolloType = "connection_init" } = pure ConnectionInit validateSub ApolloSubscription { apolloType = "start", apolloId , apolloPayload } = do sessionId <- validateSession apolloId payload <- validatePayload apolloPayload pure $ SessionStart sessionId payload validateSub ApolloSubscription { apolloType = "stop", apolloId } = SessionStop <$> validateSession apolloId validateSub ApolloSubscription { apolloType } = Left $ "Unknown Request type \""<> pack (unpack apolloType) <> "\"." -------------------------------------------- validateSession :: Maybe ID -> Validation ID validateSession = maybe (Left "\"id\" was not provided") Right ------------------------------------- validatePayload = maybe (Left "\"payload\" was not provided") validatePayloadContent ------------------------------------- validatePayloadContent RequestPayload { payloadQuery , payloadOperationName = operationName , payloadVariables = variables } = do query <- maybe (Left "\"payload.query\" was not provided") Right payloadQuery pure $ GQLRequest {query, operationName, variables}