{-# LANGUAGE TemplateHaskell #-}
module DMCC.XML.Response
( Response (..)
, Event (..)
, fromXml
)
where
import DMCC.Prelude
import Data.CaseInsensitive (mk)
import Data.List (foldl1')
import qualified Data.Text as T
import qualified Data.Text.Read as T
import Data.Aeson.TH
import Text.XML
import Text.XML.Cursor
import DMCC.Types
data Response
= UnknownResponse LByteString
| MalformedResponse LByteString SomeException
| StartApplicationSessionPosResponse
{ sessionID :: Text
, actualProtocolVersion :: Text
, actualSessionDuration :: Int
}
| StartApplicationSessionNegResponse
| GetDeviceIdResponse
{ device :: DeviceId
}
| GetThirdPartyDeviceIdResponse
{ device :: DeviceId
}
| MonitorStartResponse
{ monitorCrossRefID :: Text
}
| MakeCallResponse
{ newCallId :: CallId
, newUcid :: UCID
}
| SingleStepConferenceCallResponse
{ conferencedCall :: CallId
}
| GetAgentStateResponse
{ agentState :: Maybe AgentState
, reasonCode :: Text
}
| GetCallLinkageDataResponse
{ linkageUcid :: UCID
}
| EventResponse
{ monitorCrossRefID :: Text
, event :: Event
}
| CSTAErrorCodeResponse
{ errorText :: Text
}
deriving Show
data Event =
UnknownEvent
| DeliveredEvent
{ callId :: CallId
, distributingVdn :: DeviceId
, ucid :: UCID
, callingDevice :: DeviceId
, calledDevice :: DeviceId
}
| DivertedEvent
{ callId :: CallId
}
| OriginatedEvent
{ callId :: CallId
, callingDevice :: DeviceId
, calledDevice :: DeviceId
}
| EstablishedEvent
{ callId :: CallId
}
| FailedEvent
{ callId :: CallId
}
| ConnectionClearedEvent
{ callId :: CallId
, releasingDevice :: DeviceId
}
| HeldEvent
{ callId :: CallId
}
| RetrievedEvent
{ callId :: CallId
}
| ConferencedEvent
{ primaryOldCall :: CallId
, secondaryOldCall :: CallId
}
| TransferedEvent
{ primaryOldCall :: CallId
, secondaryOldCall :: CallId
}
deriving Show
$(deriveJSON
defaultOptions{sumEncoding = defaultTaggedObject{tagFieldName="event"}}
''Event)
fromXml :: LByteString -> Response
fromXml xml
= case parseLBS def xml of
Left err -> MalformedResponse xml err
Right doc -> let cur = fromDocument doc
evResp = EventResponse (text cur "monitorCrossRefID")
in case nameLocalName $ elementName $ documentRoot doc of
"StartApplicationSessionPosResponse" ->
StartApplicationSessionPosResponse
{ sessionID = text cur "sessionID"
, actualProtocolVersion = text cur "actualProtocolVersion"
, actualSessionDuration = decimal cur "actualSessionDuration"
}
"StartApplicationSessionNegResponse" ->
StartApplicationSessionNegResponse
"GetDeviceIdResponse" ->
GetDeviceIdResponse
{ device = DeviceId $ mk $ text cur "device"
}
"GetThirdPartyDeviceIdResponse" ->
GetThirdPartyDeviceIdResponse
{ device = DeviceId $ mk $ text cur "device"
}
"MonitorStartResponse" ->
MonitorStartResponse
{monitorCrossRefID = text cur "monitorCrossRefID"
}
"MakeCallResponse" ->
MakeCallResponse
{ newCallId = CallId $ textFromPath cur "callingDevice" ["callId"]
, newUcid =
UCID $ text cur "globallyUniqueCallLinkageID"
}
"SingleStepConferenceCallResponse" ->
SingleStepConferenceCallResponse
{ conferencedCall =
CallId $ textFromPath cur "conferencedCall" ["callId"]
}
"GetAgentStateResponse" ->
GetAgentStateResponse
{ agentState =
let
raw = textFromPath cur "agentStateList"
[ "agentStateEntry"
, "agentInfo"
, "agentInfoItem"
, "agentState"
]
state | raw == "agentReady" =
Just $ Settable Ready
| raw == "agentWorkingAfterCall" =
Just $ Settable AfterCall
| raw == "agentNotReady" =
Just $ Settable NotReady
| raw == "agentBusy" =
Just Busy
| otherwise = Nothing
in
state
, reasonCode = textFromPath cur "extensions"
[ "privateData"
, "private"
, "GetAgentStateResponsePrivateData"
, "reasonCode"
]
}
"GetCallLinkageDataResponse" ->
GetCallLinkageDataResponse
{ linkageUcid = UCID $ text cur "globallyUniqueCallLinkageID"
}
"DeliveredEvent" -> evResp
DeliveredEvent
{ callId =
CallId $ textFromPath cur "connection" ["callId"]
, distributingVdn =
DeviceId $ mk $
textFromPath cur "distributingVDN" ["deviceIdentifier"]
, ucid =
UCID $ text cur "globallyUniqueCallLinkageID"
, callingDevice =
DeviceId $ mk $ textFromPath cur "callingDevice" ["deviceIdentifier"]
, calledDevice =
DeviceId $ mk $ textFromPath cur "calledDevice" ["deviceIdentifier"]
}
"OriginatedEvent" -> evResp
OriginatedEvent
{ callId =
CallId $ textFromPath cur "originatedConnection" ["callId"]
, callingDevice =
DeviceId $ mk $ textFromPath cur "callingDevice" ["deviceIdentifier"]
, calledDevice =
DeviceId $ mk $ textFromPath cur "calledDevice" ["deviceIdentifier"]
}
"DivertedEvent" -> evResp
DivertedEvent
{ callId =
CallId $ textFromPath cur "connection" ["callID"]
}
"EstablishedEvent" -> evResp
EstablishedEvent
{ callId =
CallId $ textFromPath cur "establishedConnection" ["callId"]
}
"FailedEvent" -> evResp
FailedEvent
{ callId =
CallId $ textFromPath cur "failedConnection" ["callId"]
}
"HeldEvent" -> evResp
HeldEvent
{ callId =
CallId $ textFromPath cur "heldConnection" ["callId"]
}
"RetrievedEvent" -> evResp
RetrievedEvent
{ callId =
CallId $ textFromPath cur "retrievedConnection" ["callId"]
}
"ConferencedEvent" -> evResp
ConferencedEvent
{ primaryOldCall =
CallId $ textFromPath cur "primaryOldCall" ["callId"]
, secondaryOldCall =
CallId $ textFromPath cur "secondaryOldCall" ["callId"]
}
"TransferedEvent" -> evResp
TransferedEvent
{ primaryOldCall =
CallId $ textFromPath cur "primaryOldCall" ["callId"]
, secondaryOldCall =
CallId $ textFromPath cur "secondaryOldCall" ["callId"]
}
"ConnectionClearedEvent" -> evResp
ConnectionClearedEvent
{ callId =
CallId $ textFromPath cur "droppedConnection" ["callId"]
, releasingDevice =
DeviceId $ mk $
textFromPath cur "releasingDevice" ["deviceIdentifier"]
}
"CSTAErrorCode" ->
CSTAErrorCodeResponse
{ errorText =
let
msg = T.concat $ cur $// content
err = case map node (cur $/ checkElement (const True)) of
(NodeElement el:_) ->
nameLocalName $ elementName el
_ -> "CSTAErrorCode"
in
T.concat [err, "/", msg]
}
_ -> UnknownResponse xml
text :: Cursor -> Text -> Text
text c n = textFromPath c n []
decimal :: Cursor -> Text -> Int
decimal c n =
case T.decimal txt of
Right (x, "") -> x
_ -> error $ "Can't parse as decimal: " <> show txt
where
txt = text c n :: Text
textFromPath :: Cursor -> Text -> [Text] -> Text
textFromPath cur rootName extraNames =
fromMaybe "" $ headMay contents
where
contents =
cur $// foldl1' (&/) (map laxElement $ rootName : extraNames) &/ content