packages feed

wai-middleware-content-type 0.1.0 → 0.1.0.1

raw patch · 10 files changed

+387/−34 lines, 10 filesdep +hspecdep +hspec-waidep +pandoc-typesdep ~urlpathdep ~wai-transformersnew-component:exe:examplePVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: hspec, hspec-wai, pandoc-types, tasty, tasty-hspec, warp

Dependency ranges changed: urlpath, wai-transformers

API changes (from Hackage documentation)

- Network.Wai.Middleware.ContentType: allFileExts :: [FileExt]
+ Network.Wai.Middleware.ContentType: invalidEncoding :: MonadIO m => MiddlewareT m -> FileExtListenerT (MiddlewareT m) m ()
+ Network.Wai.Middleware.ContentType.Types: None :: FileExt
+ Network.Wai.Middleware.ContentType.Types: allFileExts :: [FileExt]
- Network.Wai.Middleware.ContentType: possibleFileExts :: Maybe FileExt -> AcceptHeader -> [FileExt]
+ Network.Wai.Middleware.ContentType: possibleFileExts :: AcceptHeader -> [FileExt]

Files

+ examples/Example.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE+    OverloadedStrings+  #-}++module Main where++import Network.Wai.Handler.Warp+import Network.Wai.Trans+import Network.Wai.Middleware.ContentType+import Network.HTTP.Types+import qualified Data.Text as T+import qualified Lucid     as L+import Control.Monad.IO.Class+++data AppError = AppError++myListener :: MonadIO m => FileExtListenerT (MiddlewareT m) m ()+myListener = do+  text "Text!"+  json ("Json!" :: T.Text)+  lucid (L.toHtmlRaw ("Html!" :: T.Text))++myApp :: MonadIO m => MiddlewareT m+myApp = fileExtsToMiddleware myListener++main :: IO ()+main = run 3000 (myApp defApp)+  where+    defApp :: Application+    defApp _ respond =+      respond $ textOnlyStatus status404 "404"
src/Network/Wai/Middleware/ContentType.hs view
@@ -43,7 +43,7 @@     fileExtsToMiddleware   , lookupResponse   , possibleFileExts-  , allFileExts+  , invalidEncoding   , AcceptHeader   , -- * Re-Exports     module Network.Wai.Middleware.ContentType.Types@@ -76,6 +76,8 @@ import Network.Wai.Middleware.ContentType.Text import Network.Wai.Middleware.ContentType.Pandoc +import Network.Wai.Middleware.ContentType.Middleware (middleware)+ import qualified Data.ByteString as BS import qualified Data.Map as Map import Data.Maybe (fromMaybe, catMaybes)@@ -92,7 +94,7 @@                           -> MiddlewareT m fileExtsToMiddleware contentRoutes app req respond = do   let mAcceptBS = Prelude.lookup ("Accept" :: HeaderName) $ requestHeaders req-      mFe = getFileExt req+      mFe       = getFileExt req   mMiddleware <- lookupResponse mAcceptBS mFe contentRoutes   fromMaybe (app req respond) $ do     mid <- mMiddleware@@ -110,19 +112,26 @@   return $ lookupFileExt femap   where     lookupFileExt xs =-      let attempts = maybe allFileExts (possibleFileExts mFe) mAcceptBS-      in  getFirst $ foldMap (\f' -> First $ Map.lookup f' xs) attempts+      let attempts = findFE $ maybe allFileExts possibleFileExts mAcceptBS+      in  getFirst $ foldMap (First . flip Map.lookup xs) attempts +    findFE :: [FileExt] -> [FileExt]+    findFE xs =+      case mFe of+        Nothing -> []+        Just fe | fe == None -> xs+                | otherwise  -> fe <$ guard (fe `elem` xs) --- | Takes a file extension and an @Accept@ header, and returns the other++-- | Takes an @Accept@ header and returns the other -- file types handleable, in order of prescedence.-possibleFileExts :: Maybe FileExt -> AcceptHeader -> [FileExt]-possibleFileExts mFe accept = if not (null wildcard) then wildcard else computed+possibleFileExts :: AcceptHeader -> [FileExt]+possibleFileExts accept = if not (null wildcard) then wildcard else computed   where     computed :: [FileExt]-    computed = findFE $ concat $+    computed = concat $       catMaybes [ mapAccept [ ("application/json"       :: BS.ByteString, [Json])-                            , ("application/javascript" :: BS.ByteString, [Json,JavaScript])+                            , ("application/javascript" :: BS.ByteString, [JavaScript,Json])                             ] accept                 , mapAccept [ ("text/html" :: BS.ByteString, [Html])                             ] accept@@ -135,15 +144,17 @@                 ]      wildcard :: [FileExt]-    wildcard = findFE $ fromMaybe [] $ mapAccept [ ("*/*" :: BS.ByteString, allFileExts)-                                                 ] accept+    wildcard = fromMaybe [] $ mapAccept [ ("*/*" :: BS.ByteString, allFileExts)+                                        ] accept  -    findFE :: [FileExt] -> [FileExt]-    findFE xs = maybe xs (\fe -> fe <$ guard (fe `elem` xs)) mFe +-- | Use this combinator as the last one, as a "catch-all":+--+--   > myApp = do+--   >   text "foo"+--   >   invalidEncoding myErrorHandler+invalidEncoding :: MonadIO m => MiddlewareT m -> FileExtListenerT (MiddlewareT m) m ()+invalidEncoding mid = mapM_ (`middleware` mid) allFileExts --- | All file extensions, in the order of preference-allFileExts :: [FileExt]-allFileExts = [Html,Text,Json,JavaScript,Css,Markdown] 
src/Network/Wai/Middleware/ContentType/Cassius.hs view
@@ -16,22 +16,22 @@  -- | Uses @cassius@ as the key in the map, and @"cassius/plain"@ as the content type. cassius :: MonadIO m => Css -> FileExtListenerT (MiddlewareT m) m ()-cassius = cassiusStatusHeaders status200 [("Content-Type", "cassius/css")]+cassius = cassiusStatusHeaders status200 [("Content-Type", "text/css")]  cassiusWith :: MonadIO m =>                (Response -> Response) -> Css             -> FileExtListenerT (MiddlewareT m) m ()-cassiusWith f = cassiusStatusHeadersWith f status200 [("Content-Type", "cassius/css")]+cassiusWith f = cassiusStatusHeadersWith f status200 [("Content-Type", "text/css")]  cassiusStatus :: MonadIO m =>                  Status -> Css               -> FileExtListenerT (MiddlewareT m) m ()-cassiusStatus s = cassiusStatusHeaders s [("Content-Type", "cassius/css")]+cassiusStatus s = cassiusStatusHeaders s [("Content-Type", "text/css")]  cassiusStatusWith :: MonadIO m =>                      (Response -> Response) -> Status -> Css                   -> FileExtListenerT (MiddlewareT m) m ()-cassiusStatusWith f s = cassiusStatusHeadersWith f s [("Content-Type", "cassius/css")]+cassiusStatusWith f s = cassiusStatusHeadersWith f s [("Content-Type", "text/css")]  cassiusHeaders :: MonadIO m =>                   RequestHeaders -> Css@@ -58,10 +58,10 @@ -- * 'Network.Wai.Response' Only  cassiusOnly :: Css -> Response-cassiusOnly = cassiusOnlyStatusHeaders status200 [("Content-Type", "cassius/css")]+cassiusOnly = cassiusOnlyStatusHeaders status200 [("Content-Type", "text/css")]  cassiusOnlyStatus :: Status -> Css -> Response-cassiusOnlyStatus s = cassiusOnlyStatusHeaders s [("Content-Type", "cassius/css")]+cassiusOnlyStatus s = cassiusOnlyStatusHeaders s [("Content-Type", "text/css")]  cassiusOnlyHeaders :: RequestHeaders -> Css -> Response cassiusOnlyHeaders = cassiusOnlyStatusHeaders status200
src/Network/Wai/Middleware/ContentType/Julius.hs view
@@ -3,6 +3,7 @@ module Network.Wai.Middleware.ContentType.Julius where  import           Network.Wai.Middleware.ContentType.Types+import qualified Network.Wai.Middleware.ContentType.Types as CT import           Network.Wai.Middleware.ContentType.ByteString import           Network.HTTP.Types                      (RequestHeaders, Status, status200) import           Network.Wai.Trans@@ -53,7 +54,7 @@                            (Response -> Response) -> Status -> RequestHeaders -> Javascript                         -> FileExtListenerT (MiddlewareT m) m () juliusStatusHeadersWith f s hs i =-  bytestringStatusWith f Json s hs $ LT.encodeUtf8 $ renderJavascript i+  bytestringStatusWith f CT.JavaScript s hs $ LT.encodeUtf8 $ renderJavascript i   -- * 'Network.Wai.Response' Only
src/Network/Wai/Middleware/ContentType/Lucius.hs view
@@ -16,22 +16,22 @@  -- | Uses @lucius@ as the key in the map, and @"lucius/css"@ as the content type. lucius :: MonadIO m => Css -> FileExtListenerT (MiddlewareT m) m ()-lucius = luciusStatusHeaders status200 [("Content-Type", "lucius/css")]+lucius = luciusStatusHeaders status200 [("Content-Type", "text/css")]  luciusWith :: MonadIO m =>               (Response -> Response) -> Css            -> FileExtListenerT (MiddlewareT m) m ()-luciusWith f = luciusStatusHeadersWith f status200 [("Content-Type", "lucius/css")]+luciusWith f = luciusStatusHeadersWith f status200 [("Content-Type", "text/css")]  luciusStatus :: MonadIO m =>                 Status -> Css              -> FileExtListenerT (MiddlewareT m) m ()-luciusStatus s = luciusStatusHeaders s [("Content-Type", "lucius/css")]+luciusStatus s = luciusStatusHeaders s [("Content-Type", "text/css")]  luciusStatusWith :: MonadIO m =>                     (Response -> Response) -> Status -> Css                  -> FileExtListenerT (MiddlewareT m) m ()-luciusStatusWith f s = luciusStatusHeadersWith f s [("Content-Type", "lucius/css")]+luciusStatusWith f s = luciusStatusHeadersWith f s [("Content-Type", "text/css")]  luciusHeaders :: MonadIO m =>                  RequestHeaders -> Css@@ -58,10 +58,10 @@ -- * 'Network.Wai.Response' Only  luciusOnly :: Css -> Response-luciusOnly = luciusOnlyStatusHeaders status200 [("Content-Type", "lucius/css")]+luciusOnly = luciusOnlyStatusHeaders status200 [("Content-Type", "text/css")]  luciusOnlyStatus :: Status -> Css -> Response-luciusOnlyStatus s = luciusOnlyStatusHeaders s [("Content-Type", "lucius/css")]+luciusOnlyStatus s = luciusOnlyStatusHeaders s [("Content-Type", "text/css")]  luciusOnlyHeaders :: RequestHeaders -> Css -> Response luciusOnlyHeaders = luciusOnlyStatusHeaders status200
src/Network/Wai/Middleware/ContentType/Pandoc.hs view
@@ -53,7 +53,7 @@                           (Response -> Response) -> Status -> RequestHeaders -> P.Pandoc                        -> FileExtListenerT (MiddlewareT m) m () markdownStatusHeadersWith f s hs i =-  bytestringStatusWith f Html s hs . LT.encodeUtf8 . LT.pack $ P.writeMarkdown P.def i+  bytestringStatusWith f Markdown s hs . LT.encodeUtf8 . LT.pack $ P.writeMarkdown P.def i   -- * 'Network.Wai.Response' Only
src/Network/Wai/Middleware/ContentType/Types.hs view
@@ -23,6 +23,7 @@ module Network.Wai.Middleware.ContentType.Types   ( -- * Types     FileExt (..)+  , allFileExts   , getFileExt   , toExt   , FileExtMap@@ -64,19 +65,25 @@              | Json              | Text              | Markdown+             | None   deriving (Show, Eq, Ord) +-- | All file extensions, in the order of likeliness+allFileExts :: [FileExt]+allFileExts = [Html,Text,Json,JavaScript,Css,Markdown] + -- | Gets the known file extension from a Request's 'Network.Wai.pathInfo'. getFileExt :: Request -> Maybe FileExt getFileExt req = case pathInfo req of-  [] -> Nothing+  [] -> Just None   xs -> toExt $ snd $ T.breakOn "." $ last xs  -- | matches a file extension (__including__ it's prefix dot - @.html@ for example) --   to a known one. toExt :: T.Text -> Maybe FileExt-toExt x | x `elem` htmls       = Just Html+toExt x | x == ""              = Just None+        | x `elem` htmls       = Just Html         | x `elem` csss        = Just Css         | x `elem` javascripts = Just JavaScript         | x `elem` jsons       = Just Json
+ test/Network/Wai/Middleware/ContentTypeSpec.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE+    OverloadedStrings+  , QuasiQuotes+  #-}++module Network.Wai.Middleware.ContentTypeSpec where++import Network.Wai.Middleware.ContentType+import Network.Wai.Trans+import Network.HTTP.Types+import qualified Data.Text      as T+import qualified Data.Text.Lazy as LT+import qualified Lucid          as L+import qualified Text.Lucius    as SL+import qualified Text.Julius    as SJ+import qualified Text.Pandoc    as P+import Test.Hspec+import Test.Hspec.Wai   as HW++++mockServer :: Spec+mockServer = do+  describe "All Requests Respond to the Accept Header" $+    with (return app) $ do+      describe "Text" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "text/plain")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Text!"+           }+      describe "Json" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "application/json")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "\"Json!\""+           }+      describe "Html" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "text/html")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Html!"+           }+      describe "Css" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "text/css")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "body{background:#fff}"+           }+      describe "JavaScript" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "application/javascript")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "function foo () {return;}"+           }+      describe "Markdown" $+        it "should respond with 200" $+        HW.request "GET" "/" [("Accept", "text/markdown")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "*Pandoc*!"+           }+  describe "All Requests Respond to the File Extension" $+    with (return app) $ do+      describe "Text" $+        it "should respond with 200" $+        HW.get "/index.txt" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Text!"+           }+      describe "Json" $+        it "should respond with 200" $+        HW.get "/index.json" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "\"Json!\""+           }+      describe "Html" $+        it "should respond with 200" $+        HW.get "index.html" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Html!"+           }+      describe "Css" $+        it "should respond with 200" $+        HW.get "/index.css" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "body{background:#fff}"+           }+      describe "JavaScript" $+        it "should respond with 200" $+        HW.get "/index.js" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "function foo () {return;}"+           }+      describe "Markdown" $+        it "should respond with 200" $+        HW.get "/index.md" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "*Pandoc*!"+           }+  describe "All Requests Respond to Both" $+    with (return app) $ do+      describe "Text" $+        it "should respond with 200" $+        HW.request "GET" "/index.txt" [("Accept", "text/plain")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Text!"+           }+      describe "Json" $+        it "should respond with 200" $+        HW.request "GET" "/index.json" [("Accept", "application/json")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "\"Json!\""+           }+      describe "Html" $+        it "should respond with 200" $+        HW.request "GET" "/index.html" [("Accept", "text/html")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Html!"+           }+      describe "Css" $+        it "should respond with 200" $+        HW.request "GET" "/index.css" [("Accept", "text/css")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "body{background:#fff}"+           }+      describe "JavaScript" $+        it "should respond with 200" $+        HW.request "GET" "/index.js" [("Accept", "application/javascript")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "function foo () {return;}"+           }+      describe "Markdown" $+        it "should respond with 200" $+        HW.request "GET" "/index.md" [("Accept", "text/markdown")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "*Pandoc*!"+           }+  describe "Outlier behavior" $+    with (return app) $ do+      describe "No extension, with header" $+        it " should respond with 200" $+        HW.request "GET" "/index" [("Accept", "text/plain")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "Text!"+           }+      describe ".markdown & text/plain" $+        it " should respond with 200" $+        HW.request "GET" "/index.md" [("Accept", "text/plain")] "" `shouldRespondWith`+        "" { matchStatus = 200+           , matchBody = Just "*Pandoc*!"+           }+  describe "Unfulfillable accept headers should break" $+    with (return app) $ do+      describe "Text on non-ContentType" $+        it "should respond with 406" $+        HW.request "GET" "/index.txt" [("Accept", "foo/bar")] "" `shouldRespondWith`+        406+      describe "Json with non-file extension" $+        it "should respond with 406" $+        HW.request "GET" "/index.asdf" [("Accept", "application/json")] "" `shouldRespondWith`+        406++app :: Application+app = fileExtsToMiddleware allExamples $ \_ resp ->+  resp $ textOnlyStatus status406 "Something went wrong"++allExamples :: FileExtListenerT Middleware IO ()+allExamples = do+  text "Text!"+  json ("Json!" :: T.Text)+  lucid (L.toHtmlRaw ("Html!" :: T.Text))+  cassius ([SL.lucius|body {background: #fff;}|] undefined)+  julius  ([SJ.julius|function foo () {return;}|] undefined)+  markdown $ case P.readMarkdown P.def "*Pandoc*!" of+               Left e -> error $ show e+               Right p -> p
+ test/Spec.hs view
@@ -0,0 +1,13 @@+module Main where++import Network.Wai.Middleware.ContentTypeSpec++import Test.Tasty+import Test.Tasty.Hspec as TH+++main :: IO ()+main = do+  wai <- TH.testSpec "Network.Wai.Middleware.ContentType" mockServer+  defaultMain $ testGroup "Testing..."+    [wai]
wai-middleware-content-type.cabal view
@@ -1,5 +1,5 @@ Name:                   wai-middleware-content-type-Version:                0.1.0+Version:                0.1.0.1 Author:                 Athan Clark <athan.clark@gmail.com> Maintainer:             Athan Clark <athan.clark@gmail.com> License:                BSD3@@ -10,6 +10,12 @@ Build-Type:             Simple Category:               Web ++Flag Example+  Description:          Build a trivial example+  Default:              False++ Library   Default-Language:     Haskell2010   HS-Source-Dirs:       src@@ -51,8 +57,115 @@                       , transformers-base                       , urlpath >= 5.0.0.1                       , wai-                      , wai-transformers >= 0.0.3+                      , wai-transformers >= 0.0.4                       , wai-util++Test-Suite test+  Type:                 exitcode-stdio-1.0+  Default-Language:     Haskell2010+  HS-Source-Dirs:       src+                      , test+  GHC-Options:          -Wall -threaded+  Main-Is:              Spec.hs+  Other-Modules:        Network.Wai.Middleware.ContentType+                        Network.Wai.Middleware.ContentType.Types+                        Network.Wai.Middleware.ContentType.Blaze+                        Network.Wai.Middleware.ContentType.Builder+                        Network.Wai.Middleware.ContentType.ByteString+                        Network.Wai.Middleware.ContentType.Cassius+                        Network.Wai.Middleware.ContentType.Clay+                        Network.Wai.Middleware.ContentType.Json+                        Network.Wai.Middleware.ContentType.Julius+                        Network.Wai.Middleware.ContentType.Lucid+                        Network.Wai.Middleware.ContentType.Lucius+                        Network.Wai.Middleware.ContentType.Pandoc+                        Network.Wai.Middleware.ContentType.Text+                        Network.Wai.Middleware.ContentType.Middleware+                        Network.Wai.Middleware.ContentTypeSpec+  Build-Depends:        base >= 4.8 && < 5+                      , aeson+                      , blaze-builder+                      , blaze-html+                      , bytestring+                      , clay+                      , containers+                      , exceptions+                      , hspec+                      , hspec-wai+                      , http-media+                      , http-types+                      , lucid+                      , mmorph+                      , monad-control+                      , monad-logger+                      , mtl+                      , pandoc+                      , pandoc-types+                      , resourcet+                      , shakespeare+                      , text+                      , tasty+                      , tasty-hspec+                      , transformers+                      , transformers-base+                      , urlpath+                      , wai+                      , wai-transformers+                      , wai-util+                      , warp++Executable example+  if flag(Example)+    Buildable: True+  else+    Buildable: False+  Default-Language:     Haskell2010+  HS-Source-Dirs:       src+                      , examples+  GHC-Options:          -Wall -threaded+  Main-Is:              Example.hs+  Other-Modules:        Network.Wai.Middleware.ContentType+                        Network.Wai.Middleware.ContentType.Types+                        Network.Wai.Middleware.ContentType.Blaze+                        Network.Wai.Middleware.ContentType.Builder+                        Network.Wai.Middleware.ContentType.ByteString+                        Network.Wai.Middleware.ContentType.Cassius+                        Network.Wai.Middleware.ContentType.Clay+                        Network.Wai.Middleware.ContentType.Json+                        Network.Wai.Middleware.ContentType.Julius+                        Network.Wai.Middleware.ContentType.Lucid+                        Network.Wai.Middleware.ContentType.Lucius+                        Network.Wai.Middleware.ContentType.Pandoc+                        Network.Wai.Middleware.ContentType.Text+                        Network.Wai.Middleware.ContentType.Middleware+  Build-Depends:        base >= 4.8 && < 5+                      , aeson+                      , blaze-builder+                      , blaze-html+                      , bytestring+                      , clay+                      , containers+                      , exceptions+                      , http-media+                      , http-types+                      , lucid+                      , mmorph+                      , monad-control+                      , monad-logger+                      , mtl+                      , pandoc+                      , resourcet+                      , shakespeare+                      , text+                      , transformers+                      , transformers-base+                      , urlpath+                      , wai+                      , wai-transformers+                      , wai-util+                      , warp++  Source-Repository head   Type:                 git