apiary 0.16.0 → 0.17.0
raw patch · 15 files changed
+297/−73 lines, 15 filesdep +processdep ~textdep ~timePVP ok
version bump matches the API change (PVP)
Dependencies added: process
Dependency ranges changed: text, time
API changes (from Hackage documentation)
- Control.Monad.Apiary: runApiaryTWith :: (Monad actM, Monad m) => Initializer '[] m exts -> ApiaryConfig -> (forall b. actM b -> IO b) -> ApiaryT exts '[] actM m () -> m Application
- Control.Monad.Apiary: runApiaryWith :: Monad m => Initializer '[] m exts -> ApiaryConfig -> ApiaryT exts '[] IO m () -> m Application
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 Check
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 First
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 LimitSome
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 Many
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 One
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 Option
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 Optional
- Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable1 Some
- Data.Apiary.Extension.Internal: instance [overlap ok] Has a ((':) * a as)
- Data.Apiary.Extension.Internal: instance [overlap ok] Has a as => Has a ((':) * a' as)
- Data.Apiary.Proxy: typeRep :: Typeable a => proxy a -> TypeRep
+ Control.Monad.Apiary: runApiaryT :: (Monad actM, Monad m) => (forall b. actM b -> IO b) -> ApiaryConfig -> ApiaryT exts [] actM m () -> EApplication exts m
+ Control.Monad.Apiary: serverWith :: Monad m => Initializer m [] exts -> (Application -> m a) -> (EApplication exts m) -> m a
+ Control.Monad.Apiary: type EApplication e m = Extensions e -> m Application
+ Control.Monad.Apiary.Action: documentGoogleAnalytics :: DefaultDocumentConfig -> Maybe Text
+ Control.Monad.Apiary.Filter: switchQuery :: Monad actM => QueryKey -> ApiaryT exts (Bool : prms) actM m () -> ApiaryT exts prms actM m ()
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable Check
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable First
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable LimitSome
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable Many
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable One
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable Option
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable Optional
+ Control.Monad.Apiary.Filter.Internal.Strategy: instance Typeable Some
+ Data.Apiary.Document.Html: analytics :: Text -> Html
+ Data.Apiary.Document.Html: documentGoogleAnalytics :: DefaultDocumentConfig -> Maybe Text
+ Data.Apiary.Extension: initializer' :: Monad m => m e -> Initializer' m e
+ Data.Apiary.Extension: initializerBracket :: (forall a. Extensions es -> (e -> m a) -> m a) -> Initializer m es (e : es)
+ Data.Apiary.Extension: initializerBracket' :: (forall a. (e -> m a) -> m a) -> Initializer m es (e : es)
+ Data.Apiary.Extension.Internal: instance [overlap ok] Has a (a : as)
+ Data.Apiary.Extension.Internal: instance [overlap ok] Has a as => Has a (a' : as)
+ Data.Apiary.Extension.Internal: instance [overlap ok] Monad m => Category (Initializer m)
+ Data.Apiary.Param: NoValue :: QueryRep
+ Web.Apiary.Heroku: HerokuConfig :: Int -> String -> Maybe String -> HerokuConfig
+ Web.Apiary.Heroku: data Heroku
+ Web.Apiary.Heroku: data HerokuConfig
+ Web.Apiary.Heroku: defaultPort :: HerokuConfig -> Int
+ Web.Apiary.Heroku: getHerokuEnv :: Has Heroku exts => Text -> Extensions exts -> IO (Maybe Text)
+ Web.Apiary.Heroku: getHerokuEnv' :: Text -> Heroku -> IO (Maybe Text)
+ Web.Apiary.Heroku: heroku :: MonadIO m => (Int -> Application -> m a) -> HerokuConfig -> EApplication '[Heroku] m -> m a
+ Web.Apiary.Heroku: herokuAppName :: HerokuConfig -> Maybe String
+ Web.Apiary.Heroku: herokuExecutableName :: HerokuConfig -> String
+ Web.Apiary.Heroku: herokuWith :: MonadIO m => Initializer m '[Heroku] exts -> (Int -> Application -> m a) -> HerokuConfig -> EApplication exts m -> m a
+ Web.Apiary.Heroku: instance Default HerokuConfig
- Control.Monad.Apiary: runApiary :: Monad m => ApiaryConfig -> ApiaryT '[] '[] IO m () -> m Application
+ Control.Monad.Apiary: runApiary :: Monad m => ApiaryConfig -> ApiaryT exts [] IO m () -> EApplication exts m
- Control.Monad.Apiary: server :: Monad m => (Application -> m a) -> m Application -> m a
+ Control.Monad.Apiary: server :: Monad m => (Application -> m a) -> EApplication [] m -> m a
- Control.Monad.Apiary.Action: DefaultDocumentConfig :: Text -> Maybe Html -> Bool -> DefaultDocumentConfig
+ Control.Monad.Apiary.Action: DefaultDocumentConfig :: Text -> Maybe Html -> Bool -> Maybe Text -> DefaultDocumentConfig
- Data.Apiary.Document.Html: DefaultDocumentConfig :: Text -> Maybe Html -> Bool -> DefaultDocumentConfig
+ Data.Apiary.Document.Html: DefaultDocumentConfig :: Text -> Maybe Html -> Bool -> Maybe Text -> DefaultDocumentConfig
- Data.Apiary.Extension: (+>) :: Monad m => Initializer i m x -> Initializer x m o -> Initializer i m o
+ Data.Apiary.Extension: (+>) :: Monad m => Initializer m i x -> Initializer m x o -> Initializer m i o
- Data.Apiary.Extension: data Initializer i m o
+ Data.Apiary.Extension: data Initializer m i o
- Data.Apiary.Extension: initializer :: Monad m => m e -> Initializer' m e
+ Data.Apiary.Extension: initializer :: Monad m => (Extensions es -> m e) -> Initializer m es (e : es)
- Data.Apiary.Extension: noExtension :: Monad m => Initializer '[] m '[]
+ Data.Apiary.Extension: noExtension :: Monad m => Initializer m i i
- Data.Apiary.Extension: preAction :: Monad m => m a -> Initializer i m i
+ Data.Apiary.Extension: preAction :: Monad m => m a -> Initializer m i i
- Data.Apiary.Extension: type Initializer' m a = forall i. Initializer i m (a : i)
+ Data.Apiary.Extension: type Initializer' m a = forall i. Initializer m i (a : i)
- Data.Apiary.Extension.Internal: Initializer :: (Extensions i -> m (Extensions o)) -> Initializer i m o
+ Data.Apiary.Extension.Internal: Initializer :: (forall a. Extensions i -> (Extensions o -> m a) -> m a) -> Initializer m i o
- Data.Apiary.Extension.Internal: NoExtension :: Extensions '[]
+ Data.Apiary.Extension.Internal: NoExtension :: Extensions []
- Data.Apiary.Extension.Internal: newtype Initializer i m o
+ Data.Apiary.Extension.Internal: newtype Initializer m i o
- Data.Apiary.Extension.Internal: unInitializer :: Initializer i m o -> Extensions i -> m (Extensions o)
+ Data.Apiary.Extension.Internal: unInitializer :: Initializer m i o -> forall a. Extensions i -> (Extensions o -> m a) -> m a
- Data.Apiary.SList: SNil :: SList '[]
+ Data.Apiary.SList: SNil :: SList []
- Data.Apiary.SList: type Reverse (a :: [*]) = Rev a '[]
+ Data.Apiary.SList: type Reverse (a :: [*]) = Rev a []
Files
- CHANGELOG.md +17/−0
- apiary.cabal +10/−6
- src/Control/Monad/Apiary.hs +2/−3
- src/Control/Monad/Apiary/Filter.hs +15/−4
- src/Control/Monad/Apiary/Filter/Internal/Capture.hs +5/−0
- src/Control/Monad/Apiary/Filter/Internal/Capture/TH.hs +3/−2
- src/Control/Monad/Apiary/Internal.hs +34/−23
- src/Data/Apiary/Document/Html.hs +18/−4
- src/Data/Apiary/Extension.hs +33/−13
- src/Data/Apiary/Extension/Internal.hs +11/−2
- src/Data/Apiary/Param.hs +1/−0
- src/Web/Apiary.hs +4/−4
- src/Web/Apiary/Heroku.hs +110/−0
- src/Web/Apiary/TH.hs +1/−0
- test/Application.hs +33/−12
CHANGELOG.md view
@@ -1,3 +1,20 @@+# 0.17.0+* fix not accept in multi Accept header.+* add greedy path capture('\*\*').+* add switchQuery filter.+* add Heroku module.+* add google analytics support to API documentation.++## Extension API+* change interface.+* fix bug which immediately finalize.+* deprecate preAction.+* Category initializer only ghc-7.8+.++## new extensions+* apiary-mongoDB+* apiary-memcached+ # 0.16.0 * new Extension API. * add middleware function.
apiary.cabal view
@@ -1,5 +1,5 @@ name: apiary-version: 0.16.0+version: 0.17.0 synopsis: Simple and type safe web framework that can be automatically generate API documentation. description: Simple and type safe web framework that can be automatically generate API documentation.@@ -31,15 +31,17 @@ 404 Page Notfound. @ .- * High performance(benchmark: <https://github.com/philopon/apiary-benchmark/tree/v0.16.0>).+ * High performance(benchmark: <https://github.com/philopon/apiary-benchmark/tree/v0.17.0>). . * Nestable route handling(Apiary Monad; capture, method and more.). . * Type safe route filter. .- * Auto generate API documentation(example: <https://github.com/philopon/apiary/blob/v0.16.0/examples/api.hs>, <https://rawgit.com/philopon/apiary/v0.16.0/examples/api.html>).+ * Auto generate API documentation(example: <https://github.com/philopon/apiary/blob/v0.17.0/examples/api.hs>, <https://rawgit.com/philopon/apiary/v0.17.0/examples/api.html>). .- more examples: <https://github.com/philopon/apiary/blob/v0.16.0/examples/>+ more examples: <https://github.com/philopon/apiary/blob/v0.17.0/examples/>+ .+ live demo: <http://best-haskell.herokuapp.com/> (source code: <https://github.com/philopon/best-haskell>) license: MIT license-file: LICENSE@@ -66,6 +68,7 @@ library exposed-modules: Web.Apiary+ Web.Apiary.Heroku Web.Apiary.Wai Control.Monad.Apiary@@ -100,7 +103,7 @@ , http-types >=0.8 && <0.9 , mime-types >=0.1 && <0.2 - , text >=1.1 && <1.2+ , text >=1.1 && <1.3 , bytestring >=0.10 && <0.11 , bytestring-lexing >=0.4 && <0.5 , blaze-builder >=0.3 && <0.4@@ -111,7 +114,8 @@ , data-default-class >=0.0 && <0.1 , unordered-containers >=0.2 && <0.3 , hashable >=1.1 && <1.3- , time >=1.4 && <1.5+ , time >=1.4 && <1.6+ , process >=1.2 && <1.3 if impl(ghc < 7.8) build-depends: tagged >=0.7 && <0.8
src/Control/Monad/Apiary.hs view
@@ -1,7 +1,6 @@ module Control.Monad.Apiary- ( ApiaryT, server- , runApiaryTWith- , runApiaryWith+ ( ApiaryT, EApplication, server, serverWith+ , runApiaryT , runApiary -- * getter , apiaryConfig
src/Control/Monad/Apiary/Filter.hs view
@@ -18,7 +18,6 @@ , http09, http10, http11 -- ** path matcher , root- , anyPath , capture , Capture.path , Capture.endPath@@ -30,6 +29,7 @@ -- *** specified operators , (=:), (=!:), (=?:), (=?!:), (?:), (=*:), (=+:) , hasQuery+ , switchQuery -- ** header matcher , hasHeader@@ -44,7 +44,7 @@ -- * deprecated , stdMethod-+ , anyPath ) where import Network.Wai as Wai@@ -68,7 +68,9 @@ import qualified Data.Text.Encoding as T import Data.Monoid import Data.Proxy+import Data.Apiary.SList import Data.String+import Data.Maybe import Data.Apiary.Param import Data.Apiary.Document@@ -112,9 +114,10 @@ root :: (Monad m, Monad actM) => ApiaryT exts prms actM m () -> ApiaryT exts prms actM m () root = focus' DocRoot Nothing (RootPath:) return +{-# DEPRECATED anyPath "use greedy filter [capture|/**|] or use restPath." #-} -- | match all subsequent path. since 0.15.0. anyPath :: (Monad m, Monad actM) => ApiaryT exts prms actM m () -> ApiaryT exts prms actM m ()-anyPath = focus' id Nothing (AnyPath:) return+anyPath = focus' id Nothing (RestPath:) return -------------------------------------------------------------------------------- @@ -239,6 +242,14 @@ hasQuery :: MonadIO actM => QueryKey -> ApiaryT exts prms actM m () -> ApiaryT exts prms actM m () hasQuery q = query q (Strategy.Check :: Strategy.Check ()) +-- | get existance of key only query parameter. since v0.17.0.+switchQuery :: Monad actM => QueryKey -> ApiaryT exts (Bool ': prms) actM m () -> ApiaryT exts prms actM m ()+switchQuery QueryKey{..} = focus doc $ \l -> do+ r <- getRequest+ return $ (not . null $ filter (\(k,v) -> isNothing v && queryKey == k) (queryString r)) ::: l+ where+ doc = DocQuery queryKey (StrategyRep "Switch") NoValue queryDesc+ -------------------------------------------------------------------------------- -- | check whether to exists specified header or not. since 0.6.0.0.@@ -284,6 +295,6 @@ accept ect = focus (DocPrecondition $ "Accept: " <> toHtml (T.decodeUtf8 ect)) $ \c -> (lookup "Accept" . requestHeaders <$> getRequest) >>= \case Nothing -> mzero- Just ct -> if ect == fst (parseContentType ct)+ Just ac -> if parseContentType ect `elem` map (parseContentType . SC.dropWhile (== ' ')) (SC.split ',' ac) then contentType ect >> return c else mzero
src/Control/Monad/Apiary/Filter/Internal/Capture.hs view
@@ -41,3 +41,8 @@ f:fs -> case readPathAs p f of Just r -> (r ::: l) <$ modifyState (\s -> s {actionFetches = fs}) Nothing -> mzero++restPath :: (Monad m, Monad actM) => ApiaryT exts ([T.Text] ': prms) actM m () -> ApiaryT exts prms actM m ()+restPath = focus' id Nothing (RestPath:) $ \l -> liftM actionFetches getState >>= \case+ [] -> return $ [] ::: l+ fs -> fs ::: l <$ modifyState (\s -> s {actionFetches = []})
src/Control/Monad/Apiary/Filter/Internal/Capture/TH.hs view
@@ -50,8 +50,8 @@ S h -> [|Just $(stringE h)|] N n -> [|Just $(varE n)|] [|Capture.fetch (Proxy :: Proxy $(conT ty)) $d . $(mkCap as)|]-mkCap (eq:as) = do- [|(Capture.path (fromString $(stringE eq))) . $(mkCap as) |]+mkCap ("**":as) = [|Capture.restPath . $(mkCap as) |]+mkCap (eq:as) = [|(Capture.path (fromString $(stringE eq))) . $(mkCap as) |] -- | capture QuasiQuoter. since 0.1.0.0. --@@ -61,6 +61,7 @@ -- [capture|\/path|] -- first path == "path" -- [capture|\/int\/:Int|] -- first path == "int" && get 2nd path as Int. -- [capture|\/:Int\/:Double|] -- get first path as Int and get 2nd path as Double.+-- [capture|/**|] -- feed greedy and get all path as [Text] (since 0.17.0). -- @ -- capture :: QuasiQuoter
src/Control/Monad/Apiary/Internal.hs view
@@ -40,7 +40,7 @@ data Router exts actM = Router { children :: H.HashMap T.Text (Router exts actM) , capturing :: Maybe (Router exts actM)- , anyMatch :: Maybe (PathMethod exts actM)+ , restMatch :: Maybe (PathMethod exts actM) , pathMethod :: PathMethod exts actM } @@ -69,7 +69,7 @@ FetchPath -> Router cln (Just $ loop ps (maybe emptyRouter id cap)) anp pm Exact p -> Router (adjust' (loop ps) p cln) cap anp pm EndPath -> loop ps rtr- AnyPath -> Router cln cap (Just . insPathMethod $ maybe emptyPathMethod id anp) pm+ RestPath -> Router cln cap (Just . insPathMethod $ maybe emptyPathMethod id anp) pm RootPath -> let cln' = foldl' (flip $ adjust' (loop [EndPath])) cln rootPat in loop [EndPath] $ Router cln' cap anp pm @@ -83,7 +83,7 @@ | FetchPath | RootPath | EndPath- | AnyPath+ | RestPath data ApiaryEnv exts prms actM = ApiaryEnv { envFilter :: ActionT exts actM (SList prms)@@ -94,8 +94,8 @@ , envExts :: Extensions exts } -initialEnv :: Monad actM => Extensions exts -> ApiaryConfig -> ApiaryEnv exts '[] actM-initialEnv exts conf = ApiaryEnv (return SNil) Nothing id conf id exts+initialEnv :: Monad actM => ApiaryConfig -> Extensions exts -> ApiaryEnv exts '[] actM+initialEnv conf = ApiaryEnv (return SNil) Nothing id conf id data ApiaryWriter exts actM = ApiaryWriter { writerRouter :: Router exts actM -> Router exts actM@@ -114,10 +114,11 @@ -> (a -> ApiaryWriter exts actM -> m b) -> m b }+ apiaryT :: Monad m => (ApiaryEnv exts prms actM -> m (a, ApiaryWriter exts actM)) -> ApiaryT exts prms actM m a-apiaryT f = ApiaryT $ \rdr cont -> f rdr >>= \(a,w) -> cont a w+apiaryT f = ApiaryT $ \rdr cont -> f rdr >>= \(a, w) -> cont a w routerToAction :: Monad actM => Router exts actM -> ActionT exts actM () routerToAction router = getRequest >>= go@@ -138,30 +139,40 @@ Nothing -> cld ana Just cp -> cld $ loop (fch . (p:)) cp ps `mplus` ana where- ana = maybe mzero (pmAction mzero) anp+ ana = do+ modifyState (\s -> s {actionFetches = fch $ p:ps} ) + maybe mzero (pmAction mzero) anp cld nxt = case H.lookup p c of Nothing -> nxt Just cd -> loop fch cd ps `mplus` nxt -runApiaryTWith :: (Monad actM, Monad m) => Initializer '[] m exts- -> ApiaryConfig -> (forall b. actM b -> IO b) -> ApiaryT exts '[] actM m () -> m Application-runApiaryTWith (Initializer ir) conf run m = do- exts <- ir NoExtension- wtr <- unApiaryT m (initialEnv exts conf) (\_ w -> return w)+type EApplication e m = Extensions e -> m Application++runApiaryT :: (Monad actM, Monad m)+ => (forall b. actM b -> IO b)+ -> ApiaryConfig+ -> ApiaryT exts '[] actM m ()+ -> EApplication exts m+runApiaryT runAct conf m exts = do+ wtr <- unApiaryT m (initialEnv conf exts) (\_ w -> return w) let doc = docsToDocuments $ writerDoc wtr [] rtr = writerRouter wtr emptyRouter mw = writerMw wtr- return $! mw $ execActionT conf exts doc (hoistActionT run $ routerToAction rtr)+ return $! mw $ execActionT conf exts doc (hoistActionT runAct $ routerToAction rtr) -runApiaryWith :: Monad m => Initializer '[] m exts- -> ApiaryConfig -> ApiaryT exts '[] IO m () -> m Application-runApiaryWith ef conf m = runApiaryTWith ef conf id m+runApiary :: Monad m+ => ApiaryConfig+ -> ApiaryT exts '[] IO m ()+ -> EApplication exts m+runApiary = runApiaryT id -runApiary :: Monad m => ApiaryConfig -> ApiaryT '[] '[] IO m () -> m Application-runApiary = runApiaryWith noExtension+server :: Monad m => (Application -> m a) -> EApplication '[] m -> m a+server = serverWith noExtension -server :: Monad m => (Application -> m a) -> m Application -> m a-server = (=<<)+serverWith :: Monad m => Initializer m '[] exts + -> (Application -> m a) -> (EApplication exts m) -> m a+serverWith (Initializer ir) run em = ir NoExtension $ \exts ->+ em exts >>= run -------------------------------------------------------------------------------- @@ -211,11 +222,11 @@ getApiaryEnv :: Monad actM => ApiaryT exts prms actM m (ApiaryEnv exts prms actM) getApiaryEnv = ApiaryT $ \env cont -> cont env mempty +apiaryExt :: (Has e exts, Monad actM) => proxy e -> ApiaryT exts prms actM m e+apiaryExt p = getExtension p . envExts <$> getApiaryEnv+ apiaryConfig :: Monad actM => ApiaryT exts prms actM m ApiaryConfig apiaryConfig = liftM envConfig getApiaryEnv--apiaryExt :: (Has e exts, Monad actM) => proxy e -> ApiaryT exts prms actM m e-apiaryExt p = liftM (getExtension p . envExts) getApiaryEnv addRoute :: Monad actM => ApiaryWriter exts actM -> ApiaryT exts prms actM m () addRoute r = ApiaryT $ \_ cont -> cont () r
src/Data/Apiary/Document/Html.hs view
@@ -44,13 +44,25 @@ ) data DefaultDocumentConfig = DefaultDocumentConfig- { documentTitle :: T.Text- , documentDescription :: Maybe Html- , documentUseCDN :: Bool+ { documentTitle :: T.Text+ , documentDescription :: Maybe Html+ , documentUseCDN :: Bool+ , documentGoogleAnalytics :: Maybe T.Text -- ^ google analytics. since 0.17.0. } +analytics :: T.Text -> Html+analytics code = H.script . H.preEscapedToHtml . T.concat $+ [ "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){"+ , "(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),"+ , "m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)"+ , "})(window,document,'script','//www.google-analytics.com/analytics.js','ga');"+ , "ga('create', '", code, "', 'auto');"+ , "ga('send', 'pageview');"+ ]++ instance Default DefaultDocumentConfig where- def = DefaultDocumentConfig "API documentation" Nothing True+ def = DefaultDocumentConfig "API documentation" Nothing True Nothing defaultDocumentToHtml :: DefaultDocumentConfig -> Documents -> Html defaultDocumentToHtml DefaultDocumentConfig{..} docs =@@ -84,11 +96,13 @@ , $(runIO (readFile "static/jquery.cookie-1.4.1.min.js") >>= \c -> [|H.script $ preEscapedToHtml (c::String)|]) , $(runIO (readFile "static/api-documentation.min.js") >>= \c -> [|H.script $ preEscapedToHtml (c::String)|]) , $(runIO (readFile "static/api-documentation.min.css") >>= \c -> [|H.style $ preEscapedToHtml (c::String)|])+ , maybe mempty analytics documentGoogleAnalytics ] htmlQR (Strict r) = toHtml (show r) htmlQR (Nullable r) = H.span (toHtml (show r) <> "?") ! A.title (toValue (show r) <> "(nullable)") htmlQR Check = "check"+ htmlQR NoValue = "-" noDesc = H.span "no description" ! A.class_ "no-description"
src/Data/Apiary/Extension.hs view
@@ -7,28 +7,48 @@ ( Has(getExtension) , Extensions , addExtension- , Initializer, Initializer'- , initializer, preAction, (+>) , noExtension+ -- * create initializer+ , Initializer, initializer+ , Initializer', initializer'+ , initializerBracket+ , initializerBracket'++ -- * combine initializer+ , (+>)+ -- * deprecated+ , preAction ) where import Data.Apiary.Extension.Internal -type Initializer' m a = forall i. Initializer i m (a ': i)+type Initializer' m a = forall i. Initializer m i (a ': i) addExtension :: e -> Extensions es -> Extensions (e ': es) addExtension = AddExtension -initializer :: Monad m => m e -> Initializer' m e-initializer m = Initializer $ \e -> do- a <- m- return (addExtension a e)+initializer :: Monad m => (Extensions es -> m e) -> Initializer m es (e ': es)+initializer m = Initializer $ \es n -> do+ e <- m es+ n (addExtension e es) -preAction :: Monad m => m a -> Initializer i m i-preAction f = Initializer $ \e -> f >> return e+initializer' :: Monad m => m e -> Initializer' m e+initializer' m = initializer (const m) -(+>) :: Monad m => Initializer i m x -> Initializer x m o -> Initializer i m o-Initializer a +> Initializer b = Initializer $ \e -> a e >>= b+initializerBracket :: (forall a. Extensions es -> (e -> m a) -> m a) -> Initializer m es (e ': es)+initializerBracket b = Initializer $ \es n ->+ b es $ \e -> n (addExtension e es) -noExtension :: Monad m => Initializer '[] m '[]-noExtension = Initializer $ return+initializerBracket' :: (forall a. (e -> m a) -> m a) -> Initializer m es (e ': es)+initializerBracket' m = initializerBracket (const m)++{-# DEPRECATED preAction "DEPRECATED" #-}+preAction :: Monad m => m a -> Initializer m i i+preAction f = Initializer $ \es n -> f >> n es++-- | combine two Initializer. since 0.16.0.+(+>) :: Monad m => Initializer m i x -> Initializer m x o -> Initializer m i o+Initializer a +> Initializer b = Initializer $ \e m -> a e (\e' -> b e' m)++noExtension :: Monad m => Initializer m i i+noExtension = Initializer $ \es n -> n es
src/Data/Apiary/Extension/Internal.hs view
@@ -5,11 +5,12 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE OverlappingInstances #-} {-# LANGUAGE TypeOperators #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE CPP #-} module Data.Apiary.Extension.Internal where -newtype Initializer i m o = Initializer - {unInitializer :: Extensions i -> m (Extensions o)}+import Control.Category data Extensions (es :: [*]) where NoExtension :: Extensions '[]@@ -24,3 +25,11 @@ instance Has a as => Has a (a' ': as) where getExtension p (AddExtension _ as) = getExtension p as +newtype Initializer m i o = Initializer + {unInitializer :: forall a. Extensions i -> (Extensions o -> m a) -> m a}++#if __GLASGOW_HASKELL__ >= 708+instance Monad m => Category (Initializer m) where+ id = Initializer $ \es m -> m es+ Initializer a . Initializer b = Initializer $ \e m -> b e (\e' -> a e' m)+#endif
src/Data/Apiary/Param.hs view
@@ -64,6 +64,7 @@ = Strict TypeRep | Nullable TypeRep | Check+ | NoValue deriving (Show, Eq) class Path a where
src/Web/Apiary.hs view
@@ -7,7 +7,7 @@ , module Control.Monad.Apiary.Filter.Internal.Strategy -- | Method(..) , module Data.Apiary.Method- -- | (+>)+ -- | Has, Extensions, Initializer, Initializer', (+>) , module Data.Apiary.Extension , act @@ -19,14 +19,14 @@ , module Control.Monad.IO.Class -- | MonadPlus(..), msum, mfilter, guard, (>=>) , module Control.Monad- -- | FilePart(..)+ -- | FilePart(..), Application , module Network.Wai -- | Html , module Text.Blaze.Html ) where import Web.Apiary.TH-import Network.Wai(FilePart(..))+import Network.Wai(FilePart(..), Application) import Network.HTTP.Types.Status hiding (mkStatus) import Control.Monad.Apiary@@ -38,6 +38,6 @@ import Data.Default.Class(def) import Data.Apiary.Param-import Data.Apiary.Extension((+>))+import Data.Apiary.Extension(Has, Extensions, Initializer, Initializer', (+>)) import Data.Apiary.Method(Method(..)) import Text.Blaze.Html(Html)
+ src/Web/Apiary/Heroku.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE FlexibleContexts #-}++module Web.Apiary.Heroku+ ( Heroku, HerokuConfig(..)+ , heroku, herokuWith+ , getHerokuEnv, getHerokuEnv'+ ) where++import System.Environment+import System.Process+import System.Exit++import Control.Exception+import Control.Arrow hiding (app)+import Control.Applicative+import Control.Monad.Trans++import Data.IORef+import Data.Proxy+import Data.Default.Class+import qualified Data.HashMap.Strict as H+import qualified Data.Text as T+import qualified Data.Text.IO as T++import Network.Wai+import Control.Monad.Apiary+import Data.Apiary.Extension+import Data.Apiary.Extension.Internal++data Heroku = Heroku + { herokuEnv :: IORef (Maybe (H.HashMap T.Text T.Text))+ , herokuConfig :: HerokuConfig+ }++data HerokuConfig = HerokuConfig+ { defaultPort :: Int+ , herokuExecutableName :: String+ , herokuAppName :: Maybe String+ }++instance Default HerokuConfig where+ def = HerokuConfig 3000 "heroku" Nothing++initHeroku :: MonadIO m => HerokuConfig -> Initializer' m Heroku+initHeroku conf = initializer' . liftIO $+ Heroku <$> newIORef Nothing <*> pure conf++-- | use this function instead of serverWith in heroku app. since 0.17.0.+--+-- @ serverWith exts (run 3000) . runApiary def $ foo @+--+-- to+--+-- @ herokuWith exts run def . runApiary def $ foo @+--+herokuWith :: MonadIO m => Initializer m '[Heroku] exts+ -> (Int -> Application -> m a)+ -> HerokuConfig -> EApplication exts m -> m a+herokuWith ir run conf eapp = ir' NoExtension $ \exts -> do+ port <- liftIO $ fmap read (getEnv "PORT")+ `catch` (\(_::IOError) -> return $ defaultPort conf)+ app <- eapp exts+ run port app+ where+ Initializer ir' = initHeroku conf +> ir++-- | use this function instead of server in heroku app. since 0.17.0.+--+-- @ server (run 3000) . runApiary def $ foo @+--+-- to+--+-- @ heroku run def . runApiary def $ foo @+--+-- this function provide:+--+-- * set port by PORT environment variable.+-- * getHerokuEnv function(get config from environment variable or @ heroku config @ command).+heroku :: MonadIO m => (Int -> Application -> m a)+ -> HerokuConfig -> EApplication '[Heroku] m -> m a+heroku = herokuWith noExtension++getHerokuEnv' :: T.Text -> Heroku -> IO (Maybe T.Text)+getHerokuEnv' key Heroku{..} = try (getEnv $ T.unpack key) >>= \case+ Right e -> return (Just $ T.pack e)+ Left (_::SomeException) -> readIORef herokuEnv >>= \case+ Just hm -> return $ H.lookup key hm+ Nothing -> do+ let args = ["config", "-s"] +++ maybe [] (\n -> ["--app", n]) (herokuAppName herokuConfig)+ cp = proc (herokuExecutableName herokuConfig) args+ (_, Just hout, _, h) <- createProcess cp {std_out = CreatePipe}+ xc <- waitForProcess h+ if xc == ExitSuccess+ then do+ hm <- H.fromList . map (second T.tail . T.break (== '=')) . T.lines+ <$> T.hGetContents hout+ writeIORef herokuEnv (Just hm)+ return $ H.lookup key hm+ else Nothing <$ writeIORef herokuEnv (Just H.empty)+++getHerokuEnv :: Has Heroku exts => T.Text -> Extensions exts -> IO (Maybe T.Text)+getHerokuEnv key exts = getHerokuEnv' key (getExtension Proxy exts)
src/Web/Apiary/TH.hs view
@@ -65,6 +65,7 @@ n -> fail $ "unknown HTTP status code:" ++ show n +{-# DEPRECATED act "no longer maintained" #-} -- | shortcut action. since 0.6.0.0. -- -- @
test/Application.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# OPTIONS_GHC -fno-warn-missing-signatures #-}+{-# LANGUAGE DataKinds #-} module Application where @@ -50,9 +51,12 @@ assert404 = assertRequest 404 (Just "text/plain") "404 Page Notfound.\n" --------------------------------------------------------------------------------+runApp :: ApiaryT '[] '[] IO Identity () -> Application+runApp = runIdentity . server return . runApiary def+-------------------------------------------------------------------------------- helloWorldApp :: Application-helloWorldApp = runIdentity . runApiary def $ action $ do+helloWorldApp = runApp $ action $ do contentType "text/plain" bytes "hello" @@ -66,7 +70,7 @@ -------------------------------------------------------------------------------- methodFilterApp :: Application-methodFilterApp = runIdentity . runApiary def $ do+methodFilterApp = runApp $ do method "GET" . action $ contentType "text/plain" >> bytes "GET" method POST . action $ contentType "text/plain" >> bytes "POST" @@ -81,7 +85,7 @@ -------------------------------------------------------------------------------- httpVersionApp :: Application-httpVersionApp = runIdentity . runApiary def $ do+httpVersionApp = runApp $ do http09 . action $ contentType "text/plain" >> bytes "09" http10 . action $ contentType "text/plain" >> bytes "10" http11 . action $ contentType "text/plain" >> bytes "11"@@ -96,7 +100,7 @@ -------------------------------------------------------------------------------- rootFilterApp :: Application-rootFilterApp = runIdentity . runApiary def . root . action $ do+rootFilterApp = runApp . root . action $ do contentType "text/html" bytes "root" @@ -110,7 +114,7 @@ -------------------------------------------------------------------------------- anyFilterApp :: Application-anyFilterApp = runIdentity . runApiary def $ [capture|/test|] . anyPath . action $ do+anyFilterApp = runApp $ [capture|/test|] . anyPath . action $ do contentType "text/plain" bytes "hello" @@ -123,8 +127,24 @@ -------------------------------------------------------------------------------- +restFilterApp :: Application+restFilterApp = runApp $ do+ [capture|/test/**|] . action $ \l -> contentType "text/plain" >> showing l+ [capture|/test/neko|] . action $ contentType "text/plain" >> bytes "nyan"++restFilterTest :: Test+restFilterTest = testGroup "rest capture" $ map ($ restFilterApp)+ [ testReq "GET /" . assert404+ , testReq "GET /test" . assertPlain200 "[]"+ , testReq "GET /test/foo" . assertPlain200 "[\"foo\"]"+ , testReq "GET /test/foo/bar" . assertPlain200 "[\"foo\",\"bar\"]"+ , testReq "GET /test/neko" . assertPlain200 "nyan"+ ]++--------------------------------------------------------------------------------+ captureApp :: Application-captureApp = runIdentity . runApiary def $ do+captureApp = runApp $ do [capture|/foo|] . action $ contentType "text/plain" >> bytes "foo" [capture|/:Int|] . method GET . action $ \i -> contentType "text/plain" >> bytes "Int " >> showing i [capture|/:Double|] . action $ \i -> contentType "text/plain" >> bytes "Double " >> showing i@@ -145,19 +165,19 @@ -------------------------------------------------------------------------------- -queryApp f g h = runIdentity . runApiary def $ do+queryApp f g h = runApp $ do _ <- (f "foo" pInt) . action $ \i -> contentType "text/plain" >> bytes "foo Int " >> showing i _ <- (g "foo" pString) . action $ \i -> contentType "text/plain" >> bytes "foo String " >> showing i (h "foo" (pMaybe pString)) . action $ \i -> contentType "text/plain" >> bytes "foo Maybe String " >> showing i queryOptionalApp :: Application-queryOptionalApp = runIdentity . runApiary def $ do+queryOptionalApp = runApp $ do ("foo" =?!: (5 :: Int)) . action $ \i -> contentType "text/plain" >> bytes "foo Int " >> showing i ("foo" =?!: ("bar" :: String)) . action $ \i -> contentType "text/plain" >> bytes "foo String " >> showing i ("foo" =?!: (Just "baz" :: Maybe String)) . action $ \i -> contentType "text/plain" >> bytes "foo Maybe String " >> showing i queryCheckApp :: Application-queryCheckApp = runIdentity . runApiary def $ do+queryCheckApp = runApp $ do ("foo" ?: pInt) . action $ contentType "text/plain" >> bytes "foo Int" ("foo" ?: pString) . action $ contentType "text/plain" >> bytes "foo String" ("foo" ?: pMaybe pString) . action $ contentType "text/plain" >> bytes "foo Maybe String"@@ -253,7 +273,7 @@ -------------------------------------------------------------------------------- stopApp :: Application-stopApp = runIdentity . runApiary def $ do+stopApp = runApp $ do [capture|/a/:Int|] . action $ \i -> do contentType "text/plain" when (i == 1) $ bytes "one\n"@@ -272,7 +292,7 @@ -------------------------------------------------------------------------------- acceptApp :: Application-acceptApp = runIdentity . runApiary def $ [capture|/|] $ do+acceptApp = runApp $ [capture|/|] $ do accept "application/json" . action $ bytes "json" accept "text/html" . action $ bytes "html" action $ bytes "other"@@ -291,7 +311,7 @@ -------------------------------------------------------------------------------- multipleFilter1App :: Application-multipleFilter1App = runIdentity . runApiary def $ do+multipleFilter1App = runApp $ do root $ do method GET . action $ contentType "text/plain" >> bytes "GET /" method POST . action $ contentType "text/html" >> bytes "POST /"@@ -315,6 +335,7 @@ , httpVersionTest , rootFilterTest , anyFilterTest+ , restFilterTest , captureTest , queryTest , stopTest