packages feed

fn 0.3.0.1 → 0.3.0.2

raw patch · 5 files changed

+95/−61 lines, 5 filesdep +resourcetdep ~basenew-uploaderPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: resourcet

Dependency ranges changed: base

API changes (from Hackage documentation)

- Web.Fn: [fileContent] :: File -> ByteString
+ Web.Fn: [filePath] :: File -> FilePath
+ Web.Fn: okJson :: Text -> IO (Maybe Response)
+ Web.Fn: tempFileBackEnd' :: InternalState -> ignored1 -> FileInfo () -> IO ByteString -> IO FilePath
- Web.Fn: File :: Text -> Text -> ByteString -> File
+ Web.Fn: File :: Text -> Text -> FilePath -> File
- Web.Fn: type Req = ([Text], Query, StdMethod, PostMVar)
+ Web.Fn: type Req = (Request, [Text], Query, StdMethod, PostMVar)

Files

CHANGELOG.md view
@@ -1,3 +1,12 @@+* 0.3.0.2 Libby Horacek <libby@positiondev.com> 2017-6-13++  - Update base dependencies to support GHC 8+  - Add a stack.yml for 8.15++* 0.3.0.1.1 David Hartunian <david@positiondev.com> 2017-6-30++  - Add okJson helper for returning JSON strings as text+ * 0.3.0.1 Daniel Patterson <dbp@dbpmail.net> 2016-3-11    - Change repository location, copyright.
README.md view
@@ -5,6 +5,6 @@  ## Example -See the [example application](https://github.com/dbp/fn/tree/master/example)+See the [example application](https://github.com/positiondev/fn/tree/master/example) in the repository for a full usage including database access, heist templates, sessions, etc.
fn.cabal view
@@ -1,5 +1,5 @@ name:                fn-version:             0.3.0.1+version:             0.3.0.2 synopsis:            A functional web framework. description:   A Haskell web framework where you write plain old functions.@@ -82,7 +82,7 @@ library   hs-source-dirs:      src   exposed-modules:     Web.Fn-  build-depends:       base >= 4.7 && < 5+  build-depends:       base >= 4.7 && < 6                      , wai >= 3                      , wai-extra >= 3                      , http-types@@ -92,6 +92,7 @@                      , unordered-containers                      , filepath                      , directory+                     , resourcet   default-language:    Haskell2010   ghc-options:         -Wall @@ -109,6 +110,7 @@                      , unordered-containers                      , filepath                      , directory+                     , resourcet   ghc-options:         -threaded -rtsopts -with-rtsopts=-N -Wall   default-language:    Haskell2010 
src/Web/Fn.hs view
@@ -52,6 +52,7 @@               , staticServe               , sendFile               , okText+              , okJson               , okHtml               , errText               , errHtml@@ -59,12 +60,18 @@               , notFoundHtml               , redirect               , redirectReferer+                -- * Helpers+              , tempFileBackEnd'   ) where  import qualified Blaze.ByteString.Builder.Char.Utf8 as B import           Control.Applicative                ((<$>)) import           Control.Arrow                      (second) import           Control.Concurrent.MVar+import           Control.Monad                      (join)+import           Control.Monad.Trans.Resource       (InternalState,+                                                     closeInternalState,+                                                     createInternalState) import           Data.ByteString                    (ByteString) import qualified Data.ByteString.Lazy               as LB import           Data.Either                        (lefts, rights)@@ -80,7 +87,9 @@                                                      lbsBackEnd,                                                      parseRequestBody) import qualified Network.Wai.Parse                  as Parse-import           System.Directory                   (doesFileExist)+import           System.Directory                   (doesFileExist,+                                                     getTemporaryDirectory,+                                                     removeFile) import           System.FilePath                    (takeExtension)  data Store b a = Store b (b -> a)@@ -90,7 +99,7 @@ -- | The type of a route, constructed with 'pattern ==> handler'. type Route ctxt = ctxt -> Req -> IO (Maybe (IO (Maybe Response))) -type PostMVar = Maybe (MVar (Maybe ([Param], [Parse.File LB.ByteString])))+type PostMVar = Maybe (MVar (Maybe (([Param], [Parse.File FilePath]), InternalState)))  -- | A normal WAI 'Request' and the parsed post body (if present). We can -- only parse the body once, so we need to have our request (which we@@ -136,7 +145,12 @@ toWAI :: RequestContext ctxt => ctxt -> (ctxt -> IO Response) -> Application toWAI ctxt f req cont =   do mv <- newMVar Nothing-     f (setRequest ctxt (req, Just mv)) >>= cont+     do resp <- f (setRequest ctxt (req, Just mv))+        posted <- tryTakeMVar mv+        case join posted of+          Nothing -> return ()+          Just (_,is) -> closeInternalState is+        cont resp  -- | The main construct for Fn, 'route' takes a context (which it will pass -- to all handlers) and a list of potential matches (which, once they@@ -158,7 +172,7 @@ route ctxt pths =   do let (r,post) = getRequest ctxt          m = either (const GET) id (parseMethod (requestMethod r))-         req = (filter (/= "") (pathInfo r), queryString r, m, post)+         req = (r, filter (/= "") (pathInfo r), queryString r, m, post)      route' req pths   where route' _ [] = return Nothing         route' req (x:xs) =@@ -287,7 +301,7 @@         else return Nothing  -- | The parts of the path, when split on /, and the query.-type Req = ([Text], Query, StdMethod, PostMVar)+type Req = (Request, [Text], Query, StdMethod, PostMVar)  -- | The non-body parsing connective between route patterns and the -- handler that will be called if the pattern matches. The type is not@@ -304,10 +318,23 @@    do rsp <- match req       case rsp of         Nothing -> return Nothing-        Just ((pathInfo',_,_,_), k) ->+        Just ((_,pathInfo',_,_,_), k) ->           let (request, mv) = getRequest ctxt in           return $ Just (k $ handle (setRequest ctxt (request { pathInfo = pathInfo' }, mv))) +-- | Internal helper - uses the name of the file as the pattern.+tempFileBackEnd' :: InternalState -> ignored1 -> FileInfo () -> IO ByteString -> IO FilePath+tempFileBackEnd' is x fi@(FileInfo nm _ _) = Parse.tempFileBackEndOpts getTemporaryDirectory (T.unpack $ T.decodeUtf8 nm) is x fi++readBody mv request =+  modifyMVar_ mv+    (\r -> case r of+             Nothing ->+               do is <- createInternalState+                  rb <- parseRequestBody (tempFileBackEnd' is) request+                  return (Just (rb, is))+             Just _ -> return r)+ -- | The connective between route patterns and the handler that parses -- the body, which allows post params to be extracted with 'param' and -- allows 'file' to work (otherwise, it will trigger a runtime error).@@ -319,13 +346,11 @@          IO (Maybe a) (match !=> handle) ctxt req =    do let (request, Just mv) = getRequest ctxt-      modifyMVar_ mv (\r -> case r of-                              Nothing -> Just <$> parseRequestBody lbsBackEnd request-                              Just _ -> return r)+      readBody mv request       rsp <- match req       case rsp of         Nothing -> return Nothing-        Just ((pathInfo',_,_,_), k) ->+        Just ((_,pathInfo',_,_,_), k) ->           do return $ Just (k $ handle (setRequest ctxt (request { pathInfo = pathInfo' }, Just mv)))  -- | Connects two path segments. Note that when normally used, the@@ -356,7 +381,7 @@ path :: Text -> Req -> IO (Maybe (Req, a -> a)) path s req =   return $ case req of-             (y:ys,q,m,x) | y == s -> Just ((ys, q, m, x), id)+             (r,y:ys,q,m,x) | y == s -> Just ((r,ys, q, m, x), id)              _               -> Nothing  -- | Matches there being no parts of the path left. This is useful when@@ -364,7 +389,7 @@ end :: Req -> IO (Maybe (Req, a -> a)) end req =   return $ case req of-             ([],_,_,_) -> Just (req, id)+             (_,[],_,_,_) -> Just (req, id)              _ -> Nothing  -- | Matches anything.@@ -377,14 +402,14 @@ segment :: FromParam p => Req -> IO (Maybe (Req, (p -> a) -> a)) segment req =   return $ case req of-             (y:ys,q,m,x) -> case fromParam [y] of-                               Left _ -> Nothing-                               Right p -> Just ((ys, q, m, x), \k -> k p)+             (r,y:ys,q,m,x) -> case fromParam [y] of+                                 Left _ -> Nothing+                                 Right p -> Just ((r, ys, q, m, x), \k -> k p)              _     -> Nothing  -- | Matches on a particular HTTP method. method :: StdMethod -> Req -> IO (Maybe (Req, a -> a))-method m r@(_,_,m',_) | m == m' = return $ Just (r, id)+method m r@(_,_,_,m',_) | m == m' = return $ Just (r, id) method _ _ = return Nothing  data ParamError = ParamMissing | ParamTooMany | ParamUnparsable | ParamOtherError Text deriving (Eq, Show)@@ -434,7 +459,7 @@                      Just mv' -> do v <- readMVar mv'                                     return $ case v of                                                Nothing -> []-                                               Just (ps',_) -> ps'+                                               Just ((ps',_),_) -> ps'                      Nothing -> return []  -- | Matches on a query parameter of the given name. It is parsed into@@ -450,7 +475,7 @@ -- match query parameters. param :: FromParam p => Text -> Req -> IO (Maybe (Req, (p -> a) -> a)) param n req =-  do let (_,q,_,mv) = req+  do let (_,_,q,_,mv) = req      ps <- getMVarParams mv      return $ case findParamMatches n (q ++ map (second Just) ps) of                 Right y -> Just (req, \k -> k y)@@ -462,7 +487,7 @@ -- handler, it won't match. paramMany :: FromParam p => Text -> Req -> IO (Maybe (Req, ([p] -> a) -> a)) paramMany n req =-  do let (_,q,_,mv) = req+  do let (_,_,q,_,mv) = req      ps <- getMVarParams mv      return $ case findParamMatches n (q ++ map (second Just) ps) of                 Left _ -> Nothing@@ -482,7 +507,7 @@             Req ->             IO (Maybe (Req, (Either ParamError p -> a) -> a)) paramOpt n req =-  do let (_,q,_,mv) = req+  do let (_,_,q,_,mv) = req      ps <- getMVarParams mv      return $ Just (req, \k -> k (findParamMatches n (q ++ map (second Just) ps))) @@ -490,50 +515,36 @@ -- | An uploaded file. data File = File { fileName        :: Text                  , fileContentType :: Text-                 , fileContent     :: LB.ByteString+                 , filePath        :: FilePath                  } -getMVarFiles mv = case mv of-                    Nothing -> error $ "Fn: tried to read a 'file' or 'files', but FnRequest wasn't initialized with MVar."-                    Just mv' -> do-                      v <- readMVar mv'-                      case v of-                        Nothing -> error $ "Fn: tried to read a 'file' or 'files' from the request without parsing the body with '!=>'"-                        Just (_,fs') -> return fs'+getMVarFiles mv req =+  case mv of+    Nothing -> error $ "Fn: tried to read a 'file' or 'files', but FnRequest wasn't initialized with MVar."+    Just mv' -> do+      -- NOTE(dbp 2016-03-25): readBody ensures that the value will be Just.+      readBody mv' req+      Just ((_,fs'),_) <- readMVar mv'+      return $ map (\(n, FileInfo nm ct c) ->+                     (T.decodeUtf8 n, File (T.decodeUtf8 nm)+                                           (T.decodeUtf8 ct)+                                           c)) fs'  -- | Matches an uploaded file with the given parameter name.------ Note: You must use the '!=>' connective between the pattern and the--- handler, or else the request body will not have been parsed and--- this will fail. file :: Text -> Req -> IO (Maybe (Req, (File -> a) -> a)) file n req =-  do let (_,_,_,mv) = req-     fs <- getMVarFiles mv-     return $ case filter ((== T.encodeUtf8 n) . fst) fs of-                [(_, FileInfo nm ct c)] -> Just (req, \k -> k (File (T.decodeUtf8 nm)-                                                                    (T.decodeUtf8 ct)-                                                                    c))+  do let (r,_,_,_,mv) = req+     fs <- getMVarFiles mv r+     return $ case filter ((== n) . fst) fs of+                [(_, f)] -> Just (req, \k -> k f)                 _ -> Nothing  -- | Matches all uploaded files, passing their parameter names and -- contents.------ Note: You must use the '!=>' connective between the pattern and the--- handler, or else the request body will not have been parsed and--- this will fail. files :: Req -> IO (Maybe (Req, ([(Text, File)] -> a) -> a)) files req =-  do let (_,_,_,Just mv) = req-     v <- readMVar mv-     let fs' = case v of-                 Nothing -> error $ "Fn: tried to read a 'file' from the request without parsing the body with '!=>'"-                 Just (_,fs) -> fs-     let fs = map (\(n, FileInfo nm ct c) ->-                 (T.decodeUtf8 n, File (T.decodeUtf8 nm)-                                       (T.decodeUtf8 ct)-                                       c))-              fs'+  do let (r,_,_,_,mv) = req+     fs <- getMVarFiles mv r      return $ Just (req, \k -> k fs)  returnText :: Text -> Status -> ByteString -> IO (Maybe Response)@@ -546,6 +557,9 @@ plainText :: ByteString plainText = "text/plain; charset=utf-8" +applicationJson :: ByteString+applicationJson = "application/json; charset=utf-8"+ html :: ByteString html = "text/html; charset=utf-8" @@ -553,6 +567,9 @@ okText :: Text -> IO (Maybe Response) okText t = returnText t status200 plainText +-- | Returns 'Text' as a JSON response with appropriate header.+okJson :: Text -> IO (Maybe Response)+okJson j = returnText j status200 applicationJson  -- | Returns Html (in 'Text') as a response. okHtml :: Text -> IO (Maybe Response)
test/Spec.hs view
@@ -26,17 +26,17 @@ rr :: R rr = R ([], []) p :: [Text] -> Req-p y = (y,[],GET,Just emv)+p y = (defaultRequest,y,[],GET,Just emv) _p :: [Text] -> Req ->  Req-_p y (_,q',m',x') = (y,q',m',x')+_p y (r,_,q',m',x') = (r,y,q',m',x') q :: Query -> Req-q y = ([],y,GET,Just emv)+q y = (defaultRequest,[],y,GET,Just emv) _q :: Query -> Req -> Req-_q y (p',_,m',x') = (p',y,m',x')+_q y (r,p',_,m',x') = (r,p',y,m',x') m :: StdMethod -> Req-m y = ([],[],y,Just emv)+m y = (defaultRequest,[],[],y,Just emv) _m :: StdMethod -> Req -> Req-_m y (p',q',_,x') = (p',q',y,x')+_m y (r,p',q',_,x') = (r,p',q',y,x')   j :: Show a => IO (Maybe (a,b)) -> Expectation@@ -160,6 +160,12 @@     it "should match nested routes" $       do r <- route (R (["a", "b"], [])) [path "a" ==> (\c -> route c [path "b" ==> const (okText "")])]          (responseStatus <$> r) `shouldSatisfy` isJust++  describe "okJson" $ do+    it "should have Content-Type: applcation/json as a header" $+      do maybeResponse <- okJson "{'key': 'value'}"+         let headers = responseHeaders $ fromJust maybeResponse+         headers `shouldBe` [(hContentType, "application/json; charset=utf-8")]    describe "parameter parsing" $     do it "should parse Text" $