httpspec 0.1 → 0.3.0.1
raw patch · 7 files changed
+287/−365 lines, 7 filesdep +MissingHdep +hxthelperdep +safedep −Safedep −tagsoupdep ~hxtnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: MissingH, hxthelper, safe
Dependencies removed: Safe, tagsoup
Dependency ranges changed: hxt
API changes (from Hackage documentation)
- Data.HttpSpec: webErr_actual :: WebErr -> String
- Data.HttpSpec: webErr_expected :: WebErr -> String
- Data.HttpSpec.MiscHelper: eitherToM :: (Show a, Monad m) => Either a b -> m b
- Data.HttpSpec.MiscHelper: errorToDefault :: (MonadError e m) => a -> m a -> m a
- Data.HttpSpec.MiscHelper: errorToMaybe :: (MonadError e m) => m a -> m (Maybe a)
- Data.HttpSpec.MiscHelper: maybeToM :: (Monad m) => String -> Maybe a -> m a
- Data.HttpSpec.MiscHelper: safeMain :: IO () -> IO ()
- Data.HttpSpec.XmlHelper: _ISO8859_10_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_11_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_13_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_14_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_15_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_16_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_1_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_2_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_3_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_4_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_5_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_6_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_7_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_8_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISO8859_9_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _ISOLATIN1_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UCS2_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UNICODE_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _USASCII_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UTF16BE_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UTF16LE_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UTF16_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: _UTF8_ :: XmlEncoding
- Data.HttpSpec.XmlHelper: data PUCase a
- Data.HttpSpec.XmlHelper: data XmlEncoding
- Data.HttpSpec.XmlHelper: fromPair :: (a -> b -> c) -> (a, b) -> c
- Data.HttpSpec.XmlHelper: fromQuadruple :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e
- Data.HttpSpec.XmlHelper: fromQuintuple :: (a -> b -> c -> d -> e -> f) -> (a, b, c, d, e) -> f
- Data.HttpSpec.XmlHelper: fromTriple :: (a -> b -> c -> d) -> (a, b, c) -> d
- Data.HttpSpec.XmlHelper: instance Eq XmlEncoding
- Data.HttpSpec.XmlHelper: instance Show XmlEncoding
- Data.HttpSpec.XmlHelper: parseXml :: (Monad m) => Bool -> Bool -> ByteString -> m XmlTree
- Data.HttpSpec.XmlHelper: pickle :: PU a -> a -> ByteString
- Data.HttpSpec.XmlHelper: pickleStr :: PU a -> a -> String
- Data.HttpSpec.XmlHelper: pickleWithEnc :: XmlEncoding -> PU a -> a -> ByteString
- Data.HttpSpec.XmlHelper: serializeXml :: XmlEncoding -> XmlTree -> ByteString
- Data.HttpSpec.XmlHelper: unpickle :: (Monad m) => PU a -> ByteString -> m a
- Data.HttpSpec.XmlHelper: unpickleDocM :: (Monad m) => PU a -> XmlTree -> m a
- Data.HttpSpec.XmlHelper: unpickleStr :: (Monad m) => PU a -> [Char] -> m a
- Data.HttpSpec.XmlHelper: xmlEncodingFromString :: String -> Either String XmlEncoding
- Data.HttpSpec.XmlHelper: xmlEncodingToString :: XmlEncoding -> String
- Data.HttpSpec.XmlHelper: xpCase :: (a -> b, b -> a) -> PU a -> PUCase b
- Data.HttpSpec.XmlHelper: xpCaseConst :: a -> PU () -> PUCase a
- Data.HttpSpec.XmlHelper: xpMany :: String -> PU a -> PU [a]
- Data.HttpSpec.XmlHelper: xpOne :: PU a -> PU a
- Data.HttpSpec.XmlHelper: xpSwitch :: (Show a) => [PUCase a] -> PU a
- Data.HttpSpec.XmlHelper: xpTagSwitch :: (Eq t) => (a -> t) -> [PUCase a] -> PU a
- Data.HttpSpec.XmlHelper: xpTextFixed :: String -> PU ()
+ Data.HttpSpec: WebCommIn :: WebIn -> WebComm
+ Data.HttpSpec: WebCommOut :: WebOut -> WebComm
+ Data.HttpSpec: WebExc :: Maybe WebComm -> WebErr -> WebExc
+ Data.HttpSpec: WebIn :: Maybe ReqIn -> Maybe ResOut -> WebIn
+ Data.HttpSpec: WebOut :: Maybe ReqOut -> Maybe ResIn -> WebOut
+ Data.HttpSpec: data WebComm
+ Data.HttpSpec: data WebExc
+ Data.HttpSpec: data WebIn
+ Data.HttpSpec: data WebOut
+ Data.HttpSpec: instance Error WebExc
+ Data.HttpSpec: instance Pretty WebErr
+ Data.HttpSpec: instance Pretty WebExc
+ Data.HttpSpec: instance Show WebComm
+ Data.HttpSpec: instance Show WebExc
+ Data.HttpSpec: instance Show WebIn
+ Data.HttpSpec: instance Show WebOut
+ Data.HttpSpec: rsPathFixed :: String -> ReqSpec a -> ReqSpec a
+ Data.HttpSpec: rsValidXml :: (Error e) => XmlEncoding -> XmlValidator -> PU a -> Spec e i o ByteString -> Spec e i o a
+ Data.HttpSpec: rsXmlBody :: (IsHttp i, IsHttp o) => PU a -> HttpSpec i o a
+ Data.HttpSpec: webExcSetReqIn :: ReqIn -> WebExc -> WebExc
+ Data.HttpSpec: webExcSetReqOut :: ReqOut -> WebExc -> WebExc
+ Data.HttpSpec: webExcSetResIn :: ResIn -> WebExc -> WebExc
+ Data.HttpSpec: webExcSetResOut :: ResOut -> WebExc -> WebExc
+ Data.HttpSpec: webExc_comm :: WebExc -> Maybe WebComm
+ Data.HttpSpec: webExc_err :: WebExc -> WebErr
+ Data.HttpSpec: webIn_req :: WebIn -> Maybe ReqIn
+ Data.HttpSpec: webIn_res :: WebIn -> Maybe ResOut
+ Data.HttpSpec: webOut_req :: WebOut -> Maybe ReqOut
+ Data.HttpSpec: webOut_res :: WebOut -> Maybe ResIn
+ Data.HttpSpec.EncodingHelper: encodingFromContentType :: String -> Maybe DynEncoding
+ Data.HttpSpec.EncodingHelper: xmlEncoding :: (Monad m) => ByteString -> m DynEncoding
+ Data.HttpSpec.HttpTypes: reqSetUrlPath :: (IsReq a) => HttpPath -> a -> a
+ Data.HttpSpec.Pretty: pprString :: (Pretty a) => a -> String
- Data.HttpSpec: rsPath :: String -> ReqSpec a -> ReqSpec a
+ Data.HttpSpec: rsPath :: ReqSpec String
- Data.HttpSpec.HttpTypes: type HttpBody = String
+ Data.HttpSpec.HttpTypes: type HttpBody = ByteString
Files
- httpspec.cabal +10/−7
- src/Data/HttpSpec.hs +167/−54
- src/Data/HttpSpec/EncodingHelper.hs +10/−1
- src/Data/HttpSpec/HttpTypes.hs +96/−21
- src/Data/HttpSpec/MiscHelper.hs +0/−40
- src/Data/HttpSpec/Pretty.hs +4/−2
- src/Data/HttpSpec/XmlHelper.hs +0/−240
httpspec.cabal view
@@ -1,9 +1,9 @@ Name: httpspec-Version: 0.1+Version: 0.3.0.1 Synopsis: Specification of HTTP request/response generators and parsers License: LGPL License-file: LICENSE-Author: David Leuschner+Author: David Leuschner, Stefan Wehr Maintainer: David Leuschner <leuschner@openfactis.org> Category: Data, Web Description:@@ -55,10 +55,13 @@ Library Hs-Source-Dirs: src GHC-Options: -Wall+ -fno-warn-name-shadowing+ -fno-warn-orphans+ -fno-warn-overlapping-patterns Build-Depends: base >= 4 && < 5, bytestring, bidispec, mtl,- tagsoup == 0.6, hxt > 8.3 && < 8.4, pretty,- Safe, cgi, network, HTTP, filepath, containers,- encoding >= 0.6+ hxt > 8.3, pretty, MissingH,+ safe, cgi, network, HTTP, filepath, containers,+ encoding >= 0.6, hxthelper Exposed-Modules: Data.HttpSpec,Data.HttpSpec.HttpTypes- Data.HttpSpec.Pretty, Data.HttpSpec.MiscHelper- Data.HttpSpec.XmlHelper, Data.HttpSpec.EncodingHelper+ Data.HttpSpec.Pretty+ Data.HttpSpec.EncodingHelper
src/Data/HttpSpec.hs view
@@ -1,21 +1,26 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} module Data.HttpSpec- (ReqSpec, ResSpec, HttpSpec, WebErr(..)+ (ReqSpec, ResSpec, HttpSpec+ ,WebComm(..), WebIn(..), WebOut(..), WebExc(..), WebErr(..) ,HasReqSpec(..), HasResSpec(..), TextEncoding ,rsHeader, rsHeaderFixed, rsParam, rsMeth, rsStatus- ,rsXmlString, rsXml, rsPath, rsWithBody, rsBody, rsContentType+ ,rsXmlString, rsXml, rsValidXml+ ,rsPath, rsPathFixed, rsWithBody, rsBody, rsContentType ,rsPathSegment, rsXmlEncoding, rsTextEncoding, rsEncodingFixed- ,genReqOut, genResOut, parseReqIn, parseResIn+ ,genReqOut, genResOut, parseReqIn, parseResIn, rsXmlBody+ ,webExcSetReqIn, webExcSetResIn, webExcSetReqOut, webExcSetResOut ) where+ ---------------------------------------- -- STDLIB -----------------------------------------import Control.Monad (liftM,when,unless)-import Control.Monad.Reader (ask,asks,local)+import Prelude hiding (exp)++import Control.Monad (liftM)+import Control.Monad.Reader (asks,local) import Control.Monad.Error (MonadError(..), Error(..))-import Data.Maybe (fromMaybe) import Data.List (isPrefixOf) ----------------------------------------@@ -23,10 +28,8 @@ ---------------------------------------- import qualified Network.HTTP as Http import qualified Network.URI as Uri-import qualified Network.CGI as Cgi import qualified Data.ByteString.Lazy as BSL-import qualified Data.ByteString.Lazy.Char8 as BSLChar import Data.Encoding (Encoding, DynEncoding ,encodeLazyByteString, decodeLazyByteStringExplicit@@ -39,18 +42,18 @@ ---------------------------------------- -- LOCAL -----------------------------------------import Data.HttpSpec.MiscHelper (eitherToM) import Data.HttpSpec.EncodingHelper (encodingName) import Data.HttpSpec.HttpTypes- (HttpHeaderName,HttpHeaderValue,HttpHeader,HttpMethod,HttpBody+ (HttpHeaderName,HttpHeaderValue,HttpHeader,HttpMethod ,HttpUrl, HttpParamName, HttpParamValue ,HttpData(..),ReqIn(..),ReqOut(..),ResIn(..),ResOut(..) ,IsHttp(..), IsReq(..), IsRes(..) ,urlParams, urlMatchPrefix, urlSplit)-import Data.HttpSpec.XmlHelper- (XmlEncoding, pickleStr, pickleWithEnc, unpickle, unpickleStr- ,xmlEncodingFromString, xmlEncodingToString)-+import Text.XML.HXT.Helper+ (XmlValidator,XmlEncoding, pickleStr, pickleWithEnc, unpickle, unpickleStr+ ,validateAndUnpickle+ ,xmlEncodingFromString, xmlEncodingToString, _UTF8_)+import Data.HttpSpec.Pretty (Pretty(..)) -- ---------------------------------------------------------------------------- -- Spec types for request and response@@ -64,15 +67,39 @@ type TextEncoding = DynEncoding +data WebComm+ = WebCommIn WebIn+ | WebCommOut WebOut+ deriving (Show)++data WebIn+ = WebIn+ { webIn_req :: Maybe ReqIn+ , webIn_res :: Maybe ResOut+ } deriving (Show)++data WebOut+ = WebOut+ { webOut_req :: Maybe ReqOut+ , webOut_res :: Maybe ResIn+ } deriving (Show)+++data WebExc+ = WebExc+ { webExc_comm :: Maybe WebComm+ , webExc_err :: WebErr+ } deriving (Show)+ data WebErr = WebErrMissingParam String | WebErrMissingHeader HttpHeaderName | WebErrInvalidHeaderValue HttpHeaderName HttpHeaderValue String | WebErrInvalidMethod HttpMethod String | WebErrInvalidStatus Int String- | WebErrInvalidUrl { webErr_expected :: String, webErr_actual :: String }+ | WebErrInvalidUrl {- expected: -} String {- actual: -} String | WebErrMissingContentType- | WebErrUnexpectedContentType String String+ | WebErrUnexpectedContentType {- expected: -} String {- actual: -} String | WebErrEmptyContent | WebErrNoMatch ReqIn | WebErrNotImplemented String@@ -83,43 +110,74 @@ noMsg = WebErrCustomMsg "HttpSpec: unknown error." strMsg = WebErrCustomMsg -type ReqErr = WebErr-type ResErr = WebErr-type HttpErr = WebErr+instance Error WebExc where+ noMsg = mkErr (WebErrCustomMsg "HttpSpec: unknown error.")+ strMsg s = mkErr (WebErrCustomMsg s) -type ReqSpecGen a = SpecGen ReqOut a-type ReqSpecParser a = SpecParser ReqIn ReqErr a+instance Pretty WebExc where+ ppr exc = ppr (webExc_err exc) -type ResSpecGen a = SpecGen ResOut a-type ResSpecParser a = SpecParser ResIn ResErr a+instance Pretty WebErr where+ pprString err =+ case err of+ WebErrMissingParam p ->+ "Missing parameter `" ++ p ++ "'"+ WebErrMissingHeader h ->+ "Missing parameter `" ++ show h ++ "'"+ WebErrInvalidHeaderValue n v s ->+ "Invalid value " ++ show v ++ " for header `" ++ show n +++ "': " ++ s+ WebErrInvalidMethod method s ->+ "Invalid HTTP method " ++ show method ++ ": " ++ s+ WebErrInvalidStatus stat s ->+ "Invalid HTTP status " ++ show stat ++ ": " ++ s+ WebErrInvalidUrl exp act ->+ "Invalid URL, expected " ++ exp ++ ", given " ++ act+ WebErrMissingContentType ->+ "Content type missing"+ WebErrUnexpectedContentType exp act ->+ "Unexpected content type, expected " ++ exp ++ ", given " ++ act+ WebErrEmptyContent ->+ "No content given"+ WebErrNoMatch req ->+ "No matching URL for " ++ show (reqIn_fullUrl req) +++ ", method " ++ show (reqIn_method req)+ WebErrNotImplemented s ->+ "Functionality not yet implemented: " ++ s+ WebErrCustomMsg s ->+ s++type ReqErr = WebExc+type ResErr = WebExc+type HttpErr = WebExc+ type HttpSpecParser i a = SpecParser i HttpErr a type ReqSpec = Spec ReqErr ReqIn ReqOut type ResSpec = Spec ResErr ResIn ResOut type HttpSpec = Spec HttpErr -mkReqSpec :: ReqSpecParser a -> ReqSpecGen a -> ReqSpec a-mkReqSpec = mkSpec+-- ----------------------------------------------------------------------------+-- helper functions+-- ----------------------------------------------------------------------------+spGetHeader :: IsHttp h => HttpHeaderName -> HttpSpecParser h HttpHeaderValue+spGetHeader name = asks (httpGetHeader name)+ >>= spFromMaybe (mkErr $ WebErrMissingHeader name) -mkResSpec :: ResSpecParser a -> ResSpecGen a -> ResSpec a-mkResSpec = mkSpec+mkErr :: WebErr -> WebExc+mkErr err = WebExc Nothing err -- ---------------------------------------------------------------------------- -- HttpSpec combinators -- ---------------------------------------------------------------------------- -spGetHeader :: IsHttp h => HttpHeaderName -> HttpSpecParser h HttpHeaderValue-spGetHeader name = asks (httpGetHeader name)- >>= spFromMaybe (WebErrMissingHeader name)- rsWithBody :: (IsHttp i, IsHttp o) => (HttpSpec i o BSL.ByteString -> HttpSpec i o a) -> HttpSpec i o a rsWithBody f = rsWith f rsBody rsBody :: (IsHttp i, IsHttp o) => HttpSpec i o BSL.ByteString-rsBody = rsWrap (BSLChar.pack, BSLChar.unpack) $- rsGetSet httpBody (flip httpSetBody)+rsBody = rsGetSet httpBody (flip httpSetBody) rsHeader :: (IsHttp i, IsHttp o) => HttpHeaderName@@ -132,7 +190,7 @@ -> HttpSpec i o a rsHeaderFixed (n,v) = rsCheckSet check (httpSetHeader n v) where check = spGetHeader n >>= spCheck (==v) err- err v' = WebErrInvalidHeaderValue n v' ("Expected `"++v++"'.")+ err v' = mkErr $ WebErrInvalidHeaderValue n v' ("Expected `"++v++"'.") rsContentType :: (IsHttp i, IsHttp o) => String@@ -141,7 +199,7 @@ rsContentType v = rsCheckSet check (httpSetHeader n v) where check = spGetHeader n >>= spCheck checkfun err checkfun v' = v `isPrefixOf` v'- err v' = WebErrInvalidHeaderValue n v' ("Expected `"++v++"'.")+ err v' = mkErr $ WebErrInvalidHeaderValue n v' ("Expected `"++v++"'.") n = Http.HdrContentType -- ----------------------------------------------------------------------------@@ -149,17 +207,17 @@ -- ---------------------------------------------------------------------------- rsParam :: HttpParamName -> ReqSpec HttpParamValue-rsParam name = mkSpec rsParse rsGen+rsParam name = mkSpec rsParseDef rsGenDef where- rsGen req val = reqAddUrlParam name val req- rsParse = spGets (urlParams . reqIn_fullUrl)+ rsGenDef req val = reqAddUrlParam name val req+ rsParseDef = spGets (urlParams . reqIn_fullUrl) >>= spFromMaybe err . lookup name- err = WebErrMissingParam name+ err = mkErr $ WebErrMissingParam name rsMeth :: HttpMethod -> ReqSpec a -> ReqSpec a rsMeth meth = rsCheckSet check (reqSetMethod meth) where check = spGets reqMethod >>= spCheck (==meth) err- err m = WebErrInvalidMethod m ("Expected method `"++show meth++"'.")+ err m = mkErr $ WebErrInvalidMethod m ("Expected method `"++show meth++"'.") rsPathSegment :: ReqSpec a -> ReqSpec (String, a) rsPathSegment rs = mkSpec rsParseDef rsGenDef@@ -169,14 +227,19 @@ let msg = "URL too short." url = reqUrl req case urlSplit url of- Just (head,tail) ->- do a <- local (reqSetUrl tail) (rsParse rs)- return (head, a)- Nothing -> throwError $ WebErrInvalidUrl msg (show url)+ Just (hd,tl) ->+ do a <- local (reqSetUrl tl) (rsParse rs)+ return (hd, a)+ Nothing -> throwError $ mkErr $ WebErrInvalidUrl msg (show url) rsGenDef r (path, a) = rsGen rs (reqAppendUrlPath path r) a -rsPath :: String -> ReqSpec a -> ReqSpec a-rsPath path rs = mkSpec rsParseDef rsGenDef+rsPath :: ReqSpec String+rsPath = mkSpec rsParseDef rsGenDef+ where rsParseDef = liftM (Uri.uriPath . reqUrl) spGet+ rsGenDef r path = reqSetUrlPath path r++rsPathFixed :: String -> ReqSpec a -> ReqSpec a+rsPathFixed path rs = mkSpec rsParseDef rsGenDef where rsParseDef = do req <- spGet@@ -184,7 +247,7 @@ url = reqUrl req case urlMatchPrefix path url of Just url' -> local (reqSetUrl url') (rsParse rs)- Nothing -> throwError $ WebErrInvalidUrl msg (show url)+ Nothing -> throwError $ mkErr $ WebErrInvalidUrl msg (show url) rsGenDef r = rsGen rs (reqAppendUrlPath path r) -- ----------------------------------------------------------------------------@@ -193,7 +256,7 @@ rsStatus :: Int -> ResSpec a -> ResSpec a rsStatus c = rsCheckSet check (resSetStatus c Nothing) where check = spGets resCode >>= spCheck (==c) err- err i = WebErrInvalidStatus i ("Expected status code `"++show c++"'.")+ err i = mkErr $ WebErrInvalidStatus i ("Expected status code `"++show c++"'.") -- ---------------------------------------------------------------------------- -- other specific combinators@@ -211,6 +274,21 @@ rsXml enc xp rs = rsWrapMaybe msg (unpickle xp, pickleWithEnc enc xp) rs where msg = "Failed to unpickle XML." +rsValidXml :: Error e =>+ XmlEncoding+ -> XmlValidator+ -> PU a+ -> Spec e i o BSL.ByteString+ -> Spec e i o a+rsValidXml enc val xp rs =+ flip rsWrapEither rs ( mapLeft strMsg . validateAndUnpickle val xp+ , pickleWithEnc enc xp)+ where mapLeft f (Left a) = Left (f a)+ mapLeft _f (Right c) = Right c++rsXmlBody :: (IsHttp i, IsHttp o) => PU a -> HttpSpec i o a+rsXmlBody xp = rsWithBody (rsXml _UTF8_ xp)+ rsEncodingFixed :: (Error e, Encoding enc) => enc -> Spec e i o BSL.ByteString@@ -221,8 +299,7 @@ rsXmlEncoding :: Error e => Spec e i o String -> Spec e i o XmlEncoding rsXmlEncoding = rsWrapEither' (decode, xmlEncodingToString)- where msg = "rsXmlEncoding: unknown encoding"- decode = xmlEncodingFromString+ where decode = xmlEncodingFromString rsTextEncoding :: Error e => Spec e i o String -> Spec e i o TextEncoding rsTextEncoding = rsWrapMaybe msg (encodingFromStringExplicit, encodingName)@@ -233,13 +310,49 @@ -- ---------------------------------------------------------------------------- genReqOut :: Monad m => ReqSpec a -> HttpUrl -> a -> m ReqOut-genReqOut rs base = genBySpec rs (ReqOut base Http.GET (HttpData [] ""))+genReqOut rs base = genBySpec rs (ReqOut base Http.GET (HttpData [] BSL.empty)) parseReqIn :: MonadError ReqErr m => ReqSpec a -> ReqIn -> m a-parseReqIn rs reqIn = parseBySpec rs reqIn+parseReqIn rs reqIn = catchError (parseBySpec rs reqIn) handler+ where handler = throwError . webExcSetReqIn reqIn genResOut :: Monad m => ResSpec a -> a -> m ResOut-genResOut rs = genBySpec rs (ResOut 200 Nothing (HttpData [] ""))+genResOut rs = genBySpec rs (ResOut 200 Nothing (HttpData [] BSL.empty)) parseResIn :: MonadError ReqErr m => ResSpec a -> ResIn -> m a-parseResIn rs reqIn = parseBySpec rs reqIn+parseResIn rs resIn = catchError (parseBySpec rs resIn) handler+ where handler = throwError . webExcSetResIn resIn+++webExcSetReqIn :: ReqIn -> WebExc -> WebExc+webExcSetReqIn reqIn exc =+ case exc of+ WebExc (Just (WebCommIn win)) err -> WebExc (Just $ WebCommIn $ updWin win) err+ WebExc Nothing err -> WebExc (Just $ WebCommIn $ updWin $ WebIn Nothing Nothing) err+ _ -> exc+ where updWin win = win { webIn_req = Just reqIn}++webExcSetResOut :: ResOut -> WebExc -> WebExc+webExcSetResOut resOut exc =+ case exc of+ WebExc (Just (WebCommIn win)) err -> WebExc (Just $ WebCommIn $ updWin win) err+ WebExc Nothing err -> WebExc (Just $ WebCommIn $ updWin $ WebIn Nothing Nothing) err+ _ -> exc+ where updWin win = win { webIn_res = Just resOut}+++webExcSetResIn :: ResIn -> WebExc -> WebExc+webExcSetResIn resIn exc =+ case exc of+ WebExc (Just (WebCommOut wout)) err -> WebExc (Just $ WebCommOut $ updWout wout) err+ WebExc Nothing err -> WebExc (Just $ WebCommOut $ updWout $ WebOut Nothing Nothing) err+ _ -> exc+ where updWout wout = wout { webOut_res = Just resIn}++webExcSetReqOut :: ReqOut -> WebExc -> WebExc+webExcSetReqOut reqOut exc =+ case exc of+ WebExc (Just (WebCommOut wout)) err -> WebExc (Just $ WebCommOut $ updWout wout) err+ WebExc Nothing err -> WebExc (Just $ WebCommOut $ updWout $ WebOut Nothing Nothing) err+ _ -> exc+ where updWout wout = wout { webOut_req = Just reqOut}
src/Data/HttpSpec/EncodingHelper.hs view
@@ -1,5 +1,9 @@-module Data.HttpSpec.EncodingHelper (encodingName) where+module Data.HttpSpec.EncodingHelper (encodingName, xmlEncoding, encodingFromContentType) where +import Data.String.Utils (strip)+import Text.XML.HXT.Helper (xmlEncoding)+import Data.Encoding (DynEncoding, encodingFromStringExplicit)+ encodingName :: Show enc => enc -> String encodingName enc = case show enc of@@ -31,3 +35,8 @@ "CP1257" -> "WINDOWS-1257" "CP1258" -> "WINDOWS-1258" _ -> "unknown"++encodingFromContentType :: String -> Maybe DynEncoding+encodingFromContentType s =+ let enc = (drop 1 . dropWhile (/='=') . strip . drop 1 . dropWhile (/= ';')) s+ in encodingFromStringExplicit enc
src/Data/HttpSpec/HttpTypes.hs view
@@ -14,15 +14,16 @@ -- STDLIB ---------------------------------------- import Data.Char (toLower)-import Data.Maybe (fromMaybe, catMaybes)-import Data.Map (Map)+import Data.Maybe (fromMaybe) import qualified Data.Map as Map-import Data.List (isPrefixOf)+import Data.List (intersperse, unfoldr, isPrefixOf) import Control.Monad (liftM) import Control.Arrow (first) -import System.FilePath (takeDirectory, (</>))+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Lazy.Char8 as BSLChar+import Data.Encoding (decodeLazyByteStringExplicit) ---------------------------------------- -- SITE-PACKAGES@@ -31,7 +32,7 @@ import qualified Network.URI as Uri import qualified Network.CGI as Cgi -import Text.PrettyPrint.HughesPJ (Doc, vcat, ($+$), (<+>), (<>), text, colon+import Text.PrettyPrint.HughesPJ (Doc, ($+$), (<+>), (<>), text, colon ,int, empty) import Safe (headMay)@@ -40,6 +41,7 @@ -- LOCAL ---------------------------------------- import Data.HttpSpec.Pretty (Pretty(..))+import Data.HttpSpec.EncodingHelper (xmlEncoding, encodingFromContentType) -- ============================================================================ -- http types@@ -57,7 +59,7 @@ type HttpHeaderValue = String type HttpHeader = (HttpHeaderName, String) type HttpHeaders = [HttpHeader]-type HttpBody = String+type HttpBody = BSL.ByteString -- Request types type HttpMethod = Http.RequestMethod@@ -78,21 +80,32 @@ , reqIn_data :: HttpData } deriving (Show)+reqIn_body :: ReqIn -> HttpBody reqIn_body = http_body . reqIn_data++reqIn_headers :: ReqIn -> HttpHeaders reqIn_headers = http_headers . reqIn_data data ReqOut = ReqOut { reqOut_url :: HttpUrl , reqOut_method :: HttpMethod , reqOut_data :: HttpData } deriving (Show)++reqOut_body :: ReqOut -> HttpBody reqOut_body = http_body . reqOut_data++reqOut_headers :: ReqOut -> HttpHeaders reqOut_headers = http_headers . reqOut_data data ResIn = ResIn { resIn_code :: HttpCode , resIn_reason :: HttpReason , resIn_data :: HttpData } deriving (Show)++resIn_body :: ResIn -> HttpBody resIn_body = http_body . resIn_data++resIn_headers :: ResIn -> HttpHeaders resIn_headers = http_headers . resIn_data @@ -100,7 +113,11 @@ , resOut_reason :: Maybe HttpReason , resOut_data :: HttpData } deriving (Show)++resOut_body :: ResOut -> HttpBody resOut_body = http_body . resOut_data++resOut_headers :: ResOut -> HttpHeaders resOut_headers = http_headers . resOut_data @@ -114,7 +131,16 @@ mapHdr other = Right (show other) mkHeaderName :: String -> HttpHeaderName-mkHeaderName = Http.HdrCustom+mkHeaderName s'+ | s == "content-length" = Http.HdrContentLength+ | s == "content-md5" = Http.HdrContentMD5+ | s == "content-type" = Http.HdrContentType+ | s == "content-encoding" = Http.HdrContentEncoding+ | s == "content-transfer-encoding" = Http.HdrContentTransferEncoding+ | s == "transfer-encoding" = Http.HdrTransferEncoding+ | s == "user-agent" = Http.HdrUserAgent+ | otherwise = Http.HdrCustom s'+ where s = map toLower s' url :: String -> HttpUrl url s = case Uri.parseURI s of@@ -133,6 +159,9 @@ urlAppendPath :: HttpPath -> HttpUrl -> HttpUrl urlAppendPath path uri = uri { Uri.uriPath = Uri.uriPath uri ++ path } +urlSetPath :: HttpPath -> HttpUrl -> HttpUrl+urlSetPath path uri = uri { Uri.uriPath = path }+ -- | Splits off the first path component of a URL. -- @urlSplit (url "http://svr/foo/bar") == Just ("/foo", url "http://svr/bar")@ -- @urlSplit (url "http://svr/") == Just ("/", url "http://svr")@@@ -140,7 +169,7 @@ urlSplit :: Monad m => HttpUrl -> m (HttpPath, HttpUrl) urlSplit uri = liftM mapUri (uriPathSplit $ Uri.uriPath uri) where- mapUri (head, tail) = (head, uri { Uri.uriPath = tail })+ mapUri (hd, tl) = (hd, uri { Uri.uriPath = tl }) uriPathSplit "" = fail "Empty URL Path." uriPathSplit "/" = return ("/", "") uriPathSplit ('/':path) = liftM (first ('/':)) (uriPathSplit path)@@ -161,7 +190,7 @@ completeReq :: IsHttp req => req -> req completeReq r = if clen /= 0 then r2 else r where- clen = length (httpBody r)+ clen = BSL.length (httpBody r) r1 | not (httpHasHeader Http.HdrTransferEncoding r) && not (httpHasHeader Http.HdrContentLength r) = httpSetHeader Http.HdrContentLength (show clen) r@@ -188,12 +217,12 @@ httpBody :: a -> HttpBody httpBody = http_body . httpData httpGetHeader :: HttpHeaderName -> a -> Maybe HttpHeaderValue- httpGetHeader n x =- case n of+ httpGetHeader hn x =+ case hn of Http.HdrCustom mixedName -> headMay vals where vals = [v | (Http.HdrCustom n, v) <- httpHeaders x, map toLower n == name] name = map toLower mixedName- _ -> lookup n (httpHeaders x)+ _ -> lookup hn (httpHeaders x) httpHasHeader :: HttpHeaderName -> a -> Bool httpHasHeader n = (/=Nothing) . httpGetHeader n httpSetBody :: HttpBody -> a -> a@@ -220,6 +249,8 @@ reqSetUrl :: HttpUrl -> a -> a reqUrlPath :: a -> HttpPath reqUrlPath req = Uri.uriPath (reqUrl req)+ reqSetUrlPath :: HttpPath -> a -> a+ reqSetUrlPath p req = reqSetUrl (urlSetPath p (reqUrl req)) req reqAppendUrlPath :: HttpPath -> a -> a reqAppendUrlPath p req = reqSetUrl (urlAppendPath p (reqUrl req)) req reqAddUrlParam :: HttpParamName -> HttpParamValue -> a -> a@@ -261,13 +292,13 @@ reqMethod = reqIn_method reqUrl = reqIn_fullUrl reqSetMethod meth req = req { reqIn_method = meth }- reqSetUrl url req = req { reqIn_fullUrl = url }+ reqSetUrl newUrl req = req { reqIn_fullUrl = newUrl } instance IsReq ReqOut where reqMethod = reqOut_method reqUrl = reqOut_url reqSetMethod meth req = req { reqOut_method = meth }- reqSetUrl url req = req { reqOut_url = url }+ reqSetUrl newUrl req = req { reqOut_url = newUrl } pprReq :: IsReq req => req -> Doc@@ -277,17 +308,61 @@ pprRes :: IsRes res => res -> Doc pprRes res = int (resCode res) <+> text (resReason res) $+$ ppr (httpData res) +pprHttpBody :: Maybe String -> BSL.ByteString -> Doc+pprHttpBody mctype b =+ case mctype of+ Just ctype | "text/xml" `isPrefixOf` ctype || "application/xml" `isPrefixOf` ctype+ -> pprWithEncoding (xmlEncoding b)+ Just ctype | "text/" `isPrefixOf` ctype+ -> pprWithEncoding (encodingFromContentType ctype)+ Nothing | BSL.length b == 0 -> empty+ _ ->+ let hd' = BSL.take (fromIntegral maxbytes) b+ hd = BSLChar.takeWhile (<'\128') hd'+ unfold x = if BSL.length x > (fromIntegral maxlinelen)+ then Just (BSL.splitAt (fromIntegral maxlinelen) x)+ else Nothing+ in if BSL.length hd' > 20+ then (text . BSLChar.unpack . BSL.concat . intersperse binsep) (unfoldr unfold hd)+ else (text "[" <> text (show $ BSL.length b) <+> text "bytes"+ <+> text (fromMaybe "binary data" mctype) <> text "]")+ where+ pprWithEncoding Nothing =+ (text ("Could not determine encoding of body of content-type "+ ++ fromMaybe "(unknown)" mctype ++ ".")+ $+$ text "The first 64 bytes are: "+ $+$ text (show (BSL.take 64 b)))+ pprWithEncoding (Just enc) =+ case decodeLazyByteStringExplicit enc b of+ Left err -> text ("Could not decode body with " ++ show (BSL.length b) ++ " bytes " +++ " using the given encoding " ++ show enc ++ ": " ++ show err)+ Right s -> foldl ($+$) empty (map text $ "" : showlines s)+ showlines s | (showlines'' s) /= lines s = (showlines'' s) ++ ["[body shortend!]"]+ | otherwise = bodylines s+ showlines'' s = map (shorten maxlinelen) (showlines' s)+ showlines' s =+ let (shorts,longs) = (span ((<=maxtakelinelen) . length) . take 25) (bodylines s)+ in shorts ++ take 1 longs+ bodylines s = lines (take maxbytes s)+ maxbytes = 1500+ binsep = BSLChar.pack "\\\n"+ maxtakelinelen = 160+ maxlinelen = 100++shorten :: Int -> String -> String+shorten i s+ | length s <= i = s+ | otherwise = take (i-3) s ++ "..."+ instance Pretty Uri.URI where ppr = text . show instance Pretty HttpData where- ppr (HttpData hds b) = foldl ($+$) empty (map pprHd hds)- $+$ foldl ($+$) empty (map text $ "" : bodylines)- where pprHd (n,v) = text (show n) <> colon <+> text v- showlines | showlines' /= bodylines = bodylines ++ ["[...]"]- | otherwise = bodylines- showlines' = takeWhile ((<=80) . length) $ take 5 bodylines- bodylines = lines b+ ppr http@(HttpData hds b) = headers $+$ body+ where+ body = pprHttpBody (httpGetHeader Http.HdrContentType http) b+ headers = foldl ($+$) empty (map pprHd hds)+ pprHd (n,v) = text (show n) <> colon <+> text v instance Pretty ReqIn where ppr = pprReq
− src/Data/HttpSpec/MiscHelper.hs
@@ -1,40 +0,0 @@-module Data.HttpSpec.MiscHelper- (errorToMaybe, errorToDefault, maybeToM, eitherToM, safeMain)-where--------------------------------------------- STDLIB------------------------------------------import Prelude hiding (catch)--import Control.Monad (liftM)-import Control.Monad.Error (MonadError, catchError)-import Control.Exception (catch,SomeException)--import System.IO (hPutStrLn,stderr)-import System.Exit (exitFailure)-import System.Environment (getProgName)---maybeToM :: Monad m => String -> Maybe a -> m a-maybeToM _msg (Just x) = return x-maybeToM msg Nothing = fail msg--errorToMaybe :: MonadError e m => m a -> m (Maybe a)-errorToMaybe ma = catchError (liftM Just ma) (\_ -> return Nothing)--errorToDefault :: MonadError e m => a -> m a -> m a-errorToDefault a ma = catchError ma (\_ -> return a)--eitherToM :: (Show a, Monad m) => Either a b -> m b-eitherToM (Left err) = fail (show err)-eitherToM (Right ok) = return ok--safeMain :: IO () -> IO ()-safeMain io = io `catch` handle- where- handle :: SomeException -> IO ()- handle e =- do s <- getProgName- hPutStrLn stderr ("Caught exception while running " ++ s ++ ": " ++ show e)- exitFailure
src/Data/HttpSpec/Pretty.hs view
@@ -3,8 +3,10 @@ ---------------------------------------- -- SITE-PACKAGES -----------------------------------------import Text.PrettyPrint.HughesPJ (Doc)-+import Text.PrettyPrint.HughesPJ (Doc, text) class Pretty a where ppr :: a -> Doc+ ppr = text . pprString+ pprString :: a -> String+ pprString = show . ppr
− src/Data/HttpSpec/XmlHelper.hs
@@ -1,240 +0,0 @@-module Data.HttpSpec.XmlHelper---- == EXPORTS =================================================================- (XmlEncoding, xmlEncodingFromString, xmlEncodingToString- ,serializeXml, parseXml- ,pickle, pickleWithEnc, pickleStr, unpickle, unpickleStr, unpickleDocM- ,fromPair,fromTriple,fromQuadruple,fromQuintuple- ,PUCase, xpOne, xpMany, xpTagSwitch, xpCase, xpCaseConst, xpSwitch- ,xpTextFixed- ,_ISO8859_1_, _ISO8859_2_, _ISO8859_3_, _ISO8859_4_, _ISO8859_5_- ,_ISO8859_6_, _ISO8859_7_, _ISO8859_8_, _ISO8859_9_, _ISO8859_10_- ,_ISO8859_11_, _ISO8859_13_, _ISO8859_14_, _ISO8859_15_, _ISO8859_16_- ,_USASCII_, _UCS2_, _UTF8_, _UTF16_, _UTF16BE_, _UTF16LE_, _ISOLATIN1_- ,_UNICODE_)-where---- == IMPORTS =================================================================--------------------------------------------- STDLIB------------------------------------------import Control.Monad (liftM)--import Data.Char (toLower)-import Data.Maybe (fromMaybe)-import Data.List (elemIndex)--------------------------------------------- SITE-PACKAGES------------------------------------------import Control.Arrow.ArrowIf (when)-import Control.Arrow.ArrowList (this,none)-import Control.Arrow.ArrowTree (processChildren, processTopDown)--import qualified Data.ByteString.Lazy.Char8 as BSLChar--import Data.Tree.NTree.TypeDefs (NTrees, NTree(..))--import Text.XML.HXT.Arrow- ( XmlTree, XmlPickler(..)- ,runLA, constA, (>>>), arr- ,addXmlPi,addXmlPiEncoding,replaceChildren,xshow,mkText,escapeXmlDoc- ,getChildren, xread)--import Text.XML.HXT.Arrow.Pickle (PU(..), Schema, unpickleDoc,- pickleDoc ,xpElem, xpList, xpAlt, xpWrap,- xpWrapMaybe, xpLift, xpText)-import Text.XML.HXT.Arrow.Pickle.Schema (scFixed)-import Text.XML.HXT.Arrow.ParserInterface (parseXmlDoc,substXmlEntityRefs)-import Text.XML.HXT.Arrow.Edit (canonicalizeContents, removeDocWhiteSpace- ,transfAllCharRef, indentDoc)-import Text.XML.HXT.Arrow.GeneralEntitySubstitution (processGeneralEntities)-import Text.XML.HXT.Arrow.XmlArrow (root, isPi, hasName)-import Text.XML.HXT.Arrow.DocumentOutput (encodeDocument')-import Text.XML.HXT.DOM.TypeDefs (XNode(..))-import Text.XML.HXT.DOM.XmlKeywords- (t_xml- ,iso8859_1, iso8859_2, iso8859_3, iso8859_4, iso8859_5, iso8859_6- ,iso8859_7, iso8859_8, iso8859_9, iso8859_10, iso8859_11, iso8859_13- ,iso8859_14, iso8859_15, iso8859_16, usAscii, ucs2, utf8, utf16, utf16be- ,utf16le, unicodeString, isoLatin1)-import Text.XML.HXT.Arrow.Namespace (propagateNamespaces, validateNamespaces)--------------------------------------------- LOCAL------------------------------------------import Data.HttpSpec.MiscHelper (maybeToM)---- == TYPES ===================================================================--newtype XmlEncoding = XmlEncoding { xmlEncodingName :: String } deriving (Eq)---instance Show XmlEncoding where- show (XmlEncoding name) = name--_UNICODE_ = XmlEncoding unicodeString-_ISO8859_1_ = XmlEncoding iso8859_1-_ISO8859_2_ = XmlEncoding iso8859_2-_ISO8859_3_ = XmlEncoding iso8859_3-_ISO8859_4_ = XmlEncoding iso8859_4-_ISO8859_5_ = XmlEncoding iso8859_5-_ISO8859_6_ = XmlEncoding iso8859_6-_ISO8859_7_ = XmlEncoding iso8859_7-_ISO8859_8_ = XmlEncoding iso8859_8-_ISO8859_9_ = XmlEncoding iso8859_9-_ISO8859_10_ = XmlEncoding iso8859_10-_ISO8859_11_ = XmlEncoding iso8859_11-_ISO8859_13_ = XmlEncoding iso8859_13-_ISO8859_14_ = XmlEncoding iso8859_14-_ISO8859_15_ = XmlEncoding iso8859_15-_ISO8859_16_ = XmlEncoding iso8859_16-_USASCII_ = XmlEncoding usAscii-_UCS2_ = XmlEncoding ucs2-_UTF8_ = XmlEncoding utf8-_UTF16_ = XmlEncoding utf16-_UTF16BE_ = XmlEncoding utf16be-_UTF16LE_ = XmlEncoding utf16le-_ISOLATIN1_ = XmlEncoding isoLatin1----- == ENCODING FUNCTIONS ======================================================--xmlEncodingFromString :: String -> Either String XmlEncoding-xmlEncodingFromString name =- case map toLower name of- "latin1" -> return _ISOLATIN1_- "latin9" -> return _ISO8859_15_- "iso-8859-1" -> return _ISO8859_1_- "iso-8859-2" -> return _ISO8859_2_- "iso-8859-3" -> return _ISO8859_3_- "iso-8859-4" -> return _ISO8859_4_- "iso-8859-5" -> return _ISO8859_5_- "iso-8859-6" -> return _ISO8859_6_- "iso-8859-7" -> return _ISO8859_7_- "iso-8859-8" -> return _ISO8859_8_- "iso-8859-9" -> return _ISO8859_9_- "iso-8859-10" -> return _ISO8859_10_- "iso-8859-11" -> return _ISO8859_11_- "iso-8859-13" -> return _ISO8859_13_- "iso-8859-14" -> return _ISO8859_14_- "iso-8859-15" -> return _ISO8859_15_- "utf8" -> return _UTF8_- "utf-8" -> return _UTF8_- "utf16" -> return _UTF16_- "utf-16" -> return _UTF16_- "ascii" -> return _USASCII_- "us-ascii" -> return _USASCII_- "unicode" -> return _UNICODE_- _ -> fail $ "Can't parse unsupported XmlEncoding name `" ++ name ++ "'."--xmlEncodingToString :: XmlEncoding -> String-xmlEncodingToString = show---- == XML FUNCTIONS ===========================================================--xpOne :: PU a -> PU a-xpOne = id--xpMany :: String -> PU a -> PU [a]-xpMany root pu = xpElem root (xpList pu)--pickle :: PU a -> a -> BSLChar.ByteString-pickle pu = serializeXml _UTF8_ . pickleDoc pu--pickleWithEnc :: XmlEncoding -> PU a -> a -> BSLChar.ByteString-pickleWithEnc enc pu = serializeXml' noProlog enc . pickleDoc pu- where noProlog = enc == _UNICODE_--unpickle :: Monad m => PU a -> BSLChar.ByteString -> m a-unpickle pu bsl = parseXml True True bsl >>= unpickleDocM pu--pickleStr :: PU a -> a -> String-pickleStr pu = BSLChar.unpack . pickleWithEnc _UNICODE_ pu--unpickleDocM :: Monad m => PU a -> XmlTree -> m a-unpickleDocM pu = maybeToM "Unpickling failed." . unpickleDoc pu--unpickleStr pu = unpickle pu . BSLChar.pack--serializeXml :: XmlEncoding -> XmlTree -> BSLChar.ByteString-serializeXml enc xml = serializeXml' False enc xml--serializeXml' :: Bool -> XmlEncoding -> XmlTree -> BSLChar.ByteString-serializeXml' indent enc xml =- let [str] = runLA (constA xml >>> encodeA) undefined- in (BSLChar.pack (strip str))- where rmpi = enc == _UNICODE_- encodeA =- (if indent then processChildren indentDoc else this)- >>>- escapeXmlDoc- >>>- encodeDocument' rmpi (xmlEncodingName enc)- >>>- replaceChildren (xshow getChildren >>> arr encode >>> mkText)- >>>- xshow getChildren- encode = id- strip = reverse . dropWhile isSpace . reverse . dropWhile isSpace- isSpace = (`elem` " \n\t")---parseXml :: Monad m => Bool -> Bool -> BSLChar.ByteString -> m XmlTree-parseXml rmpi rmspace bstr =- let trees = runLA pipe undefined- in case trees of- [] -> fail "empty result"- (NTree _ ((NTree (XError _ msg) _) : _) : _) -> fail msg- trees -> return (last trees)- where str = BSLChar.unpack bstr- pipe = root [] []- >>> replaceChildren parse- >>> (if rmpi then removeXmlPi else this)- >>> (if rmspace then removeDocWhiteSpace else this)- parse = constA ("urn:Data.ByteString", str)- >>> parseXmlDoc- >>> substXmlEntityRefs- >>> canonicalizeContents- -- >>> propagateNamespaces--removeXmlPi = processTopDown (none `when` (isPi >>> hasName t_xml))--fromPair :: (a -> b -> c) -> (a, b) -> c-fromPair f ~(a, b) = f a b--fromTriple :: (a -> b -> c -> d) -> (a, b, c) -> d-fromTriple f ~(a, b, c) = f a b c--fromQuadruple :: (a -> b -> c -> d -> e) -> (a, b, c, d) -> e-fromQuadruple f ~(a, b, c, d) = f a b c d--fromQuintuple :: (a -> b -> c -> d -> e -> f) -> (a, b, c, d, e) -> f-fromQuintuple f ~(a, b, c, d, e) = f a b c d e--data PUCase a = PUCase { case_value :: a- , case_spec :: PU a }--xpCase :: (a -> b, b -> a) -> PU a -> PUCase b-xpCase wrapfuns@(aToB,_bToA) specA = PUCase value spec- where value = aToB (error "xpCase: tagging function requires evaluation")- spec = xpWrap wrapfuns specA--xpCaseConst :: a -> PU () -> PUCase a-xpCaseConst a pu = xpCase (const a, const undefined) pu- where undef = error "xpCaseConst: this value should have been ignored"--xpSwitch :: Show a => [PUCase a] -> PU a-xpSwitch = xpTagSwitch (takeWhile (/= ' ') . show)--xpTagSwitch :: Eq t => (a -> t) -> [PUCase a] -> PU a-xpTagSwitch tag cases = xpAlt idx (map case_spec cases)- where idx = fromMaybe err . flip elemIndex tags . tag- err = error $ "xpTagSwitch: no case matched"- tags = map (tag . case_value) cases--xpTextFixed :: String -> PU ()-xpTextFixed text =- (xpWrapMaybe (\t -> if t == text then Just () else Nothing, const text)- xpText) { theSchema = scFixed text }