packages feed

servant-scotty (empty) → 0.1

raw patch · 8 files changed

+484/−0 lines, 8 filesdep +aesondep +basedep +http-typessetup-changed

Dependencies added: aeson, base, http-types, scotty, servant, servant-response, text, transformers

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2014, Alp Mestanogullari++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Alp Mestanogullari nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ servant-scotty.cabal view
@@ -0,0 +1,33 @@+name:                servant-scotty+version:             0.1+synopsis:            Generate a web service for servant 'Resource's using scotty and JSON+description:         Generate a web service for servant 'Resource's using scotty and JSON+homepage:            http://github.com/zalora/servant+license:             BSD3+license-file:        LICENSE+author:              Alp Mestanogullari+maintainer:          alp@zalora.com+copyright:           2014 Zalora SEA+category:            Web+build-type:          Simple+cabal-version:       >=1.10++library+  exposed-modules:+      Servant.Scotty+    , Servant.Scotty.Arguments+    , Servant.Scotty.Op+    , Servant.Scotty.Prelude+    , Servant.Scotty.Response+  build-depends:+      base >=4 && <5+    , http-types >= 0.8+    , servant >= 0.1+    , servant-response >= 0.1+    , scotty >= 0.8+    , text >= 0.11+    , transformers >= 0.4+    , aeson >= 0.7+  hs-source-dirs:      src+  default-language:    Haskell2010+  ghc-options:         -Wall
+ src/Servant/Scotty.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}++{- |+Module      :  Servant.Scotty+Copyright   :  (c) Zalora SEA 2014+License     :  BSD3+Maintainer  :  Alp Mestanogullari <alp@zalora.com>+Stability   :  experimental++Module for defining a <http://hackage.haskell.org/package/scotty scotty>+webservice from 'Resource's.++> EXAMPLE HERE+-}+module Servant.Scotty+  ( -- * Setting up handlers for a 'Resource'+    Runnable(runResource)+    -- * Defining handlers for an operation+  , ScottyOp(..)+  , Index(..)+  , js+  , safely+  , Response(..)+  , respond+    -- * Utilities+  , Suitables+  ) where++import Control.Applicative+import Control.Monad.IO.Class+import Data.Aeson hiding (json)+import GHC.Exts+import Servant.Resource+import Servant.Scotty.Arguments+import Servant.Scotty.Op+import Servant.Scotty.Response+import Web.Scotty.Trans++-- | Internal class used to drive the recursion+--   on the list of operations when generating all+--   the endpoints and handlers.+--+--   This lets everyone care only about the specific+--   behavior of their operations, this class will make+--   sure all the operations of your 'Resource' have an+--   implementation.+--+--   Regardless of what's+--   specific about each operation, we just recursively+--   go through all the operations and call 'runOperation'+--   for each of them.+class Runnable ops where+  -- | Call this function to setup a 'Resource' in your+  --   scotty application.+  runResource :: (Functor m, MonadIO m, ScottyError e, Suitables ops a i r)+              => Resource c a i r e ops+              -> ScottyT e m ()++-- | No operation means we don't setup any handler.+instance Runnable '[] where+  -- runResource :: (MonadIO m, ScottyError e)+  --             => Resource c a i r e '[]+  --             -> ScottyT e m ()+  -- no operation supported+  -- (or "no more", if there was any and we're ending the recursion)+  runResource _ = return ()++-- | Given some already runnable operation list @ops@,+--   and an operation that we can run in scotty+--+--   (that's the @'ScottyOp' o@ constraint),+--+--   we can run the @(o ': ops)@ operation list.+instance (ScottyOp o, Runnable ops) => Runnable (o ': ops) where+  -- runResource :: (MonadIO m, ScottyError e)+	--   					 => Resource c a i r e (o ': ops)+	--	    			 -> ScottyT e m ()+  runResource r = do+    withHeadOperation r runOperation+    runResource (dropHeadOperation r)++type family Suitables (ops :: [*]) a i (r :: * -> *) :: Constraint+type instance Suitables '[] a i r = ()+type instance Suitables (o ': ops) a i r = (Suitable o a i r, Suitables ops a i r)
+ src/Servant/Scotty/Arguments.hs view
@@ -0,0 +1,55 @@+{- |+Module      :  Servant.Scotty.Arguments+Copyright   :  (c) Zalora SEA 2014+License     :  BSD3+Maintainer  :  Alp Mestanogullari <alp@zalora.com>+Stability   :  experimental++Functions and typeclasses for automatically fetching+the arguments of the \"database operations\"+from either the request path or from the request body,+by decoding it from JSON to your type.+-}+module Servant.Scotty.Arguments+  ( -- * Deducing an argument from the request path+    Index(..)+  , -- * Deducing an argument from the JSON body+    js+  , FromJSON+  ) where++import Control.Applicative+import Control.Monad.IO.Class+import Data.Aeson (FromJSON)+import Servant.Resource+import Web.Scotty.Trans++-- | What it means for a scotty 'Resource'+--   to have an index type.+--+--   * 'idx' should lookup in the request path+--     whatever is necessary to get the @i@+--     of @Resource c a i r e ops@, for operations+--     that take it as an argument, e.g /Delete/,+--     /Update/ or /View/.+--+--   * 'route' should return a 'String' that'll be+--     passed to 'capture'. You may use one or more+--     \"path parameters\" (calls to 'param', instances+--     of 'Param') to compute your value of type @k@.+--     You probably want to use 'name' on the 'Resource'+--     to generate the beginning of the path.+class Index k where+  -- | Lookup the index in the request path+  idx :: (Functor m, MonadIO m, ScottyError e)+      => ActionT e m k++  -- | String to 'capture' that represents the+  --   'RoutePattern'.+  route :: Resource c a k r e ops -> String++-- | Simply gets the request's body as JSON+--   (or raises an exception if the decoding fails)+js :: (MonadIO m, ScottyError e, FromJSON a)+   => ActionT e m a+js = jsonData
+ src/Servant/Scotty/Op.hs view
@@ -0,0 +1,110 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{- |+Module      :  Servant.Scotty+Copyright   :  (c) Zalora SEA 2014+License     :  BSD3+Maintainer  :  Alp Mestanogullari <alp@zalora.com>+Stability   :  experimental++This module contains the class that you must implement+if you want some operation supported by the scotty backend and+some utilities you'll most likely need if you want to define your+own instances.++Import @Servant.Scotty.Prelude@ if you want instances for the+REST-y operations defined in @Servant.Prelude@.+-}+module Servant.Scotty.Op+	( -- * The 'ScottyOp' class+    ScottyOp(..)+  , -- * Utility functions/classes you'll want to use+    --   for you own instances+    safely+  , Response(..)+  , respond+  , Index(..)+  , js+  ) where++import Control.Monad.IO.Class+import GHC.Exts+import Servant.Context+import Servant.Error+import Servant.Resource+import Servant.Scotty.Arguments+import Servant.Scotty.Response+import Web.Scotty.Trans++-- | A class that lets you define one or more handler(s) for an operation @o@.+class ScottyOp o where+  -- | Each operation can define its own constraints on:+  --+  --   * the type of the entries, @a@+  --   * the type by which the entries are indexed, @i@+  --   * the result type @r@ of \"effectful\" database operations+  --     (those that add/update/delete entries)+  --+  --   This is useful because that way, your types will /only/ have to+  --   satisfy the constraints /specified/ by the operations your 'Resource'+  --   carries, not some global dumb constraints you have to pay for even if+  --   you don't care about the operation that requires this constraint.+  type Suitable o a i (r :: * -> *) :: Constraint++  -- | Given a 'Resource' and the \"database function\" (so to speak)+  --   corresponding to your operation, do some business in /scotty/'s+  --   'ScottyT' and 'ActionT' monads to define a handler for this very operation.+  --+  --   To provide the \"database function\" with some 'Context' @c@+  --   you can use 'Servant.Context.withContext' to run the operation+  --   and 'Servant.Resource.context' to get the context of your 'Resource'.+  --+  --   To catch exceptions around your db operation in your handler,+  --   you can use the 'Servant.Resource.excCatcher' access the+  --   'Servant.Error.ExceptionCatcher' of your 'Resource' and+  --   'Servant.Error.handledWith' to catch them and convert them+  --   to your error type @e@. You can then 'raise' the error value+  --   if you have a sensible default handler or handle it locally and+  --   respond with whatever is appropriate in your case.+  runOperation :: (Functor m, MonadIO m, ScottyError e, Suitable o a i r)+               => Resource c a i r e (o ': ops)+               -> Operation o c a i r+               -> ScottyT e m ()++-- | This is a function you'll want to use when defining your+--   own operations.+--+--   It runs the second argument to get the operation to run,+--   and feeds it the 'Resource' argument's context and extracts+--   the result.+--+--   Intended to be used in 'runOperation' in a way similar to+--+--   > -- example: for the 'Delete' operation+--   > runOperation res op =+--   >   delete (capture $ "/" ++ name res ++ route res) $ do+--   >     result <- safely res $ op <$> idx+--   >     respond result+--+--   Here we just take the particular delete operation for the+--   client code's type, lookup the 'Index' argument it takes+--   from the request path and run the operation 'safely', eventually+--   converting its result to an appropriate response.+safely :: (MonadIO m, ScottyError e)+       => Resource c a i r e ops  -- ^ the resource, that holds the context @c@+                                  --   and the exception catchers required to run+                                  --   the operation+       -> ActionT e m (c -> IO x) -- ^ a scotty action that'll produce the operation+                                  --   we want, with all the arguments but the context+                                  --   already provided.+       -> ActionT e m x           -- ^ returns the result of the operation+                                  --   or 'raise' if an exception the 'Resource'+                                  --   is watching for is thrown, using the appropriate conversion+                                  --   to your application's error type @e@.+safely res mact = do+  act <- mact+  result <- liftIO $ withContext (context res) act+              `handledWith` excCatcher res+  either raise return result
+ src/Servant/Scotty/Prelude.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE DeriveGeneric,+             TypeFamilies,+             MultiParamTypeClasses,+             OverloadedStrings #-}+{- |+Module      :  Servant.Scotty.Prelude+Copyright   :  (c) Zalora SEA 2014+License     :  BSD3+Maintainer  :  Alp Mestanogullari <alp@zalora.com>+Stability   :  experimental++Instances of 'ScottyOp' for the operations defined+in "Servant.Prelude", along with some reusable types+necessary for the instances.+-}+module Servant.Scotty.Prelude+  ( -- * Defining 'Resource's and standard operations+    module Servant.Prelude++  , -- * Standard response types+    module Servant.Response.Prelude++  , -- * 'ScottyOp' class and standard operations implementations in scotty+    ScottyOp(..)++  , -- * Helpful types, functions, classes and instances+    --   for defining your own operations+    module Servant.Scotty.Arguments+  , module Servant.Scotty.Response+  , module Web.Scotty.Trans+  ) where++import Control.Applicative+import Data.Aeson+import Data.Text (Text)+import GHC.Generics+import Servant.Prelude+import Servant.Response.Prelude+import Servant.Scotty.Arguments+import Servant.Scotty.Op+import Servant.Scotty.Response+import Web.Scotty.Trans++-- | Generate a+--+--   > POST /:resourcename+--+--   handler for adding entries.+--+--   /Constraints on @a@, @i@ and @r@/:+--+--   > type Suitable Add a i r = (FromJSON a, Response (UpdateResponse Add) r)+instance ScottyOp Add where+  type Suitable Add a i r =+    (FromJSON a, Response (UpdateResponse Add) (r Add))++  runOperation res op =+    post (capture $ "/" ++ name res) $ do+      result <- safely res $ op <$> js+      respond result++-- | Generate a+--+--   > DELETE /:resourcename/<index specific stuffs>+--+--   handler for deleting entries.+--+--   /Constraints on @a@, @i@ and @r@/:+--+--   > type Suitable Delete a i r = (Index i, Response (UpdateResponse Delete) r)+instance ScottyOp Delete where+  type Suitable Delete a i r =+    (Index i, Response (UpdateResponse Delete) (r Delete))++  runOperation res op =+    delete (capture $ "/" ++ name res ++ route res) $ do+      result <- safely res $ op <$> idx+      respond result++-- | Generate a+--+--   > GET /:resourcename+--+--   handler for listing all entries.+--+--   /Constraints on @a@, @i@ and @r@/:+--+--   > type Suitable ListAll a i r = ToJSON a+instance ScottyOp ListAll where+  type Suitable ListAll a i r = ToJSON a++  runOperation res op =+    get (capture $ "/" ++ name res) $ do+      result <- safely res $ pure op+      respond result++-- | Generate a+--+--   > PUT /:resourcename/<index specific stuffs>+--+--   handler for updating an entry.+--+--   /Constraints on @a@, @i@ and @r@/:+--+--   > type Suitable Update a i r = (Index i, FromJSON a, Response (UpdateResponse Update) r)+instance ScottyOp Update where+  type Suitable Update a i r =+    (Index i, FromJSON a, Response (UpdateResponse Update) (r Update))++  runOperation res op =+    put (capture $ "/" ++ name res ++ route res) $ do+      result <- safely res $ op <$> idx <*> js+      respond result++-- | Generate a+--+--   > GET /:resourcename/<index specific stuffs>+--+--   handler for viewing an entry.+--+--   /Constraints on @a@, @i@ and @r@/:+--+--   > type Suitable View a i r = (Index i, ToJSON a)+instance ScottyOp View where+  type Suitable View a i r =+    (Index i, ToJSON a)++  runOperation res op =+    get (capture $ "/" ++ name res ++ route res) $ do+      result <- safely res $ op <$> idx+      respond result
+ src/Servant/Scotty/Response.hs view
@@ -0,0 +1,36 @@+{-# LANGUAGE FunctionalDependencies,+             MultiParamTypeClasses #-}+{- |+Module      :  Servant.Scotty.Response+Copyright   :  (c) Zalora SEA 2014+License     :  BSD3+Maintainer  :  Alp Mestanogullari <alp@zalora.com>+Stability   :  experimental++This module exports the 'Response' class and a handy 'respond'+function that you can use when defining handlers for your own+operations.++-}+module Servant.Scotty.Response+  ( -- * The 'Response' class+    module Servant.Response+  , -- * 'respond'+    respond+  ) where++import Servant.Response+import Web.Scotty.Trans++-- | Given the result of some operation,+--   it picks the appropriate response type+--   and uses 'toResponse' to convert the result+--   to a JSON-encodable value along with a status code,+--   both used to then send a response to the client.+respond :: (Response resp x, ScottyError e, Monad m)+        => x+        -> ActionT e m ()+respond result = do+  let (respValue, statuscode) = toResponse result+  status statuscode+  json respValue