module OpcXmlDaClient
( -- * Operations
Op,
getStatus,
read,
write,
subscribe,
subscriptionPolledRefresh,
subscriptionCancel,
browse,
getProperties,
-- ** Operation parameter types
Uri,
textUri,
RequestTimeout,
millisecondsRequestTimeout,
-- ** Operation errors
Error (..),
-- * Value types
module OpcXmlDaClient.Protocol.Types,
module OpcXmlDaClient.XmlSchemaValues.Types,
)
where
import qualified Data.Text as Text
import qualified Network.HTTP.Client as Hc
import OpcXmlDaClient.Base.Prelude hiding (Read, read)
import OpcXmlDaClient.Protocol.Types
import qualified OpcXmlDaClient.Protocol.XmlConstruction as XmlConstruction
import qualified OpcXmlDaClient.Protocol.XmlParsing as XmlParsing
import OpcXmlDaClient.XmlSchemaValues.Types
import qualified XmlParser
-- * Operations
-- |
-- Alias to an HTTP request operation in the scope of
-- HTTP connection manager, timeout for the operation, URI of the server.
--
-- All errors are explicit and are wrapped by the 'Error' type.
type Op i o = Hc.Manager -> RequestTimeout -> Uri -> i -> IO (Either Error o)
getStatus :: Op GetStatus GetStatusResponse
getStatus = encDecOp XmlConstruction.getStatus XmlParsing.getStatusResponse
read :: Op Read ReadResponse
read = encDecOp XmlConstruction.read XmlParsing.readResponse
write :: Op Write WriteResponse
write = encDecOp XmlConstruction.write XmlParsing.writeResponse
subscribe :: Op Subscribe SubscribeResponse
subscribe = encDecOp XmlConstruction.subscribe XmlParsing.subscribeResponse
subscriptionPolledRefresh :: Op SubscriptionPolledRefresh SubscriptionPolledRefreshResponse
subscriptionPolledRefresh = encDecOp XmlConstruction.subscriptionPolledRefresh XmlParsing.subscriptionPolledRefreshResponse
subscriptionCancel :: Op SubscriptionCancel SubscriptionCancelResponse
subscriptionCancel = encDecOp XmlConstruction.subscriptionCancel XmlParsing.subscriptionCancelResponse
browse :: Op Browse BrowseResponse
browse = encDecOp XmlConstruction.browse XmlParsing.browseResponse
getProperties :: Op GetProperties GetPropertiesResponse
getProperties = encDecOp XmlConstruction.getProperties XmlParsing.getPropertiesResponse
encDecOp :: (i -> ByteString) -> XmlParser.Element (Either SoapFault o) -> Op i o
encDecOp encode decode manager (RequestTimeout timeout) (Uri request) input = do
let encodedInput = encode input
request
{ Hc.method = "POST",
Hc.requestHeaders =
[ ("Content-Type", "application/soap+xml; charset=utf-8")
],
Hc.requestBody = Hc.RequestBodyBS encodedInput,
Hc.responseTimeout = Hc.responseTimeoutMicro (timeout * 1000)
}
& \request -> do
response <- try $ Hc.httpLbs request manager
case response of
Left exc
| Just exc <- fromException @Hc.HttpException exc -> case exc of
Hc.HttpExceptionRequest _ reason -> return $ Left $ HttpError reason
Hc.InvalidUrlException uri reason -> error $ "Invalid URI: " <> uri <> ". " <> reason
| Just exc <- fromException @IOException exc ->
return $ Left $ IoError exc
| otherwise -> throwIO exc
Right response -> do
return $ case XmlParser.parseLazyByteString decode (Hc.responseBody response) of
Right res -> case res of
Right res -> Right res
Left err -> Left $ SoapError err
Left err -> Left $ ParsingError err
-- * Helper types
-- |
-- URI of the server.
newtype Uri = Uri Hc.Request
-- |
-- Construct a correct URI by validating a textual value.
textUri :: Text -> Maybe Uri
textUri = fmap Uri . Hc.parseRequest . Text.unpack
newtype RequestTimeout = RequestTimeout Int
-- |
-- RequestTimeout of 30 seconds.
instance Default RequestTimeout where
def = RequestTimeout 30000
-- |
-- Construct a request timeout value,
-- ensuring that it's in the proper range.
millisecondsRequestTimeout :: Int -> Maybe RequestTimeout
millisecondsRequestTimeout x =
if x >= 0
then Just $ RequestTimeout x
else Nothing
-- * Errors
-- |
-- Error during the execution of an operation.
data Error
= HttpError Hc.HttpExceptionContent
| IoError IOException
| ParsingError Text
| SoapError SoapFault
instance Eq Error where
(HttpError _) == (HttpError _) = False -- NOTE: HttpEcxceptionContent has not EQ instance
(IoError a) == (IoError b) = a == b
(ParsingError a) == (ParsingError b) = a == b
(SoapError a) == (SoapError b) = a == b
(==) _ _ = False
instance Show Error where
show = \case
HttpError a -> showString "HTTP error: " $ show a
IoError a -> showString "IO error: " $ show a
ParsingError a -> showString "Parsing error: " $ Text.unpack a
SoapError a ->
"SOAP fault response with code: " <> show (#code a) <> ". "
<> "Reason: "
<> Text.unpack (#reason a)