packages feed

soap 0.1.0.4 → 0.2.0.0

raw patch · 9 files changed

+388/−217 lines, 9 filesdep +HUnitdep +bytestringdep +conduitdep ~basePVP ok

version bump matches the API change (PVP)

Dependencies added: HUnit, bytestring, conduit, data-default, hspec, mtl, soap, xml-conduit-writer, xml-types

Dependency ranges changed: base

API changes (from Hackage documentation)

- Web.SOAP.Service: SOAPSettings :: String -> Text -> EncodingName -> SOAPSettings
- Web.SOAP.Service: data SOAPSettings
- Web.SOAP.Service: flowNS :: Maybe Text -> Node -> Node
- Web.SOAP.Service: instance Read SOAPSettings
- Web.SOAP.Service: instance Show SOAPSettings
- Web.SOAP.Service: invokeWS :: (ToNodes h, ToNodes i, FromCursor o) => SOAPSettings -> Text -> h -> i -> IO o
- Web.SOAP.Service: invokeWS' :: (ToNodes h, ToNodes i, FromCursor o) => (Request (ResourceT IO) -> Request (ResourceT IO)) -> SOAPSettings -> Text -> h -> i -> IO o
- Web.SOAP.Service: soapCodepage :: SOAPSettings -> EncodingName
- Web.SOAP.Service: soapNamespace :: SOAPSettings -> Text
- Web.SOAP.Service: soapURL :: SOAPSettings -> String
- Web.SOAP.Types: (.=) :: Name -> Text -> Node
- Web.SOAP.Types: (.=:) :: Name -> [Node] -> [Node]
- Web.SOAP.Types: asDict :: Axis -> Cursor -> Dict
- Web.SOAP.Types: class FromCursor a
- Web.SOAP.Types: class ToNodes a where toElement = undefined toElements x = [toElement x] toNodes = map NodeElement . toElements
- Web.SOAP.Types: fromCursor :: FromCursor a => Cursor -> a
- Web.SOAP.Types: instance [overlap ok] (FromCursor a, FromCursor b) => FromCursor (a, b)
- Web.SOAP.Types: instance [overlap ok] (FromCursor a, FromCursor b, FromCursor c) => FromCursor (a, b, c)
- Web.SOAP.Types: instance [overlap ok] FromCursor Dict
- Web.SOAP.Types: instance [overlap ok] ToNodes ()
- Web.SOAP.Types: instance [overlap ok] ToNodes Text
- Web.SOAP.Types: instance [overlap ok] ToNodes [Node]
- Web.SOAP.Types: instance [overlap ok] ToNodes a => ToNodes (Name, a)
- Web.SOAP.Types: instance [overlap ok] ToNodes a => ToNodes [a]
- Web.SOAP.Types: readC :: Read a => Text -> Cursor -> a
- Web.SOAP.Types: readT :: Text -> Cursor -> Text
- Web.SOAP.Types: toElement :: ToNodes a => a -> Element
- Web.SOAP.Types: toElements :: ToNodes a => a -> [Element]
- Web.SOAP.Types: toNodes :: ToNodes a => a -> [Node]
- Web.SOAP.Types: type Dict = HashMap Text Text
+ Network.SOAP: CursorParser :: (Cursor -> a) -> ResponseParser a
+ Network.SOAP: RawParser :: (ByteString -> a) -> ResponseParser a
+ Network.SOAP: StreamParser :: (Parser a) -> ResponseParser a
+ Network.SOAP: data ResponseParser a
+ Network.SOAP: invokeWS :: (ToXML h, ToXML b) => Transport -> String -> h -> b -> ResponseParser a -> IO a
+ Network.SOAP: type Parser a = Sink Event (ResourceT IO) a
+ Network.SOAP: type Transport = String -> Document -> IO ByteString
+ Network.SOAP.Parsing.Cursor: readC :: Read a => Text -> Cursor -> a
+ Network.SOAP.Parsing.Cursor: readDict :: Axis -> Cursor -> Dict
+ Network.SOAP.Parsing.Cursor: readT :: Text -> Cursor -> Text
+ Network.SOAP.Parsing.Cursor: type Dict = HashMap Text Text
+ Network.SOAP.Transport: type Transport = String -> Document -> IO ByteString
+ Network.SOAP.Transport.HTTP.Conduit: clientCert :: FilePath -> FilePath -> IO RequestP
+ Network.SOAP.Transport.HTTP.Conduit: iconv :: EncodingName -> BodyP
+ Network.SOAP.Transport.HTTP.Conduit: initTransport :: EndpointURL -> RequestP -> BodyP -> IO Transport
+ Network.SOAP.Transport.HTTP.Conduit: initTransport_ :: EndpointURL -> IO Transport
+ Network.SOAP.Transport.HTTP.Conduit: runQuery :: Manager -> EndpointURL -> RequestP -> BodyP -> Transport
+ Network.SOAP.Transport.HTTP.Conduit: type BodyP = ByteString -> ByteString
+ Network.SOAP.Transport.HTTP.Conduit: type EndpointURL = String
+ Network.SOAP.Transport.HTTP.Conduit: type RequestP = Request (ResourceT IO) -> Request (ResourceT IO)
+ Network.SOAP.Transport.Mock: handler :: ToXML a => (Document -> IO a) -> Handler
+ Network.SOAP.Transport.Mock: initTransport :: Handlers -> IO Transport
+ Network.SOAP.Transport.Mock: runQuery :: [(String, Handler)] -> Transport
+ Network.SOAP.Transport.Mock: type Handler = Document -> IO ByteString
+ Network.SOAP.Transport.Mock: type Handlers = [(String, Handler)]

