packages feed

servant-router 0.7.1 → 0.8.0

raw patch · 4 files changed

+166/−8 lines, 4 filesdep +blaze-htmldep +servant-blazedep +servant-serverdep ~basedep ~mtlPVP ok

version bump matches the API change (PVP)

Dependencies added: blaze-html, servant-blaze, servant-server, warp

Dependency ranges changed: base, mtl

API changes (from Hackage documentation)

- Servant.Router: RCapture :: (x -> Router m a) -> Router m a
- Servant.Router: RChoice :: Router m a -> Router m a -> Router m a
- Servant.Router: RPage :: m a -> Router m a
- Servant.Router: RPath :: Proxy sym -> Router m a -> Router m a
- Servant.Router: RQueryFlag :: Proxy sym -> (Bool -> Router m a) -> Router m a
- Servant.Router: RQueryParam :: Proxy sym -> (Maybe x -> Router m a) -> Router m a
- Servant.Router: RQueryParams :: Proxy sym -> ([x] -> Router m a) -> Router m a
- Servant.Router: instance forall (k :: BOX) (path :: GHC.TypeLits.Symbol) (sublayout :: k). (Servant.Router.HasRouter sublayout, GHC.TypeLits.KnownSymbol path) => Servant.Router.HasRouter (path Servant.API.Sub.:> sublayout)
- Servant.Router: instance forall (k :: BOX) (sym :: GHC.TypeLits.Symbol) (sublayout :: k). (Servant.Router.HasRouter sublayout, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryFlag sym Servant.API.Sub.:> sublayout)
- Servant.Router: instance forall (k :: BOX) (sym :: GHC.TypeLits.Symbol) x (sublayout :: k). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x) => Servant.Router.HasRouter (Servant.API.Capture.Capture sym x Servant.API.Sub.:> sublayout)
- Servant.Router: instance forall (k :: BOX) (sym :: GHC.TypeLits.Symbol) x (sublayout :: k). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryParam sym x Servant.API.Sub.:> sublayout)
- Servant.Router: instance forall (k :: BOX) (sym :: GHC.TypeLits.Symbol) x (sublayout :: k). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryParams sym x Servant.API.Sub.:> sublayout)
+ Servant.Router: [RCapture] :: FromHttpApiData x => (x -> Router m a) -> Router m a
+ Servant.Router: [RChoice] :: Router m a -> Router m a -> Router m a
+ Servant.Router: [RPage] :: m a -> Router m a
+ Servant.Router: [RPath] :: KnownSymbol sym => Proxy sym -> Router m a -> Router m a
+ Servant.Router: [RQueryFlag] :: KnownSymbol sym => Proxy sym -> (Bool -> Router m a) -> Router m a
+ Servant.Router: [RQueryParam] :: (FromHttpApiData x, KnownSymbol sym) => Proxy sym -> (Maybe x -> Router m a) -> Router m a
+ Servant.Router: [RQueryParams] :: (FromHttpApiData x, KnownSymbol sym) => Proxy sym -> ([x] -> Router m a) -> Router m a
+ Servant.Router: constHandler :: (HasRouter layout, Monad m) => Proxy layout -> Proxy m -> a -> RouteT layout m a
+ Servant.Router: instance forall k (sublayout :: k) (path :: GHC.Types.Symbol). (Servant.Router.HasRouter sublayout, GHC.TypeLits.KnownSymbol path) => Servant.Router.HasRouter (path Servant.API.Sub.:> sublayout)
+ Servant.Router: instance forall k (sublayout :: k) (sym :: GHC.Types.Symbol). (Servant.Router.HasRouter sublayout, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryFlag sym Servant.API.Sub.:> sublayout)
+ Servant.Router: instance forall k (sublayout :: k) x (sym :: GHC.Types.Symbol). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x) => Servant.Router.HasRouter (Servant.API.Capture.Capture sym x Servant.API.Sub.:> sublayout)
+ Servant.Router: instance forall k (sublayout :: k) x (sym :: GHC.Types.Symbol). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryParam sym x Servant.API.Sub.:> sublayout)
+ Servant.Router: instance forall k (sublayout :: k) x (sym :: GHC.Types.Symbol). (Servant.Router.HasRouter sublayout, Web.HttpApiData.Internal.FromHttpApiData x, GHC.TypeLits.KnownSymbol sym) => Servant.Router.HasRouter (Servant.API.QueryParam.QueryParams sym x Servant.API.Sub.:> sublayout)
+ Servant.Router: routeConst :: (HasRouter layout, Monad m) => Proxy layout -> Proxy m -> a -> Router m a
+ Servant.Router: type family RouteT layout (m :: * -> *) a :: *;
+ Servant.Router: }
- Servant.Router: class HasRouter layout where type family RouteT layout (m :: * -> *) a :: *
+ Servant.Router: class HasRouter layout where type RouteT layout (m :: * -> *) a :: * routeConst l m a = route l m (Proxy :: Proxy a) (constHandler l m a) where {
- Servant.Router: runRoute :: (HasRouter layout, MonadError RoutingError m) => String -> Proxy layout -> RouteT layout m a -> m a
+ Servant.Router: runRoute :: forall layout m a. (HasRouter layout, MonadError RoutingError m) => String -> Proxy layout -> RouteT layout m a -> m a

Files

+ README.md view
@@ -0,0 +1,88 @@+servant-router+---++`servant-router` routes a URI given a Servant API and an appropriate handler.+In web applications,+this is used to make single page applications (SPAs) with front-end routing,+letting you share portions of your Servant APIs between the client and server.++`servant-router` does not depend on `reflex` or any GHCJS packages.+It's intended to be a general purpose URI router on any platform.+Combined with `reflex-dom`, `servant-reflex`, and `reflex-dom-contrib`,+this makes for a very satisfactory front-end Haskell experience.++```haskell+{-# LANGUAGE DataKinds     #-}+{-# LANGUAGE TypeOperators #-}++module Main where++import           Control.Monad.Except+import           Data.Proxy+import           Reflex.Dom+import           Reflex.Dom.Contrib.Router+import           Servant.API+import           Servant.Router++type MyApi = "books" :> Capture "id" Int :> View+        :<|> "search" :> QueryParam "keywords" String :> View+myApi :: Proxy MyApi+myApi = Proxy++main :: IO ()+-- reflex-dom-contrib provides a 'routeSite' function+-- for routing single page applications with reflex.+-- servant-router uses the provided uri String to perform the routing.+main = routeSite $ \uri -> do+  let handler = books :<|> search+      books i = do+        -- Here, you would get and display a book.+        -- Return a Reflex event for changing the browser location.+        return never+      search Nothing = do+        -- Here, you would display a search bar.+        return never+      search (Just keywords) = do+        -- Here you would display the search bar plus results.+        return never+  -- With the handler constructed, run the router with the uri.+  result <- runExceptT $ runRoute uri myApi handler+  case result of+    -- If 'Left', there was no correct route for the uri.+    Left _ -> do+      el "div" $ text "No such page"+      return never+    -- If 'Right', the result of the route is returned.+    Right e -> return e+```++Serving+---++When using `servant-router` on the front-end in Single Page Applications (SPAs),+you still need to serve the application from the back-end.+To share the routing layout between the front-end and back-end,+the `View` endpoints need to be converted to a verb like `Get`.+Plus, with an SPA,+all the end-points should serve the same HTML on the back-end.+To do this, use the `ViewTransform` type family, and `constHandler` function.++```haskell+type Views = "books" :> View+        :<|> "search" :> QueryParam "query" String :> View++-- Equivalent to:+--+--   type ViewsServer = "books" :> Get '[HTML] Blaze.Html+--          :<|> "search" :> QueryParam "query" String :> Get '[HTML] Blaze.Html+type ViewsServer = ViewTransform Views (Get '[HTML] Blaze.Html)++viewsServer :: Server ViewsServer+viewsServer = constHandler+	    (Proxy :: Proxy ViewsServer)+	    (Proxy :: Proxy Handler) $+	    docTypeHtml $ do+	      H.head $ return ()+	      body $+	        script ! src "app.js" $ return ()+```
servant-router.cabal view
@@ -1,5 +1,5 @@ name:                servant-router-version:             0.7.1+version:             0.8.0 synopsis:            Servant router for non-server applications. description:         Write Servant APIs to be routed without a server. homepage:            https://github.com/ElvishJerricco/servant-router@@ -10,7 +10,7 @@ copyright:           2016 Will Fancher category:            Web build-type:          Simple--- extra-source-files:+extra-source-files:  README.md cabal-version:       >=1.10  library@@ -35,7 +35,21 @@                      , servant-router                      , servant                      , mtl-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N+  default-language:    Haskell2010++test-suite server-test+  type:                exitcode-stdio-1.0+  hs-source-dirs:      test+  main-is:             Server.hs+  build-depends:       base+                     , servant-router+                     , servant-server+                     , servant-blaze+                     , mtl+                     , warp+                     , blaze-html+  ghc-options:         -Wall -threaded -rtsopts -with-rtsopts=-N   default-language:    Haskell2010  source-repository head
src/Servant/Router.hs view
@@ -54,16 +54,31 @@   RPath         :: KnownSymbol sym => Proxy sym -> Router m a -> Router m a   RPage         :: m a -> Router m a +-- | Transform a layout by replacing 'View' with another type+type family ViewTransform layout view where+  ViewTransform (a :<|> b) view = ViewTransform a view :<|> ViewTransform b view+  ViewTransform (a :> b) view = a :> ViewTransform b view+  ViewTransform View view = view+ -- | This is similar to the @HasServer@ class from @servant-server@. -- It is the class responsible for making API combinators routable. -- 'RuoteT' is used to build up the handler types. -- 'Router' is returned, to be interpretted by 'routeLoc'. class HasRouter layout where+  -- | A route handler.   type RouteT layout (m :: * -> *) a :: *+  -- | Create a constant route handler that returns @a@+  constHandler :: Monad m => Proxy layout -> Proxy m -> a -> RouteT layout m a+  -- | Transform a route handler into a 'Router'.   route :: Proxy layout -> Proxy m -> Proxy a -> RouteT layout m a -> Router m a+  -- | Create a 'Router' from a constant.+  routeConst :: Monad m => Proxy layout -> Proxy m -> a -> Router m a+  routeConst l m a = route l m (Proxy :: Proxy a) (constHandler l m a)  instance (HasRouter x, HasRouter y) => HasRouter (x :<|> y) where   type RouteT (x :<|> y) m a = RouteT x m a :<|> RouteT y m a+  constHandler _ m a = constHandler (Proxy :: Proxy x) m a+                  :<|> constHandler (Proxy :: Proxy y) m a   route     _     (m :: Proxy m)@@ -72,41 +87,47 @@     = RChoice (route (Proxy :: Proxy x) m a x) (route (Proxy :: Proxy y) m a y)  instance (HasRouter sublayout, FromHttpApiData x)-    => HasRouter (Capture sym x :> sublayout) where+         => HasRouter (Capture sym x :> sublayout) where   type RouteT (Capture sym x :> sublayout) m a = x -> RouteT sublayout m a+  constHandler _ m a _ = constHandler (Proxy :: Proxy sublayout) m a   route _ m a f = RCapture (route (Proxy :: Proxy sublayout) m a . f)  instance (HasRouter sublayout, FromHttpApiData x, KnownSymbol sym)-    => HasRouter (QueryParam sym x :> sublayout) where+         => HasRouter (QueryParam sym x :> sublayout) where   type RouteT (QueryParam sym x :> sublayout) m a     = Maybe x -> RouteT sublayout m a+  constHandler _ m a _ = constHandler (Proxy :: Proxy sublayout) m a   route _ m a f = RQueryParam     (Proxy :: Proxy sym)     (route (Proxy :: Proxy sublayout) m a . f)  instance (HasRouter sublayout, FromHttpApiData x, KnownSymbol sym)-    => HasRouter (QueryParams sym x :> sublayout) where+         => HasRouter (QueryParams sym x :> sublayout) where   type RouteT (QueryParams sym x :> sublayout) m a = [x] -> RouteT sublayout m a+  constHandler _ m a _ = constHandler (Proxy :: Proxy sublayout) m a   route _ m a f = RQueryParams     (Proxy :: Proxy sym)     (route (Proxy :: Proxy sublayout) m a . f)  instance (HasRouter sublayout, KnownSymbol sym)-    => HasRouter (QueryFlag sym :> sublayout) where+         => HasRouter (QueryFlag sym :> sublayout) where   type RouteT (QueryFlag sym :> sublayout) m a = Bool -> RouteT sublayout m a+  constHandler _ m a _ = constHandler (Proxy :: Proxy sublayout) m a   route _ m a f = RQueryFlag     (Proxy :: Proxy sym)     (route (Proxy :: Proxy sublayout) m a . f)  instance (HasRouter sublayout, KnownSymbol path)-    => HasRouter (path :> sublayout) where+         => HasRouter (path :> sublayout) where   type RouteT (path :> sublayout) m a = RouteT sublayout m a+  constHandler _ = constHandler (Proxy :: Proxy sublayout)   route _ m a page = RPath     (Proxy :: Proxy path)     (route (Proxy :: Proxy sublayout) m a page)  instance HasRouter View where   type RouteT View m a = m a+  constHandler _ _ = return   route _ _ _ = RPage  -- | Use a handler to route a location, represented as a 'String'.
+ test/Server.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE DataKinds         #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeOperators     #-}++import           Data.Proxy+import           Network.Wai.Handler.Warp+import           Servant+import           Servant.HTML.Blaze+import           Servant.Router+import           Text.Blaze.Html5            as H hiding (main)+import           Text.Blaze.Html5.Attributes++type Views = "books" :> View+        :<|> "search" :> QueryParam "query" String :> View+views :: Proxy Views+views = Proxy++type Api = "api" :> "books" :> Get '[JSON] [String]+api :: Proxy Api+api = Proxy++type WholeServer = ViewTransform Views (Get '[HTML] Html)+              :<|> Api+wholeServer :: Proxy WholeServer+wholeServer = Proxy++server :: Server WholeServer+server = viewServer :<|> apiServer where+  apiServer = return ["Book Title!"]+  viewServer = constHandler views (Proxy :: Proxy Handler) $ docTypeHtml $ do+    H.head $ return ()+    body $ script ! src "app.js" $ return ()++main :: IO ()+main = run 8080 $ serve wholeServer server