packages feed

apiary 2.0.2 → 2.1.0

raw patch · 15 files changed

+646/−76 lines, 15 filesdep +bytestring-lexingdep +ghc-primdep +hetero-dictdep −bytestring-readdep −types-compatdep −web-routingdep ~basedep ~processdep ~template-haskellPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring-lexing, ghc-prim, hetero-dict, primitive

Dependencies removed: bytestring-read, types-compat, web-routing

Dependency ranges changed: base, process, template-haskell, transformers, transformers-base

API changes (from Hackage documentation)

- Data.Apiary.Param: qTypeRep :: Query a => proxy a -> TypeRep
+ Data.Apiary.Param: type family SNext w (k :: Symbol) a (prms :: [KV *]) :: [KV *];
+ Data.Apiary.Param: }
+ Data.Apiary.Routing: (+|) :: MonadPlus m => Path '[] m a -> Router '[] m a -> Router '[] m a
+ Data.Apiary.Routing: action :: Maybe Method -> (Dict d -> m a) -> Path d m a
+ Data.Apiary.Routing: any :: Monad m => Path d m a -> Path d m a
+ Data.Apiary.Routing: data Path d m a
+ Data.Apiary.Routing: data Router d m a
+ Data.Apiary.Routing: empty :: MonadPlus m => Router '[] m a
+ Data.Apiary.Routing: exact :: Text -> Path d m a -> Path d m a
+ Data.Apiary.Routing: execute :: MonadPlus m => Router '[] m a -> Method -> [Text] -> m a
+ Data.Apiary.Routing: fetch :: (MonadPlus m, KnownSymbol k, k </ d) => proxy k -> (Text -> Maybe v) -> Path ((k := v) : d) m a -> Path d m a
+ Data.Apiary.Routing: getMethod :: Path d m a -> Maybe Method
+ Data.Apiary.Routing: infixr 9 +|
+ Data.Apiary.Routing: insert :: MonadPlus m => Path '[] m a -> Router '[] m a -> Router '[] m a
+ Data.Apiary.Routing: instance GHC.Show.Show (Data.Apiary.Routing.Path d m a)
+ Data.Apiary.Routing: raw :: String -> Raw m d d' -> Path d' m a -> Path d m a
+ Data.Apiary.Routing: rest :: (KnownSymbol k, Monad m, k </ d) => proxy k -> Path ((k := [Text]) : d) m a -> Path d m a
+ Data.Apiary.Routing: root :: Path '[] m a -> Path '[] m a
+ Data.Apiary.Routing: showPath :: Path d m a -> String
+ Data.Apiary.Routing: type Method = ByteString
+ Data.Apiary.Routing: type Raw m d d' = Store d input dictionary -> [Text] input path information -> m (Store d', [Text]) output dictionary and path information
+ Data.Apiary.Routing.Dict: (:=) :: Symbol -> v -> KV v
+ Data.Apiary.Routing.Dict: AlreadyHasKey :: Symbol -> AddResult
+ Data.Apiary.Routing.Dict: Dictionary :: AddResult
+ Data.Apiary.Routing.Dict: Key :: Symbol -> GetResult
+ Data.Apiary.Routing.Dict: NotInDicrionary :: Nat -> GetResult
+ Data.Apiary.Routing.Dict: add :: (k </ kvs) => proxy k -> v -> Store kvs -> Store ((k := v) : kvs)
+ Data.Apiary.Routing.Dict: class Member (k :: Symbol) (v :: *) (kvs :: [KV *]) | k kvs -> v
+ Data.Apiary.Routing.Dict: class ShowDict (kvs :: [KV *])
+ Data.Apiary.Routing.Dict: data AddResult
+ Data.Apiary.Routing.Dict: data Dict (kvs :: [KV *])
+ Data.Apiary.Routing.Dict: data GetResult
+ Data.Apiary.Routing.Dict: data KV v
+ Data.Apiary.Routing.Dict: data Store kvs
+ Data.Apiary.Routing.Dict: emptyDict :: Dict '[]
+ Data.Apiary.Routing.Dict: emptyStore :: Store '[]
+ Data.Apiary.Routing.Dict: get :: Member k v kvs => proxy k -> Dict kvs -> v
+ Data.Apiary.Routing.Dict: mkDict :: Store kvs -> Dict kvs
+ Data.Apiary.Routing.Dict: type (</) k v = HasKey k v ~ AlreadyHasKey k
- Control.Monad.Apiary.Filter: (=?!:) :: (HasDesc query, MonadIO actM, Show v, ReqParam v, KnownSymbol k, k </ prms) => query k -> v -> Filter exts actM m prms ((k := v) : prms)
+ Control.Monad.Apiary.Filter: (=?!:) :: forall query k v exts prms actM m. (HasDesc query, MonadIO actM, Show v, ReqParam v, KnownSymbol k, k </ prms) => query k -> v -> Filter exts actM m prms ((k := v) : prms)
- Control.Monad.Apiary.Filter: query :: (k </ prms, MonadIO actM, KnownSymbol k, ReqParam v, HasDesc query, Strategy strategy) => query k -> strategy v -> Filter exts actM m prms (SNext strategy k v prms)
+ Control.Monad.Apiary.Filter: query :: forall query strategy k v exts prms actM m. (k </ prms, MonadIO actM, KnownSymbol k, ReqParam v, HasDesc query, Strategy strategy) => query k -> strategy v -> Filter exts actM m prms (SNext strategy k v prms)
- Control.Monad.Apiary.Filter.Capture: fetch :: (k </ prms, KnownSymbol k, Path p, Monad actM) => proxy (k := p) -> Maybe Html -> Filter exts actM m prms ((k := p) : prms)
+ Control.Monad.Apiary.Filter.Capture: fetch :: forall proxy k p exts prms actM m. (k </ prms, KnownSymbol k, Path p, Monad actM) => proxy (k := p) -> Maybe Html -> Filter exts actM m prms ((k := p) : prms)
- Data.Apiary.Param: class Query a where queryRep = Strict . qTypeRep
+ Data.Apiary.Param: class Typeable a => Query a where queryRep = Strict . typeRep
- Data.Apiary.Param: class Strategy (w :: * -> *) where type family SNext w (k :: Symbol) a (prms :: [KV *]) :: [KV *]
+ Data.Apiary.Param: class Strategy (w :: * -> *) where type SNext w (k :: Symbol) a (prms :: [KV *]) :: [KV *] where {

Files

CHANGELOG.md view
@@ -1,3 +1,7 @@+# 2.1.0+* GHC 8.0/8.2 compatible+* merge web-routing modules, `Network.Routing.XXX` changed to `Data.Apiary.Routing.XXX`.+ # 2.0.2 * add json to send ToJson instances with auto 'application/json' content type. 
apiary.cabal view
@@ -1,5 +1,5 @@ name:                apiary-version:             2.0.2+version:             2.1.0 synopsis:            Simple and type safe web framework that generate web API documentation. description:   Simple and type safe web framework that can be automatically generate API documentation.@@ -47,7 +47,7 @@ license:             MIT license-file:        LICENSE author:              HirotomoMoriwaki<philopon.dependence@gmail.com>-maintainer:          HirotomoMoriwaki<philopon.dependence@gmail.com>+maintainer:          HirotomoMoriwaki<philopon.dependence@gmail.com>, 韩冬<drkoster@qq.com> Homepage:            https://github.com/philopon/apiary Bug-reports:         https://github.com/philopon/apiary/issues copyright:           (c) 2014-2015 Hirotomo Moriwaki@@ -80,30 +80,36 @@                        Data.Apiary.Document                        Data.Apiary.Document.Html +                       Data.Apiary.Routing+                       Data.Apiary.Routing.Dict+   other-modules:       Web.Apiary.TH                        Data.Apiary.SProxy                        Data.Apiary.Document.Internal                        Data.Apiary.Extension.Internal+                       Data.Apiary.Routing.Dict.Internal                        Control.Monad.Apiary.Internal                        Control.Monad.Apiary.Filter.Internal                        Control.Monad.Apiary.Action.Internal                        Control.Monad.Apiary.Filter.Internal.Capture.TH-  build-depends:       base                 >=4.6   && <4.9-                     , template-haskell     >=2.8   && <2.11+  build-depends:       base                 >=4.7   && <5.0+                     , template-haskell     >=2.8   && <2.13 -                     , transformers         >=0.2   && <0.5-                     , transformers-base    >=0.4   && <0.5+                     , transformers         >=0.2   && <0.6+                     , transformers-base    >=0.4   && <0.6                      , mtl                  >=2.1   && <2.3                      , monad-control        >=0.3   && <1.1                      , exceptions           >=0.6   && <0.9                      , resourcet            >=0.4.6 && <1.2 +                     , ghc-prim+                     , primitive+                      , http-types           >=0.8   && <0.10                      , mime-types           >=0.1   && <0.2                       , text                 >=1.1   && <1.3                      , bytestring           >=0.10  && <0.11-                     , bytestring-read      >=0.2   && <0.4                      , blaze-builder        >=0.3   && <0.5                      , blaze-html           >=0.7   && <0.9                      , blaze-markup         >=0.6   && <0.8@@ -115,15 +121,15 @@                      , unordered-containers >=0.2   && <0.3                      , hashable             >=1.1   && <1.3                      , time                 >=1.4   && <1.6-                     , process              >=1.2   && <1.3+                     , process              >=1.2   && <2.0                      , unix-compat          >=0.4   && <0.5                      , http-date            >=0.0   && <0.1                       , wai                  >=3.0   && <3.3                      , wai-extra            >=3.0.15 && <3.1                      , stringsearch         >=0.3   && <0.4-                     , web-routing          >=0.6   && <0.7-                     , types-compat         >=0.1   && <0.2+                     , bytestring-lexing    >=0.5   && <0.6+                     , hetero-dict          >=0.1.1 && <0.1.2    hs-source-dirs:      src   ghc-options:         -O2 -Wall@@ -135,7 +141,7 @@   other-modules:       Application                      , Method   type:                exitcode-stdio-1.0-  build-depends:       base                      >=4.6   && <4.9+  build-depends:       base                      >=4.6   && <5.0                      , mtl                       >=2.1   && <2.3                      , tasty                     >=0.10  && <0.12                      , tasty-hunit               >=0.9   && <0.10
src/Control/Monad/Apiary/Action/Internal.hs view
@@ -122,7 +122,7 @@ import qualified Network.Wai.Parse as P import Network.Wai.Request (requestSizeCheck, RequestSizeException(..)) -import qualified Network.Routing.Dict as Dict+import qualified Data.Apiary.Routing.Dict as Dict import Data.Apiary.Param(Param, File(..)) import Data.Apiary.SProxy(SProxy(..)) import Data.Apiary.Document(Documents)
src/Control/Monad/Apiary/Filter.hs view
@@ -66,12 +66,12 @@ import qualified Data.Text             as T import qualified Data.CaseInsensitive  as CI import Data.Monoid((<>))-import Data.Proxy.Compat(Proxy(..))-import GHC.TypeLits.Compat(KnownSymbol, Symbol, symbolVal)+import Data.Proxy(Proxy(..))+import GHC.TypeLits(KnownSymbol, Symbol, symbolVal) import Data.Apiary.SProxy(SProxy(..))-import Network.Routing.Dict(type (</), KV((:=)))-import qualified Network.Routing.Dict as Dict-import qualified Network.Routing as R+import Data.Apiary.Routing.Dict(type (</), KV((:=)))+import qualified Data.Apiary.Routing.Dict as Dict+import qualified Data.Apiary.Routing as R  import Data.Apiary.Param     ( ReqParam, StrategyRep(..), QueryRep(NoValue)
src/Control/Monad/Apiary/Filter/Capture.hs view
@@ -15,12 +15,12 @@ import Control.Monad.Apiary.Filter.Internal     (Doc(DocPath, DocFetch, DocAny, DocRest)) -import GHC.TypeLits.Compat(KnownSymbol, symbolVal)-import Data.Proxy.Compat(Proxy(..))+import GHC.TypeLits(KnownSymbol, symbolVal)+import Data.Proxy(Proxy(..)) import Data.Apiary.Param(Path, pathRep, readPathAs)-import Network.Routing.Dict(KV((:=)))-import qualified Network.Routing.Dict as Dict-import qualified Network.Routing as R+import Data.Apiary.Routing.Dict(KV((:=)))+import qualified Data.Apiary.Routing.Dict as Dict+import qualified Data.Apiary.Routing as R  import qualified Data.Text as T import Text.Blaze.Html(Html)
src/Control/Monad/Apiary/Filter/Internal.hs view
@@ -16,10 +16,10 @@ import Control.Monad.Apiary.Internal(Filter, Filter', focus) import Control.Monad.Apiary.Action(getRequest) -import GHC.TypeLits.Compat(KnownSymbol)-import Network.Routing.Dict(type (</), KV((:=)), Store)-import qualified Network.Routing.Dict as Dict-import qualified Network.Routing as R+import GHC.TypeLits(KnownSymbol)+import Data.Apiary.Routing.Dict(type (</), KV((:=)), Store)+import qualified Data.Apiary.Routing.Dict as Dict+import qualified Data.Apiary.Routing as R import Data.Apiary.Document.Internal(Doc(..))  -- | low level filter function.@@ -38,6 +38,6 @@ function' d f = function d $ \c r -> f r >>= \(k, p) -> return $ Dict.add k p c  -- | filter only(not modify arguments).-function_ :: Monad actM => (Doc -> Doc) -> (Wai.Request -> Bool) +function_ :: Monad actM => (Doc -> Doc) -> (Wai.Request -> Bool)           -> Filter' exts actM m function_ d f = function d $ \c r -> if f r then Just c else Nothing
src/Control/Monad/Apiary/Filter/Internal/Capture/TH.hs view
@@ -16,7 +16,7 @@ import Data.String(IsString(..)) import Data.List(isInfixOf) import Data.Apiary.SProxy(SProxy(..))-import Data.Proxy.Compat(Proxy(..))+import Data.Proxy(Proxy(..))  preCap :: String -> [String] preCap ""  = []@@ -41,7 +41,7 @@         (_:'$':b, "]") -> TH.lookupValueName b >>=             maybe (fail $ b ++ " not found.") (\n -> return (t, [|Just $(TH.varE n)|]))         (_:b,     "]") -> return (t, [|Just $(TH.stringE b)|])-        (_, _)         -> fail "capture: syntax error." +        (_, _)         -> fail "capture: syntax error."  mkCap :: [String] -> TH.ExpQ mkCap [] = [|id|]@@ -67,7 +67,7 @@ -- [capture|\/path|] -- first path == "path" -- [capture|\/int\/foo::Int|] -- first path == "int" && get 2nd path as Int. -- [capture|\/bar::Int\/baz::Double|] -- get first path as Int and get 2nd path as Double.--- [capture|/**baz|] -- feed greedy and get all path as [Text] (since 0.17.0). +-- [capture|/**baz|] -- feed greedy and get all path as [Text] (since 0.17.0). -- @ -- -- this QQ can convert pure function easily.@@ -78,7 +78,7 @@ -- @ -- capture :: QuasiQuoter-capture = QuasiQuoter +capture = QuasiQuoter     { quoteExp = mkCap . preCap     , quotePat  = \_ -> error "No quotePat."     , quoteType = \_ -> error "No quoteType."
src/Control/Monad/Apiary/Internal.hs view
@@ -55,7 +55,7 @@     , execActionT, hoistActionT, applyDict, rootPattern     ) -import qualified Network.Routing as R+import qualified Data.Apiary.Routing as R import Data.Apiary.Method(Method, renderMethod) import Data.Apiary.Extension ( Has, MonadExts(..), getExt, noExtension ) import Data.Apiary.Extension.Internal(Initializer(..), allMiddleware, allMiddleware')
src/Data/Apiary/Param.hs view
@@ -62,12 +62,11 @@ import Data.Word(Word, Word8, Word16, Word32, Word64) #endif import Data.Maybe(isJust, catMaybes)-import Network.Routing.Dict(KV((:=)), type (</), Store)-import qualified Network.Routing.Dict as Dict-import Data.Typeable.Compat-    ( Typeable, mkTyConApp, typeRepTyCon, typeOf, TypeRep, typeRep, Proxy(..))+import Data.Apiary.Routing.Dict(KV((:=)), type (</), Store)+import qualified Data.Apiary.Routing.Dict as Dict+import Data.Typeable -import GHC.TypeLits.Compat(Symbol, KnownSymbol)+import GHC.TypeLits(Symbol, KnownSymbol)  import Data.String(IsString) import Data.Time.Calendar(Day, fromGregorian)@@ -81,7 +80,8 @@ import qualified Data.ByteString.Char8 as S import qualified Data.ByteString.Lazy.Char8 as L -import qualified Data.ByteString.Read as SL+import qualified Data.ByteString.Lex.Integral as BSL+import qualified Data.ByteString.Lex.Fractional as BSF  jsToBool :: (IsString a, Eq a) => a -> Bool jsToBool = flip notElem jsFalse@@ -172,15 +172,14 @@  -------------------------------------------------------------------------------- -class Query a where+class Typeable a => Query a where     -- | read query parameter.     readQuery :: Maybe S.ByteString -- ^ value of query parameter. Nothing is key only parameter.               -> Maybe a -- ^ Noting is fail.      -- | pretty query parameter.     queryRep  :: proxy a            -> QueryRep-    queryRep = Strict . qTypeRep-    qTypeRep  :: proxy a            -> TypeRep+    queryRep = Strict . typeRep  readBS :: (S.ByteString -> Maybe (a, S.ByteString))        -> S.ByteString -> Maybe a@@ -189,56 +188,50 @@     _           -> Nothing  readBSInt :: Integral a => S.ByteString -> Maybe a-readBSInt = readBS (SL.signed SL.integral)+readBSInt = readBS (BSL.readSigned BSL.readDecimal)  readBSWord :: Integral a => S.ByteString -> Maybe a-readBSWord = readBS SL.integral+readBSWord = readBS BSL.readDecimal  readBSDouble :: S.ByteString -> Maybe Double-readBSDouble = readBS (SL.signed SL.double)+readBSDouble = readBS (BSF.readSigned BSF.readDecimal)  -- | javascript boolean. -- when \"false\", \"0\", \"-0\", \"\", \"null\", \"undefined\", \"NaN\" then False, else True. since 0.6.0.0. instance Query Bool    where     readQuery (Just b) = Just $ jsToBool b     readQuery Nothing  = Just True-    qTypeRep = typeRep -instance Query Int     where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep-instance Query Int8    where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep-instance Query Int16   where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep-instance Query Int32   where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep-instance Query Int64   where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep-instance Query Integer where readQuery = maybe Nothing readBSInt; qTypeRep = typeRep+instance Query Int     where readQuery = maybe Nothing readBSInt+instance Query Int8    where readQuery = maybe Nothing readBSInt+instance Query Int16   where readQuery = maybe Nothing readBSInt+instance Query Int32   where readQuery = maybe Nothing readBSInt+instance Query Int64   where readQuery = maybe Nothing readBSInt+instance Query Integer where readQuery = maybe Nothing readBSInt -instance Query Word    where readQuery = maybe Nothing readBSWord; qTypeRep = typeRep-instance Query Word8   where readQuery = maybe Nothing readBSWord; qTypeRep = typeRep-instance Query Word16  where readQuery = maybe Nothing readBSWord; qTypeRep = typeRep-instance Query Word32  where readQuery = maybe Nothing readBSWord; qTypeRep = typeRep-instance Query Word64  where readQuery = maybe Nothing readBSWord; qTypeRep = typeRep+instance Query Word    where readQuery = maybe Nothing readBSWord+instance Query Word8   where readQuery = maybe Nothing readBSWord+instance Query Word16  where readQuery = maybe Nothing readBSWord+instance Query Word32  where readQuery = maybe Nothing readBSWord+instance Query Word64  where readQuery = maybe Nothing readBSWord -instance Query Double  where readQuery = maybe Nothing readBSDouble; qTypeRep = typeRep-instance Query Float   where readQuery = maybe Nothing (fmap realToFrac . readBSDouble); qTypeRep = typeRep+instance Query Double  where readQuery = maybe Nothing readBSDouble+instance Query Float   where readQuery = maybe Nothing (fmap realToFrac . readBSDouble)  instance Query T.Text where     readQuery  = fmap $ T.decodeUtf8With lenientDecode-    qTypeRep _ = typeRep (Proxy :: Proxy Text)  instance Query TL.Text where     readQuery  = fmap (TL.decodeUtf8With lenientDecode . L.fromStrict)-    qTypeRep _ = typeRep (Proxy :: Proxy Text)  instance Query S.ByteString where     readQuery  = id-    qTypeRep _ = typeRep (Proxy :: Proxy Text)  instance Query L.ByteString where     readQuery  = fmap L.fromStrict-    qTypeRep _ = typeRep (Proxy :: Proxy Text)  instance Query String where     readQuery  = fmap S.unpack-    qTypeRep _ = typeRep (Proxy :: Proxy Text)  -- | fuzzy date parse. three decimal split by 1 char. -- if year < 100 then + 2000. since 0.16.0.@@ -250,15 +243,14 @@ -- * 14.2.05 instance Query Day where     readQuery = (>>= \s0 -> do-        (y, s1) <- SL.integral s0+        (y, s1) <- BSL.readDecimal s0         when (S.null s1) Nothing-        (m, s2) <- SL.integral $ S.tail s1+        (m, s2) <- BSL.readDecimal $ S.tail s1         when (S.null s2) Nothing-        (d, s3) <- SL.integral $ S.tail s2+        (d, s3) <- BSL.readDecimal $ S.tail s2         unless (S.null s3) Nothing         let y' = if y < 100 then 2000 + y else y         return $ fromGregorian y' m d)-    qTypeRep _ = typeRep (Proxy :: Proxy Day)  -- | fuzzy date parse. three decimal split by 1 char. -- if year < 100 then + 2000. since 0.16.0.@@ -284,15 +276,12 @@ instance Query a => Query (Maybe a) where     readQuery (Just a) = Just `fmap` readQuery (Just a)     readQuery Nothing  = Just Nothing-    queryRep _         = Nullable $ qTypeRep (Proxy :: Proxy a)-    qTypeRep _         = maybeCon `mkTyConApp` [qTypeRep (Proxy :: Proxy a)]-      where maybeCon = typeRepTyCon $ typeOf (Nothing :: Maybe ())+    queryRep _         = Nullable $ typeRep (Proxy :: Proxy a)  -- | always success. for check existence. instance Query () where     readQuery _ = Just ()     queryRep  _ = Check-    qTypeRep  _ = typeOf ()  pBool :: Proxy Bool pBool = Proxy
+ src/Data/Apiary/Routing.hs view
@@ -0,0 +1,306 @@+{-# LANGUAGE FlexibleContexts #-} -- for ghc-7.6+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE Rank2Types #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}++-- | >>> :set -XDataKinds -XPolyKinds -XOverloadedStrings+-- >>> data Proxy s = Proxy+-- >>> import Text.Read(readMaybe)+-- >>> import qualified Data.Text as T+--+-- 1. create path+--+-- >>> data Result = A | B T.Text | C | D Int | E T.Text deriving Show+--+-- >>> let key = Proxy :: Proxy "key"+--+-- >>> let a = root $ exact "foo" $ action Nothing (\_ -> Just A)+-- >>> let b = root $ exact "bar" $ fetch key Just $ action Nothing (\d -> Just . B $ D.get key d)+-- >>> let c = root $ exact "bar" $ any $ action (Just "GET") (\_ -> Just C)+-- >>> let d = root $ exact "bar" $ fetch key (\t -> readMaybe (T.unpack t) :: Maybe Int) $ action Nothing (\d -> Just . D $ D.get key d)+-- >>> let e = root $ exact "foo" $ fetch key Just $ exact "qux" $ action (Just "POST") (\d -> Just . E $ D.get key d)+-- >>> a+-- * /foo+-- >>> b+-- * /bar/:key+-- >>> c+-- GET /bar/**+-- >>> d+-- * /bar/:key+-- >>> e+-- POST /foo/:key/qux+--+-- 2. create router+--+-- >>> let r = e +| d +| a +| b +| c +| empty+--+-- 3. execute router+--+-- >>> let run = execute r+--+-- >>> run "GET" ["foo"]+-- Just A+-- >>> run "GET" ["foo", "bar"]+-- Nothing+-- >>> run "GET" ["bar", "12"]+-- Just (D 12)+-- >>> run "GET" ["bar", "baz"]+-- Just (B "baz")+-- >>> run "GET" ["bar", "baz", "qux"]+-- Just C+-- >>> run "POST" ["bar", "baz", "qux"]+-- Nothing+-- >>> run "POST" ["foo", "bar", "baz"]+-- Nothing+-- >>> run "POST" ["foo", "bar", "qux"]+-- Just (E "bar")++module Data.Apiary.Routing+    ( Method+      -- * Path+    , Path+    , showPath, getMethod+    , root+      -- ** children+    , exact+      -- ** action+    , action+      -- ** get parameter+    , Raw+    , raw+    , fetch+    , any+    , rest++    -- * Router+    , Router+    , empty+    , insert, (+|)++    -- * execute+    , execute++    -- * reexport+    -- | 'Store', '</', 'add'+    --+    -- 'Dict', 'Member', 'get'+    --+    -- 'KV'((':=')), 'Members'+    , module Data.Apiary.Routing.Dict+    ) where++import Prelude hiding(any)+import Control.Monad(MonadPlus(..))++import GHC.TypeLits(KnownSymbol, symbolVal)+import qualified Data.Apiary.Routing.Dict.Internal as D+import qualified Data.Text as T+import qualified Data.HashMap.Strict as H+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as SC++import Data.Apiary.Routing.Dict+    ( Store+    , type (</)+    , add++    , Dict+    , Member+    , get++    , KV(..)++    , Members+    )++type Method = S.ByteString++data Params d m a where+    PCons :: !(D.Store d -> [T.Text] -> m (D.Store d', [T.Text]))+          -> !(Router d' m a)+          -> !(Params d m a) -> Params d m a+    PNil  :: Params d m a++-- | routing path+data Path d m a where+    Exact :: !T.Text -> !(Path d m a) -> Path d m a++    Param :: !String -> (D.Store d -> [T.Text] -> m (D.Store d', [T.Text]))+          -> !(Path d' m a) -> Path d m a++    Action :: !(Maybe Method)+           -> (D.Dict d -> m a) -> Path d m a++-- | root+--+-- @+-- root == id+-- @+root :: Path '[] m a -> Path '[] m a+root = id+{-# INLINABLE root #-}++-- | exact matching path+exact :: T.Text -> Path d m a -> Path d m a+exact = Exact+{-# INLINABLE exact #-}++type Raw m d d'+    = D.Store d -- ^ input dictionary+    -> [T.Text] -- ^ input path information+    -> m (D.Store d', [T.Text]) -- ^ output dictionary and path information++-- | raw get parameter function+--+-- if you want matching exact path, use 'exact' for performance+raw :: String -- ^ pretty print+    -> Raw m d d'+    -> Path d' m a -> Path d m a+raw = Param+{-# INLINABLE raw #-}++-- ^ get one directory as parameter.+fetch :: (MonadPlus m, KnownSymbol k, k D.</ d)+      => proxy k -- ^ dictionary key+      -> (T.Text -> Maybe v) -- ^ reading function+      -> Path (k 'D.:= v ': d) m a -> Path d m a+fetch p f = Param (':' : symbolVal p) go+  where+    go _ [] = mzero+    go d (t:ts) = case f t of+        Nothing -> mzero+        Just v  -> return (D.add p v d, ts)+{-# INLINABLE fetch #-}++-- | drop any pathes+any :: Monad m => Path d m a -> Path d m a+any = Param "**" go+  where+    go d _ = return (d, [])+{-# INLINABLE any #-}++-- | take any pathes as [Text]+rest :: (KnownSymbol k, Monad m, k D.</ d) => proxy k -- ^ dictionary key+     -> Path (k 'D.:= [T.Text] ': d) m a -> Path d m a+rest k = Param (':': symbolVal k ++ "**") go+  where+    go d r = return (D.add k r d, [])+{-# INLINABLE rest #-}++-- | action+action :: Maybe Method -- ^ if Nothing, any method allowed+       -> (D.Dict d -> m a) -- ^ action when route matching+       -> Path d m a+action  = Action+{-# INLINABLE action #-}++instance Show (Path d m a) where+    show p = maybe "*" SC.unpack (getMethod p) ++ ' ': showPath p++-- | show path. since v0.6.0.+showPath :: Path d m a -> String+showPath = go id+  where+    go :: (String -> String) -> Path d m a -> String+    go s (Exact t   ps) = go (s . (++) ('/' : T.unpack t)) ps+    go s (Param l _ ps) = go (s . (++) ('/' : l)) ps+    go s (Action _ _)   = s []++-- | get method. since v0.6.0.+getMethod :: Path d m a -> Maybe Method+getMethod = go+  where+    go :: Path d m a -> Maybe Method+    go (Action m _)   = m+    go (Exact _   ps) = go ps+    go (Param _ _ ps) = go ps++-- | router+data Router d m a where+    Router ::+        { params    :: !(Params d m a)+        , children  :: !(H.HashMap T.Text (Router d m a))+        , methods   :: !(H.HashMap Method (D.Dict d -> m a))+        , anyMethod :: D.Dict d -> m a+        } -> Router d m a++emptyRouter :: MonadPlus m => Router d m a+emptyRouter = Router { params    = PNil+                     , children  = H.empty+                     , methods   = H.empty+                     , anyMethod = const mzero+                     }+{-# INLINABLE emptyRouter #-}++-- | empty router+empty :: MonadPlus m => Router '[] m a+empty = emptyRouter+{-# INLINABLE empty #-}++insert' :: MonadPlus m => Path d m a -> Router d m a -> Router d m a+insert' (Exact p n) r =+    let c = H.lookupDefault emptyRouter p (children r)+    in r { children = H.insert p (insert' n c) (children r) }++insert' (Param _ f n) Router{..} = Router+    { params    = PCons f (insert' n emptyRouter) params+    , children  = children+    , methods   = methods+    , anyMethod = anyMethod+    }++insert' (Action (Just m) n) r =+    let c = case H.lookup m (methods r) of+            Nothing -> \d -> n d+            Just p  -> \d -> p d `mplus` n d+    in r { methods = H.insert m c (methods r) }++insert' (Action Nothing n) r =+    r { anyMethod = \d -> anyMethod r d `mplus` n d }+{-# INLINABLE insert' #-}++-- | insert path to router+insert :: MonadPlus m => Path '[] m a -> Router '[] m a -> Router '[] m a+insert = insert'+{-# INLINABLE insert #-}++-- | infix version of `insert`+(+|) :: MonadPlus m => Path '[] m a -> Router '[] m a -> Router '[] m a+(+|) = insert+{-# INLINABLE (+|) #-}++infixr `insert`+infixr +|++-- | execute router+execute :: MonadPlus m => Router '[] m a -> Method -> [T.Text] -> m a+execute = {-# SCC execute #-} execute' D.emptyStore+{-# INLINABLE execute #-}++execute' :: MonadPlus m => D.Store d -> Router d m a -> Method -> [T.Text] -> m a+execute' d Router{params, methods, anyMethod} m [] = {-# SCC execute' #-} fetching next d m [] params+  where+    next = case H.lookup m methods of+        Nothing -> anyMethod (D.mkDict d)+        Just f  -> f (D.mkDict d)++execute' d Router{params, children} m pps@(p:ps) = {-# SCC execute' #-} case H.lookup p children of+    Nothing -> next+    Just c  -> execute' d c m ps `mplus` next+  where+    next = fetching mzero d m pps params+{-# INLINABLE execute' #-}++fetching :: MonadPlus m => m a -> D.Store d -> Method -> [T.Text] -> Params d m a -> m a+fetching next d m pps = {-# SCC fetching #-} loop+  where+    loop PNil = next+    loop (PCons f r o) =+        do (d', pps') <- f d pps+           execute' d' r m pps'+        `mplus` loop o+{-# INLINABLE fetching #-}
+ src/Data/Apiary/Routing/Dict.hs view
@@ -0,0 +1,41 @@+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE DataKinds #-}++module Data.Apiary.Routing.Dict+    ( -- * store+      Store+    , emptyStore+    , type (</)+    , add+    , mkDict++      -- * dictionary+    , Dict+    , emptyDict+    , Member+    , get++      -- * types+    , ShowDict+    , KV(..)++      -- * convenient+    , Members++      -- * pretty print type errors+    , GetResult(..)+    , AddResult(..)+    ) where++import GHC.Exts(Constraint)+import Data.Apiary.Routing.Dict.Internal++-- | type family to constraint multi kvs.+--+-- > Members ["foo" := Int, "bar" := Double] prms == (Member "foo" Int prms, Member "bar" Double prms)+--+type family   Members (kvs :: [KV *]) (prms :: [KV *]) :: Constraint+type instance Members '[] prms = ()+type instance Members (k ':= v ': kvs) prms = (Member k v prms, Members kvs prms)
+ src/Data/Apiary/Routing/Dict/Internal.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE BangPatterns #-} -- for ghc-7.6+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE CPP #-}++#if __GLASGOW_HASKELL__ < 710+{-# LANGUAGE OverlappingInstances #-}+#endif++module Data.Apiary.Routing.Dict.Internal+    ( Dict, Store+    , ShowDict+    , KV(..)+    , emptyStore+    , emptyDict+    , type (</)+    , add+    , Member+    , get+    , mkDict+    , AddResult(..)+    , GetResult(..)+    ) where++import GHC.Exts(Any)++import Data.Proxy+import GHC.TypeLits+import Data.Typeable(typeOf, Typeable, TypeRep)+import Data.List(intercalate)+import Unsafe.Coerce+import qualified Control.Monad.Primitive as P+import qualified Data.Primitive.Array as P+import Control.Monad.ST (ST, runST)++unsafeToAny :: a -> Any+unsafeToAny = unsafeCoerce+{-# INLINABLE unsafeToAny #-}++unsafeFromAny :: Any -> a+unsafeFromAny = unsafeCoerce+{-# INLINABLE unsafeFromAny #-}++-- | (kind) key-value pair+data KV v = Symbol := v++-- | data store to construct dictionary.+--+-- `add` and `mkDict` operation only allowed.+data KVList (kvs :: [KV *]) where+    Cons  :: v -> KVList kvs -> KVList (k ':= v ': kvs)+    Empty :: KVList '[]++instance ShowDict kvs => Show (Store kvs) where+    show d = "Store {" +++        (intercalate ", " . map (\(k, v, t) -> k ++ " = " ++ v ++ " :: " ++ show t) $ showDict 0 (mkDict d))+        ++ "}"++data Store kvs = Store+    { storeSize :: {-# UNPACK #-} !Int+    , storeBody :: KVList kvs+    }++emptyStore :: Store '[]+emptyStore = Store 0 Empty+{-# INLINABLE emptyStore #-}++emptyDict :: Dict '[]+emptyDict = mkDict emptyStore+{-# INLINABLE emptyDict #-}++-- | (kind) pretty print type error of 'add'.+--+-- @+-- > add (Proxy :: Proxy "a") 12 $ add (Proxy :: Proxy "a") "a" emptyStore+-- Couldn't match type ‘'Dictionary’ with ‘'AlreadyHasKey "a"’+-- @++data AddResult+    = AlreadyHasKey Symbol+    | Dictionary++#if __GLASGOW_HASKELL__ > 707+type family HasKey (k :: Symbol) (kvs :: [KV *]) :: AddResult where+  HasKey k '[] = 'AlreadyHasKey k+  HasKey k (k  ':= v ': kvs) = 'Dictionary+  HasKey k (k' ':= v ': kvs) = HasKey k kvs+#else+type family   HasKey (k :: Symbol) (kvs :: [KV *]) :: AddResult+type instance HasKey k kvs = 'AlreadyHasKey k+#endif++-- | 'not elem key' constraint(ghc >= 7.8)+type k </ v = HasKey k v ~ 'AlreadyHasKey k++-- | O(1) add key value pair to dictionary.+--+-- >>> let a = add (Proxy :: Proxy "foo") (12 :: Int) emptyStore+-- >>> a+-- Store {foo = 12 :: Int}+--+-- >>> add (Proxy :: Proxy "bar") "baz" a+-- Store {bar = "baz" :: [Char], foo = 12 :: Int}+add :: (k </ kvs) => proxy k -> v -> Store kvs -> Store (k ':= v ': kvs)+add _ v (Store l c) = Store (l + 1) (Cons v c)+{-# INLINABLE add #-}++-- | heterogeneous dictionary+--+-- 'get' operation only allowed.+newtype Dict (kvs :: [KV *]) = Dict (P.Array Any)++class ShowDict (kvs :: [KV *]) where+    showDict :: Int -> Dict kvs -> [(String, String, TypeRep)]++instance ShowDict '[] where+    showDict _ _ = []++instance (KnownSymbol k, Typeable v, Show v, ShowDict kvs) => ShowDict (k ':= v ': kvs) where+    showDict i (Dict t) =+        (symbolVal (Proxy :: Proxy k), show (unsafeFromAny $ P.indexArray t i :: v), typeOf (undefined :: v)):+        showDict (i + 1) (unsafeCoerce $ Dict t :: Dict kvs)++instance ShowDict kvs => Show (Dict kvs) where+    show d = "Dict {" +++        (intercalate ", " . map (\(k, v, t) -> k ++ " = " ++ v ++ " :: " ++ show t) $ showDict 0 d)+        ++ "}"++mkDict' :: forall s kvs. Store kvs -> ST s (Dict kvs)+mkDict' store = do+    ary <- P.newArray (storeSize store) undefined+    go ary+    Dict `fmap` P.unsafeFreezeArray ary+  where+    go :: P.MutableArray (P.PrimState (ST s)) Any -> ST s ()+    go array = loop 0 (storeBody store)+      where+        loop :: Int -> KVList kvs' -> ST s ()+        loop !i (Cons v ss) = do+            P.writeArray array i (unsafeToAny v)+            loop (i + 1) ss+        loop _ Empty = return ()+{-# INLINABLE mkDict' #-}++-- | O(n) convert "Store" to "Dictionary".+mkDict :: Store kvs -> Dict kvs+mkDict store = runST $ mkDict' store+{-# INLINABLE mkDict #-}++-- | O(1) (>= ghc-7.8), O(n) (< ghc-7.8) get key from dictionary+--+-- >>> let d = mkDict $ add (Proxy :: Proxy "foo") 12 $ add (Proxy :: Proxy "bar") "baz" emptyStore+-- >>> get (Proxy :: Proxy "foo") d+-- 12+-- >>> get (Proxy :: Proxy "bar") d+-- "baz"+get :: Member k v kvs => proxy k -> Dict kvs -> v++-- | (kind) pretty print type error of 'get'+--+--  used only >= ghc-7.8+--+-- @+-- > get (Proxy :: Proxy "b") (mkDict $ add (Proxy :: Proxy "a") "a" emptyStore)+-- Couldn't match type ‘'Key "b"’ with ‘'NotInDicrionary i’+-- @+data GetResult+    = NotInDicrionary Nat+    | Key Symbol++#if __GLASGOW_HASKELL__ > 707++type family Ix' (i :: Nat) (k :: Symbol) (kvs :: [KV *]) :: GetResult where+  Ix' i k '[] = 'Key k+  Ix' i k (k  ':= v ': kvs) = 'NotInDicrionary i+  Ix' i k (k' ':= v ': kvs) = Ix' (i + 1) k kvs++type Ix k kvs = Ix' 0 k kvs++type Index = 'NotInDicrionary++getImpl :: forall i proxy k kvs v. (Index i ~ Ix k kvs, KnownNat i) => proxy (k :: Symbol) -> Dict kvs -> v+getImpl _ (Dict d) = unsafeFromAny $ d `P.indexArray` fromIntegral (natVal (Proxy :: Proxy i))+{-# INLINABLE getImpl #-}++class Member (k :: Symbol) (v :: *) (kvs :: [KV *]) | k kvs -> v where+    get' :: proxy k -> Dict kvs -> v++#if __GLASGOW_HASKELL__ >= 710+instance {-# OVERLAPPING #-} Member k v (k ':= v ': kvs) where+#else+instance Member k v (k ':= v ': kvs) where+#endif+    get' = getImpl+    {-# INLINE get' #-}++instance (Member k v kvs, Index i ~ Ix k (k' ':= v' ': kvs), KnownNat i) => Member k v (k' ':= v' ': kvs) where+    get' = getImpl+    {-# INLINE get' #-}++get = get'+#else+class Member (k :: Symbol) (v :: *) (kvs :: [KV *]) | k kvs -> v where+    get' :: Int -> proxy k -> Dict kvs -> v++instance Member k v (k ':= v ': kvs) where+    get' !i _ (Dict d) = unsafeFromAny $ d `P.indexArray` i+    {-# INLINE get' #-}++instance Member k v kvs => Member k v (k' ':= v' ': kvs) where+    get' !i k d = get' (i + 1) k (unsafeCoerce d :: Dict kvs)+    {-# INLINE get' #-}++get = get' 0+#endif
src/Data/Apiary/SProxy.hs view
@@ -3,6 +3,6 @@  module Data.Apiary.SProxy(SProxy(..)) where -import GHC.TypeLits.Compat(Symbol)+import GHC.TypeLits(Symbol)  data SProxy (k :: Symbol) = SProxy
src/Web/Apiary.hs view
@@ -12,7 +12,7 @@     -- | Has, MonadHas, Extensions, Initializer, Initializer', (+>), noExtension     , module Data.Apiary.Extension     -- | Member, Members, NotMember, Elem((:=))-    , module Network.Routing.Dict+    , module Data.Apiary.Routing.Dict     , key      -- | hiding mkStatus@@ -98,7 +98,7 @@  import Data.Apiary.Method(Method(..)) import Data.Apiary.Extension(Has, MonadExts(..), getExt, Extensions, Initializer, Initializer', (+>), noExtension)-import Network.Routing.Dict (Members, type (</), KV((:=)))+import Data.Apiary.Routing.Dict (Members, type (</), KV((:=)))  import Network.HTTP.Types.Status hiding (mkStatus) import Data.Default.Class(def)
src/Web/Apiary/Heroku.hs view
@@ -42,9 +42,9 @@     ( Has, Extension, Extensions, getExtension, noExtension     , Initializer, Initializer', initializer', (+>)     )-import Data.Proxy.Compat(Proxy(..))+import Data.Proxy(Proxy(..)) -data Heroku = Heroku +data Heroku = Heroku     { herokuEnv    :: IORef (Maybe (H.HashMap T.Text T.Text))     , herokuConfig :: HerokuConfig     }