Files

soap.cabal view
@@ -1,10 +1,45 @@--- Initial soap.cabal generated by cabal init.  For further documentation, --- see http://haskell.org/cabal/users-guide/- name:                soap-version:             0.1.0.4+version:             0.2.0.0 synopsis:            SOAP client tools-description:         Tools to build SOAP clients using xml-conduit.+description:+  Tools to build SOAP clients using xml-conduit.+  .+  A mildly-complicated example:+  .+  > main = do+  >     -- Initial one-time preparations.+  >     certP <- clientCert "priv/client.crt" "priv/client.key"+  >     transport <- initTransport "https://example.com/soap/endpoint" certP (iconv "cp-1251")+  >+  >     -- Making queries+  >     activeStaff <- listStaff transport True+  >     print activeStaff+  >+  > data Person = Person Text Int deriving Show+  > +  > listStaff :: Transport -> Bool -> IO [Person]+  > listStaff t active = invokeWS t "urn:dummy:listStaff" () body parser+  >     where+  >         body = element "request" $ element "listStaff" $ do+  >                    element "active" $ toXML active+  >                    element "order" "age"+  >                    element "limit" $ toXML (10 :: Int)+  >+  >         parser = StreamParser $ force "no people" $ tagNoAttr "people" $ Parse.many parsePerson+  >+  >         parsePerson = tagName "person" (requireAttr "age") $ \age -> do+  >                           name <- Parse.content+  >                           return $ Person name (read . unpack $ age)+  .+  Changelog+  .+  * 0.2: Switch to xml-conduit-writer for more clean serializers.+         Pluggable transports.+         Raw and streaming parsers.+  .+  * 0.1: Initial implementation, somewhat inflexible and warty, but working+         with diverse services.+ homepage:            https://bitbucket.org/dpwiz/haskell-soap license:             MIT license-file:        LICENSE@@ -19,13 +54,24 @@   hs-source-dirs:    src/   ghc-options: -Wall -O2   exposed-modules:-    Web.SOAP.Service-    Web.SOAP.Types---  other-modules:+    Network.SOAP+    Network.SOAP.Transport+    Network.SOAP.Transport.HTTP.Conduit+    Network.SOAP.Transport.Mock+    Network.SOAP.Parsing.Cursor   build-depends:     base ==4.*,     http-conduit, resourcet, tls-extra,-    xml-conduit,-    iconv,-    text,+    xml-conduit-writer, xml-conduit, xml-types, conduit, data-default,+    text, bytestring, iconv,+    unordered-containers, mtl++test-suite tests+  type: exitcode-stdio-1.0+  main-is: Main.hs+  hs-source-dirs: test/+  build-depends:+    base, soap, hspec, HUnit,+    xml-conduit, xml-conduit-writer,+    text, bytestring,     unordered-containers
+ src/Network/SOAP.hs view
@@ -0,0 +1,55 @@+-- | A heart of the package, 'invokeWS' assembles and executes requests.++{-# LANGUAGE OverloadedStrings, Rank2Types, FlexibleContexts #-}+module Network.SOAP+    (+      invokeWS, Transport+    , ResponseParser(..)+    , Parser+    ) where++import Network.SOAP.Transport (Transport)++import Data.Conduit++import qualified Data.ByteString.Lazy.Char8 as LBS++import           Data.Default (def)+import qualified Text.XML as XML+import           Text.XML.Cursor as XML+import qualified Text.XML.Stream.Parse as XSP+import           Data.XML.Types (Event)+import           Text.XML.Writer (ToXML, soap)++-- | Different parsing modes available to extract reply contents.+data ResponseParser a = StreamParser (Parser a)         -- ^ Streaming parser from Text.XML.Stream.Parse+                      | CursorParser (XML.Cursor -> a)  -- ^ XPath-like parser from Text.XML.Cursor+                      | RawParser (LBS.ByteString -> a) -- ^ Work with a raw bytestring.++-- | Stream parser from Text.XML.Stream.Parse.+type Parser a = Sink Event (ResourceT IO) a++-- | Prepare data, assemble request and apply a parser to a response.+invokeWS :: (ToXML h, ToXML b)+         => Transport        -- ^ Configured transport to make requests with.+         -> String           -- ^ SOAPAction header.+         -> h                -- ^ SOAP Header element. () or Nothing will result in omiting the Header node. Put a comment if you need an empty element present.+         -> b                -- ^ SOAP Body element.+         -> ResponseParser a -- ^ Parser to use on a request reply.+         -> IO a+invokeWS transport soapAction header body parser = do+    lbs <- transport soapAction $! soap header body+    case parser of+        StreamParser sink -> runResourceT $ XSP.parseLBS def lbs $$ unwrapEnvelopeSink sink+        CursorParser func -> return . func . unwrapEnvelopeCursor . XML.fromDocument $ XML.parseLBS_ def lbs+        RawParser func    -> return . func $ lbs++unwrapEnvelopeSink :: Parser a -> Parser a+unwrapEnvelopeSink sink = XSP.force "No SOAP Envelope" $ XSP.tagNoAttr "Envelope"+                        $ XSP.force "No SOAP Body" $ XSP.tagNoAttr "Body"+                        $ sink++unwrapEnvelopeCursor :: Cursor -> Cursor+unwrapEnvelopeCursor c = forceCur $ c $| laxElement "Envelope" &/ laxElement "Body"+    where forceCur [] = error "No SOAP Body"+          forceCur (x:_) = x
+ src/Network/SOAP/Parsing/Cursor.hs view
@@ -0,0 +1,52 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}++-- | Some helpers to parse documents with Text.XML.Cursor.++module Network.SOAP.Parsing.Cursor+    (+      -- * Extract single element+      readT, readC+      -- * Extract from multiple elements+    , readDict, Dict+    ) where++import Text.XML+import Text.XML.Cursor++import           Data.Text (Text)+import qualified Data.Text as T+import qualified Data.HashMap.Strict as HM+import           Data.Maybe (mapMaybe)++-- ** Single-element extraction.++-- | Grab node content by element name.+--+-- > pair cur = (readT "fst" cur, readT "snd" cur)+readT :: Text -> Cursor -> Text+readT n c = T.concat $ c $/ laxElement n &/ content+{-# INLINE readT #-}++-- | Extract a read-able type from a content of a node with given name.+--+-- > age = readC "age" :: Cursor -> Integer+readC :: (Read a) => Text -> Cursor -> a+readC n c = read . T.unpack $ readT n c+{-# INLINE readC #-}++-- ** Multi-element extraction.++-- | Very generic type to catch server reply when you don't care about types.+type Dict = HM.HashMap Text Text++-- | Apply an axis and extract a key-value from child elements.+--+-- > invokeWS … (CursorParser . readDict $ laxElement "WebScaleResponse" &/ laxElement "BigDataResult")+readDict :: Axis -> Cursor -> Dict+readDict a c = extract . head $ c $/ a+    where+        extract cur = HM.fromList . mapMaybe dict . map node $ cur $| child++        dict (NodeElement (Element (Name n _ _) _ [NodeContent c])) = Just (n, c)+        dict (NodeElement (Element (Name n _ _) _ []))              = Just (n, T.empty)+        dict _                                                      = Nothing
+ src/Network/SOAP/Transport.hs view
@@ -0,0 +1,17 @@+-- | This package comes with a single transport, but the your vendor's+-- SOAP implementation can behave very differently, so invokeWS can be+-- rigged to use anything that follows a simple interface.++module Network.SOAP.Transport+    (+      Transport+    ) where++import Text.XML (Document)+import Data.ByteString.Lazy.Char8 (ByteString)++-- | Common transport type. Get a request and deliver it to an endpoint+--   specified during initialization.+type Transport = String   -- ^ SOAPAction header+              -> Document -- ^ XML document with a SOAP request+              -> IO ByteString
+ src/Network/SOAP/Transport/HTTP/Conduit.hs view
@@ -0,0 +1,89 @@+{-# LANGUAGE OverloadedStrings #-}++-- | A feature-rich http-conduit based transport allowing to deal with+--   HTTPS, authentication and other stuff using request and body processors.++module Network.SOAP.Transport.HTTP.Conduit+    (+      -- * Initialization+      initTransport, initTransport_+    , EndpointURL+      -- * Making a request+    , RequestP, clientCert+      -- * Processing a response+    , BodyP, iconv+      -- * Raw transport function+    , runQuery+    ) where++import Text.XML+import Network.HTTP.Conduit+import Control.Monad.Trans.Resource+import           Codec.Text.IConv (EncodingName, convertFuzzy, Fuzzy(Transliterate))+import qualified Network.TLS.Extra as TLS++import qualified Data.ByteString.Char8 as BS+import           Data.ByteString.Lazy.Char8 (ByteString)+++import Network.SOAP.Transport++-- | Update request record after defaults and method-specific fields are set.+type RequestP = Request (ResourceT IO) -> Request (ResourceT IO)++-- | Process response body to make it a nice UTF8-encoded XML document.+type BodyP = ByteString -> ByteString++-- | Web service URL. Configured at initialization, but you can tweak it+--   dynamically with a request processor.+type EndpointURL = String++-- | Create a http-conduit transport. Use identity transformers if you+--   don't need any special treatment.+initTransport :: EndpointURL+              -> RequestP+              -> BodyP+              -> IO Transport+initTransport url updateReq updateBody = do+    manager <- newManager def+    return $! runQuery manager url updateReq updateBody++-- | Create a transport without any request and body processing.+initTransport_ :: EndpointURL -> IO Transport+initTransport_ url = initTransport url id id++-- | Render document, submit it as a POST request and retrieve a body.+runQuery :: Manager+         -> EndpointURL+         -> RequestP+         -> BodyP+         -> Transport+runQuery manager url updateReq updateBody soapAction doc = do+    let body = renderLBS def $! doc++    request <- parseUrl url+    let request' = request { method          = "POST"+                           , responseTimeout = Just 15000000+                           , requestBody     = RequestBodyLBS body+                           , requestHeaders  = [ ("Content-Type", "text/xml; charset=utf-8")+                                               , ("SOAPAction", BS.pack soapAction)+                                               ]+                           }+    res <- runResourceT $ httpLbs (updateReq request') manager+    return . updateBody . responseBody $ res++-- * Some common processors.++-- | Create an IConv-based processor.+iconv :: EncodingName -> BodyP+iconv src = convertFuzzy Transliterate src "UTF-8"++-- | Load certificate, key and make a request processor setting them.+clientCert :: FilePath -- ^ Path to a certificate.+           -> FilePath -- ^ Path to a private key.+           -> IO RequestP+clientCert certPath keyPath = do+    cert <- TLS.fileReadCertificate certPath+    pkey <- TLS.fileReadPrivateKey keyPath++    return $ \req -> req { clientCertificates = [(cert, Just pkey)] }
+ src/Network/SOAP/Transport/Mock.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE OverloadedStrings #-}+-- | Debug transport to train your parsers without bugging real services.++module Network.SOAP.Transport.Mock+    (+      initTransport+    , Handler, Handlers+    , handler+    , runQuery+    ) where++import Network.SOAP.Transport++import Text.XML+import Text.XML.Writer+import Data.ByteString.Lazy.Char8 as LBS++type Handler = Document -> IO LBS.ByteString+type Handlers = [(String, Handler)]++-- | Wrap a collection of handlers into a transport.+initTransport :: Handlers -> IO Transport+initTransport handlers = return $ runQuery handlers++-- | Choose and apply a handler.+runQuery :: [(String, Handler)] -> Transport+runQuery handlers soapAction doc = do+    case lookup soapAction handlers of+        Nothing -> error $ "No handler for action " ++ soapAction+        Just handler -> handler doc++-- | Process a Document and wrap result in a SOAP Envelope.+handler :: (ToXML a) => (Document -> IO a) -> Handler+handler h doc = do+    result <- h doc+    return . renderLBS def+           . document "Envelope"+           . element "Body"+           . toXML+           $ result
− src/Web/SOAP/Service.hs
@@ -1,114 +0,0 @@-{-# LANGUAGE OverloadedStrings, RecordWildCards, Rank2Types, KindSignatures #-}-module Web.SOAP.Service-    ( SOAPSettings(..)-    , invokeWS-    , invokeWS'-    , flowNS-    ) where--import           Text.XML-import           Text.XML.Cursor-import           Network.HTTP.Conduit-import           Control.Monad.Trans.Resource (ResourceT)--import           Data.Text (Text)-import qualified Data.Text as T-import qualified Data.Text.Lazy as TL-import qualified Data.Text.Lazy.IO as TL-import qualified Data.Text.Encoding as TE-import qualified Data.Text.Lazy.Encoding as TLE-import qualified Codec.Text.IConv as IC-import           Data.Monoid ((<>))--import Web.SOAP.Types---- | SOAP service parameters-data SOAPSettings = SOAPSettings {-    soapURL :: String,-    soapNamespace :: Text,-    soapCodepage :: IC.EncodingName-} deriving (Read, Show)---- | Query a SOAP service.-invokeWS :: (ToNodes h, ToNodes i, FromCursor o)-         => SOAPSettings  -- ^ web service configuration-         -> Text          -- ^ SOAPAction header-         -> h             -- ^ request headers-         -> i             -- ^ request body-         -> IO o          -- ^ response-invokeWS = invokeWS' id---- | Query a SOAP service with a customized 'Request'.-invokeWS' :: (ToNodes h, ToNodes i, FromCursor o)-          => (Request (ResourceT IO) -> Request (ResourceT IO)) -- ^ request transformation to apply before sending-          -> SOAPSettings  -- ^ web service configuration-          -> Text          -- ^ SOAPAction header-          -> h             -- ^ request headers-          -> i             -- ^ request body-          -> IO o          -- ^ response-invokeWS' reqProc SOAPSettings{..} methodHeader h b = do-    let headerNodes = toNodes h-    let bodyNodes = toNodes b--    let doc = document $! envelope headerNodes bodyNodes-    let body = renderLBS def $! doc--    putStrLn "Request:"-    TL.putStrLn . renderText def { rsPretty = True } $! doc--    request <- parseUrl soapURL-    let request' = request { method          = "POST"-                           , responseTimeout = Just 15000000-                           , requestBody     = RequestBodyLBS body-                           , requestHeaders  = [ ("Content-Type", "text/xml; charset=utf-8")-                                               , ("SOAPAction", TE.encodeUtf8 methodHeader)-                                               ]-                           }-    res <- withManager $ httpLbs (reqProc request')--    let resBody = IC.convertFuzzy IC.Transliterate soapCodepage "utf-8" $ responseBody res--    case parseLBS def resBody of-        Left err -> do-            putStrLn $ "Error: " <> show err-            putStrLn "Raw response:"-            print $ responseBody res-            error $ show err--        Right replyDoc -> do-            putStrLn "Response:"-            TL.putStrLn . renderText def { rsPretty = True } $ replyDoc-            let reply = fromDocument replyDoc-            print reply-            return $! fromCursor reply---- ** Request components--document :: Element -> Document-document r = Document (Prologue [] Nothing []) r []--envelope :: [Node] -> [Node] -> Element-envelope header body =-    Element-        (soapenv "Envelope")-        def-        ( if null header-            then [ NodeElement $! Element (soapenv "Body") def body ]-            else [ NodeElement $! Element (soapenv "Header") def header-                 , NodeElement $! Element (soapenv "Body") def body-                 ]-        )-    where-        soapenv ln = Name ln (Just "http://schemas.xmlsoap.org/soap/envelope/") (Just "soapenv")---- | Little helper to apply default service namespace to body nodes and their descendants.---   This removes the necessity to litter your code with {<http://example.com/nonexistant/service/url.spamx>} in element names.------ > foo = "test" .=: [ "shmest" .= "spam"--- >                  , "spanish" .= "inquisition"--- >                  ]--- > foo' = map (flowNS $ Just "whatever") foo-flowNS :: Maybe Text -> Node -> Node-flowNS ns (NodeElement (Element (Name name Nothing prefix) as cs)) = NodeElement $ Element (Name name ns prefix) as $ map (flowNS ns) cs  -- update element ns and continue-flowNS _ (NodeElement (Element name@(Name _ ns' _) as cs))         = NodeElement $ Element name as                  $ map (flowNS ns') cs -- switch to new namespace and continue-flowNS _ node = node -- ignore non-elements
− src/Web/SOAP/Types.hs
@@ -1,92 +0,0 @@-{-# LANGUAGE FlexibleInstances, OverlappingInstances #-}--module Web.SOAP.Types-    ( ToNodes(..), (.=:), (.=)-    , FromCursor(..), readT, readC, Dict, asDict-    ) where--import           Data.Text (Text)-import qualified Data.Text as T-import           Text.XML-import           Text.XML.Cursor-import qualified Data.HashMap.Strict as HM-import           Data.Maybe---- * Prepare data---- ** Construct elements---- | Convert data to a Node list.---   One of the functions should be provided with others building up on it.---   ---   Only use 'toNodes' to obtain a Node list.-class ToNodes a where-    toElement :: a -> Element-    toElement = undefined--    toElements :: a -> [Element]-    toElements x = [toElement x]--    toNodes :: a -> [Node]-    toNodes = map NodeElement . toElements--instance ToNodes () where-    toNodes () = []--instance ToNodes Text where-    toNodes x = [NodeContent x]--instance ToNodes [Node] where-    toNodes = id--instance (ToNodes a) => ToNodes (Name, a) where-    toElement (n, b) = Element n def (toNodes b)--instance (ToNodes a) => ToNodes [a] where-    toNodes = concat . map toNodes--(.=:) :: Name -> [Node] -> [Node]-n .=: ns = toNodes (n, ns)--(.=) :: Name -> Text -> Node-n .= t = head $ toNodes (n, toNodes t)---- * Extract data from XML cursor--class FromCursor a where-    fromCursor :: Cursor -> a---- ** Single-element extraction.--readT :: Text -> Cursor -> Text-readT n c = T.concat $ c $/ laxElement n &/ content-{-# INLINE readT #-}--readC :: (Read a) => Text -> Cursor -> a-readC n c = read . T.unpack $ readT n c-{-# INLINE readC #-}----readContent :: (Read a) => Cursor -> a---readContent = read . T.unpack . T.concat . content---- ** Multi-element extraction.--type Dict = HM.HashMap Text Text--asDict :: Axis -> Cursor -> Dict-asDict a c = fromCursor . head $ c $// a--instance FromCursor Dict where-    fromCursor cur = HM.fromList . mapMaybe dict . map node $ cur $| child--dict :: Node -> Maybe (Text, Text)-dict (NodeElement (Element (Name n _ _) _ [NodeContent c])) = Just (n, c)-dict (NodeElement (Element (Name n _ _) _ []))              = Just (n, T.empty)-dict _                                                      = Nothing-{-# INLINE dict #-}--instance (FromCursor a, FromCursor b) => FromCursor (a, b) where-    fromCursor c = (fromCursor c, fromCursor c)--instance (FromCursor a, FromCursor b, FromCursor c) => FromCursor (a, b, c) where-    fromCursor c = (fromCursor c, fromCursor c, fromCursor c)
+ test/Main.hs view
@@ -0,0 +1,78 @@+{-# LANGUAGE OverloadedStrings #-}+import Network.SOAP+import Network.SOAP.Parsing.Cursor+import qualified Network.SOAP.Transport.Mock as Mock+import qualified Network.SOAP.Transport.HTTP.Conduit as HTTP+import Text.XML+import Text.XML.Writer+import Text.XML.Cursor hiding (element)+import Text.XML.Stream.Parse as Parse++import Data.Text (Text)+import qualified Data.HashMap.Strict as HM+import qualified Data.ByteString.Lazy.Char8 as LBS++import Test.Hspec++main = hspec $ do+    describe "Transport.Mock" $ do+        it "dispatches requests" $ do+            t <- Mock.initTransport [ ("ping", const $ return "pong") ]+            result <- t "ping" (document "request" empty)+            result `shouldBe` "pong"++        it "generates a soap response" $ do+            t <- Mock.initTransport [ ("foo", Mock.handler $ \_ -> return ())]+            result <- t "foo" (document "request" empty)+            result `shouldBe` "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Envelope><Body/></Envelope>"++    context "SOAP" $ do+        it "Smoke-test with RawParser" $ do+            t <- Mock.initTransport [ ("ping", const $ return "pong") ]+            result <- invokeWS t "ping" () () (RawParser id)+            result `shouldBe` "pong"++        describe "CursorParser" $ do+            let salad cur = head $ cur $/ laxElement "salad"++            let check parser = do+                t <- Mock.initTransport [ ("spam", saladHandler )]+                invokeWS t "spam" () () parser++            it "reads content" $ do+                result <- check $ CursorParser (readT "bacon" . salad)+                result `shouldBe` "many"++            it "reads and converts" $ do+                result <- check $ CursorParser (readC "eggs" . salad)+                result `shouldBe` (2 :: Integer)++            it "reads dict" $ do+                result <- check $ CursorParser (readDict $ laxElement "salad" )+                result `shouldBe` HM.fromList [ ("bacon","many")+                                              , ("sausage","some")+                                              , ("eggs","2")+                                              ]+        describe "StreamParser" $ do+            it "extracts stuff" $ do+                let recipeParser = do+                    ings <- Parse.force "no salad" $ Parse.tagNoAttr "salad" $ Parse.many $ Parse.tag Just return $ \name -> do+                        quantity <- Parse.content+                        return $ RecipeEntry (nameLocalName name) quantity+                    return $ Recipe ings++                t <- Mock.initTransport [ ("spam", saladHandler) ]+                result <- invokeWS t "spam" () () $ StreamParser recipeParser+                result `shouldBe` Recipe [ RecipeEntry "sausage" "some"+                                         ,  RecipeEntry "bacon" "many"+                                         ,  RecipeEntry "eggs" "2"+                                         ]++saladHandler = Mock.handler $ \_ -> do+    return . element "salad" $ do+        element "sausage" ("some" :: Text)+        element "bacon" ("many" :: Text)+        element "eggs" (2 :: Integer)++data RecipeEntry = RecipeEntry Text Text deriving (Eq, Show)+data Recipe = Recipe [RecipeEntry] deriving (Eq, Show)