scotty-resource 0.2.0.2 → 0.4.0.0
raw patch · 3 files changed
Files
- README.md +115/−0
- scotty-resource.cabal +29/−20
- src/Web/Scotty/Resource/Trans.hs +24/−26
+ README.md view
@@ -0,0 +1,115 @@+# scotty-resource++This module defines better resource routing for Scotty.++Scotty is defined in terms of "routes", whereas HTTP is defined in+terms of "resources". This package adds a "resource" abstraction to+the scotty ecosystem.++(note: All examples probably require -XOverloadedStrings)++Scotty comes out of the box with a way to model "routes". The problem+is that "routes" is not the abstraction used by the HTTP standard and+it can sometimes be tricky to write a perfectly correct HTTP service+using the routes model (where "correct" is judged against rfc-2616).+The most blatant, (and who knows, maybe the only) example of this problem+is shown by the scotty code:++```haskell+import Web.Scotty.Trans (get, scottyT, text)++...++scottyT 8080 id $ do+ get "/hello" $ do+ text "world"+```++If a client requests something like `DELETE /hello`, this scotty application+will return `404 Not Found`, which conflicts with section 5.1.1 of rfc-2616.+A better response would be `405 Method Not Allowed`, and it would include an+automatically generated `Allow` response header.++This library gives users a way to model "resources" which is closer to the+abstractions used in the HTTP standard.++We can re-write the above example like this:++```haskell+import Web.Scotty.Trans (scottyT, text)+import Web.Scotty.Resource.Trans (resource, get)++...++scottyT 8080 id $ do+ resource "/hello" $ do+ get $ do+ text "world"+```++Given a request:++```http+DELETE /hello HTTP/1.1+Host: localhost:8080+```++The resource-based scotty application will produce something like:++```http+HTTP/1.1 405 Method Not Allowed+Allow: GET+```++Each resource is described by a `WebResource` value, which happens to be a+`Monad`. The only reason `WebResource` implements `Monad` to fit in with the+do-notation coding style of `ScottyT`. This is an abuse of `Monad`, but, you+know, whatever. The `Monoid` typeclass more correctly represents what a+`WebResource` really is. The `Monad` and `Monoid` typeclasses are used to+compose instances of `WebResource`.++Here is another more complex example, with multiple resources.++```haskell+import Data.Aeson (decode)+import Network.HTTP.Types (notFound404, badRequest400, noContent204)+import Web.Scotty.Resource.Trans (resource, get, post)+import Web.Scotty.Trans (scottyT, text, body, raw, status, param)++import MyApplication (lookupPerson, storePerson)++...++scottyT 8080 id $ do++ -- an "echo" resource+ resource "/echo" $ do+ get $ do+ text "hello world"+ post $ do+ -- echo the request body back to the user+ raw =<< body++ -- A resource that represents a kind of a RESTful database of people.+ -- This resource supports GET and PUT.+ resource "/people/:personId" $ do+ get $ do+ personId <- param "personId"+ maybePerson <- lookupPerson personId+ case maybePerson of+ Nothing ->+ status notFound404+ Just person ->+ json person+ put $ do+ personId <- param "personId"+ maybePerson <- decode <$> body+ case maybePerson of+ Nothing -> do+ status badRequest400+ text "Invalid person JSON"+ Just person ->+ storePerson personId person+ status noContent204+```+
scotty-resource.cabal view
@@ -1,39 +1,48 @@--- Initial scotty-resource.cabal generated by cabal init. For further --- documentation, see http://haskell.org/cabal/users-guide/-+cabal-version: 3.0 name: scotty-resource-version: 0.2.0.2+version: 0.4.0.0 synopsis: A Better way of modeling web resources. description: Allows users of the Scotty web framework to model resources more like the HTTP standard models them.-homepage: https://github.com/taphu/scotty-resource+homepage: https://github.com/owensmurray/scotty-resource license: Apache-2.0 license-file: LICENSE author: Rick Owens-maintainer: rick@owenssoftware.com-copyright: 2016 - Rick Owens+maintainer: rick@owensmurray.com+copyright: 2025 Rick Owens category: Web build-type: Simple--- extra-source-files: -cabal-version: >=1.10+extra-source-files:+ README.md+ LICENSE +common dependencies+ build-depends:+ , base >= 4.14.3.0 && < 4.22+ , containers >= 0.5.7.0 && < 0.8+ , http-types >= 0.9.1 && < 0.13+ , scotty >= 0.20.1 && < 0.23+ , text >= 1.2.3.2 && < 2.2+ , transformers >= 0.5.6.2 && < 0.7+ , wai >= 3.2.2.1 && < 3.3+ , unliftio >= 0.2.0.0 && < 0.3++common warnings+ ghc-options:+ -Wmissing-export-lists+ -Wredundant-constraints+ -Wall+ library+ import: dependencies, warnings exposed-modules: Web.Scotty.Resource.Trans- -- other-modules: + -- other-modules: other-extensions: OverloadedStrings- build-depends:- base >= 4.8 && < 4.11,- containers >= 0.1.0.0 && < 0.6,- http-types >= 0.8.2 && < 0.13,- scotty >= 0.10 && < 0.12,- text >= 0.11.3.1 && < 1.3,- transformers >= 0.3.0.0 && < 0.6,- wai >= 3.0.0 && < 3.3- hs-source-dirs: src- default-language: Haskell2010+ hs-source-dirs: src+ default-language: Haskell2010 source-repository head type: git
src/Web/Scotty/Resource/Trans.hs view
@@ -56,8 +56,8 @@ Each resource is described by a `WebResource` value, which happens to be a `Monad`. The only reason `WebResource` implements `Monad` to fit in with the do-notation coding style of `ScottyT`. This is an abuse of- `Monad`, but, you know, whatever. The `Monoid` typeclass more correctly- represents what a `WebResource` really is. The `Monad` and `Monoid`+ `Monad`, but, you know, whatever. The `Semigroup` typeclass more correctly+ represents what a `WebResource` really is. The `Monad` and `Semigroup` typeclasses are used to compose instances of `WebResource`. Here is another more complex example, with multiple resources.@@ -119,25 +119,24 @@ import Prelude hiding (head) import Control.Monad (liftM, ap)-import Control.Monad.IO.Class (MonadIO) import Data.List (intersperse) import Data.Maybe (fromMaybe)-import Data.Monoid ((<>)) import Data.Set (fromList, toList) import Data.Text.Encoding (decodeUtf8) import Data.Text.Lazy (fromStrict) import Network.HTTP.Types (Method, methodNotAllowed405) import Network.Wai (requestMethod) import Web.Scotty.Trans (ActionT, RoutePattern, ScottyT, matchAny,- request, ScottyError, setHeader, status)+ request, setHeader, status)+import UnliftIO (MonadUnliftIO) {- | Add a resource whose uri matches the route pattern. -}-resource :: (MonadIO m, ScottyError e)+resource :: (MonadUnliftIO m) => RoutePattern- -> WebResource e m ()- -> ScottyT e m ()+ -> WebResource m ()+ -> ScottyT m () resource uri (W methods ()) = matchAny uri $ getMethod `liftM` request >>= fromMaybe notAllowed where@@ -162,29 +161,28 @@ {- | An opaque representation of an http resource. Use `get`, `post`, etc. to- create one of these. Use the `Monad` or `Monoid` instances to compose them.+ create one of these. Use the `Monad` or `Semigroup` instances to compose them. -}-data WebResource e m a = W [(Method, ActionT e m ())] a-instance Functor (WebResource e m) where+data WebResource m a = W [(Method, ActionT m ())] a+instance Functor (WebResource m) where fmap = liftM-instance Applicative (WebResource e m) where- pure = return+instance Applicative (WebResource m) where+ pure = W [] (<*>) = ap-instance Monad (WebResource e m) where- return = W []+instance Monad (WebResource m) where+ return = pure W methods a >>= f = let W newMethods b = f a in W (methods <> newMethods) b-instance Monoid (WebResource e m a) where- mempty = W mempty undefined- mappend (W a _) (W b _) = W (a <> b) undefined+instance Semigroup (WebResource m a) where+ W a _ <> W b c = W (a <> b) c {- | Create a `WebResource` that handles OPTIONS requests using the given scotty action. -}-options :: ActionT e m () -> WebResource e m ()+options :: ActionT m () -> WebResource m () options action = W [("OPTIONS", action)] () @@ -192,7 +190,7 @@ Create a `WebResource` that handles GET requests using the given scotty action. -}-get :: ActionT e m () -> WebResource e m ()+get :: ActionT m () -> WebResource m () get action = W [("GET", action)] () @@ -200,7 +198,7 @@ Create a `WebResource` that handles HEAD requests using the given scotty action. -}-head :: ActionT e m () -> WebResource e m ()+head :: ActionT m () -> WebResource m () head action = W [("HEAD", action)] () @@ -208,7 +206,7 @@ Create a `WebResource` that handles POST requests using the given scotty action. -}-post :: ActionT e m () -> WebResource e m ()+post :: ActionT m () -> WebResource m () post action = W [("POST", action)] () @@ -216,7 +214,7 @@ Create a `WebResource` that handles PUT requests using the given scotty action. -}-put :: ActionT e m () -> WebResource e m ()+put :: ActionT m () -> WebResource m () put action = W [("PUT", action)] () @@ -224,7 +222,7 @@ Create a `WebResource` that handles DELETE requests using the given scotty action. -}-delete :: ActionT e m () -> WebResource e m ()+delete :: ActionT m () -> WebResource m () delete action = W [("DELETE", action)] () @@ -232,7 +230,7 @@ Create a `WebResource` that handles PATCH requests using the given scotty action. -}-patch :: ActionT e m () -> WebResource e m ()+patch :: ActionT m () -> WebResource m () patch action = W [("PATCH", action)] () @@ -240,7 +238,7 @@ Create a `WebResource` that handles the specific method using the given scotty action. -}-method :: Method -> ActionT e m () -> WebResource e m ()+method :: Method -> ActionT m () -> WebResource m () method m action = W [(m, action)] ()