From 1caa0c4891bfe2fffdcf1909b589cccd153a4506 Mon Sep 17 00:00:00 2001
From: Michael Snoyman
Date: Fri, 28 Aug 2009 11:01:19 +0300
Subject: [PATCH] Split up Web.Restful.Response
---
Data/Object/Instances.hs | 23 ++--
Web/Restful/Generic/ListDetail.hs | 55 +++++++++
Web/Restful/Response.hs | 187 ++----------------------------
Web/Restful/Response/AtomFeed.hs | 88 ++++++++++++++
Web/Restful/Response/Sitemap.hs | 90 ++++++++++++++
restful.cabal | 5 +-
6 files changed, 258 insertions(+), 190 deletions(-)
create mode 100644 Web/Restful/Generic/ListDetail.hs
create mode 100644 Web/Restful/Response/AtomFeed.hs
create mode 100644 Web/Restful/Response/Sitemap.hs
diff --git a/Data/Object/Instances.hs b/Data/Object/Instances.hs
index 6f2301ef..8b3ba8ef 100644
--- a/Data/Object/Instances.hs
+++ b/Data/Object/Instances.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE FlexibleInstances #-}
---------------------------------------------------------
--
-- Module : Data.Object.Instances
@@ -15,6 +16,7 @@ module Data.Object.Instances
( Json (..)
, Yaml (..)
, Html (..)
+ , SafeFromObject (..)
) where
import Data.Object
@@ -23,9 +25,12 @@ import Data.ByteString.Class
import Web.Encodings (encodeJson)
import qualified Text.Yaml as Y
-newtype Json = Json B.ByteString
-instance FromObject Json where
- fromObject = return . Json . helper where
+class SafeFromObject a where
+ safeFromObject :: Object -> a
+
+newtype Json = Json { unJson :: B.ByteString }
+instance SafeFromObject Json where
+ safeFromObject = Json . helper where
helper :: Object -> B.ByteString
helper (Scalar s) = B.concat
[ toStrictByteString "\""
@@ -50,19 +55,19 @@ instance FromObject Json where
, helper v
]
-newtype Yaml = Yaml B.ByteString
-instance FromObject Yaml where
- fromObject = return . Yaml . Y.encode
+newtype Yaml = Yaml { unYaml :: B.ByteString }
+instance SafeFromObject Yaml where
+ safeFromObject = Yaml . Y.encode
-- | Represents as an entire HTML 5 document by using the following:
--
-- * A scalar is a paragraph.
-- * A sequence is an unordered list.
-- * A mapping is a definition list.
-newtype Html = Html B.ByteString
+newtype Html = Html { unHtml :: B.ByteString }
-instance FromObject Html where
- fromObject o = return $ Html $ B.concat
+instance SafeFromObject Html where
+ safeFromObject o = Html $ B.concat
[ toStrictByteString "\n"
, helper o
, toStrictByteString ""
diff --git a/Web/Restful/Generic/ListDetail.hs b/Web/Restful/Generic/ListDetail.hs
new file mode 100644
index 00000000..00d48d57
--- /dev/null
+++ b/Web/Restful/Generic/ListDetail.hs
@@ -0,0 +1,55 @@
+---------------------------------------------------------
+--
+-- Module : Web.Restful.Generic.ListDetail
+-- Copyright : Michael Snoyman
+-- License : BSD3
+--
+-- Maintainer : Michael Snoyman
+-- Stability : Stable
+-- Portability : portable
+--
+-- Generic responses for listing items and then detailing them.
+--
+---------------------------------------------------------
+module Web.Restful.Generic.ListDetail
+ ( ListDetail (..)
+ , ItemList (..)
+ , ItemDetail (..)
+ ) where
+
+import Web.Restful.Response
+import Web.Encodings
+import Data.Object
+import Data.Object.Instances
+import Data.ByteString.Class
+
+class ToObject a => ListDetail a where
+ htmlDetail :: a -> String
+ htmlDetail = fromStrictByteString . unHtml . safeFromObject . toObject
+ detailTitle :: a -> String
+ detailUrl :: a -> String
+ htmlList :: [a] -> String
+ htmlList l = "" ++ concatMap helper l ++ "
"
+ where
+ helper i = "" ++ encodeHtml (detailTitle i) ++
+ ""
+ -- | Often times for the JSON response of the list, we don't need all
+ -- the information.
+ treeList :: [a] -> Object -- FIXME
+ treeList = Sequence . map treeListSingle
+ treeListSingle :: a -> Object
+ treeListSingle = toObject
+
+newtype ItemList a = ItemList [a]
+instance ListDetail a => Response (ItemList a) where
+ reps (ItemList l) =
+ [ ("text/html", response 200 [] $ htmlList l)
+ , ("application/json", response 200 [] $ unJson $ safeFromObject $ treeList l)
+ ]
+newtype ItemDetail a = ItemDetail a
+instance ListDetail a => Response (ItemDetail a) where
+ reps (ItemDetail i) =
+ [ ("text/html", response 200 [] $ htmlDetail i)
+ , ("application/json", response 200 [] $ unJson $ safeFromObject $ toObject i)
+ ]
diff --git a/Web/Restful/Response.hs b/Web/Restful/Response.hs
index c950ab86..9d5e8221 100644
--- a/Web/Restful/Response.hs
+++ b/Web/Restful/Response.hs
@@ -18,25 +18,12 @@ module Web.Restful.Response
-- * Response construction
Response (..)
, response
- -- ** Helper 'Response' instances
- -- *** Atom news feed
- , AtomFeed (..)
- , AtomFeedEntry (..)
- -- *** Sitemap
- , sitemap
- , SitemapUrl (..)
- , SitemapLoc (..)
- , SitemapChangeFreq (..)
- -- *** Generics
- -- **** List/detail
- , ListDetail (..)
- , ItemList (..)
- , ItemDetail (..)
- -- **** Multiple response types.
- , GenResponse (..)
-- * FIXME
+ , GenResponse (..)
, ResponseWrapper (..)
, ErrorResponse (..)
+ , formatW3
+ , UTCTime
) where
import Data.ByteString.Class
@@ -45,10 +32,8 @@ import Data.Time.Format
import Data.Time.Clock
import Web.Encodings
import System.Locale
-import Web.Restful.Request -- FIXME ultimately remove
import Data.Object
import Data.List (intercalate)
-import Data.Object.Instances
type ContentType = String
@@ -80,138 +65,6 @@ data ResponseWrapper = forall res. Response res => ResponseWrapper res
instance Response ResponseWrapper where
reps (ResponseWrapper res) = reps res
-data AtomFeed = AtomFeed
- { atomTitle :: String
- , atomLinkSelf :: String
- , atomLinkHome :: String
- , atomUpdated :: UTCTime
- , atomEntries :: [AtomFeedEntry]
- }
-instance Response AtomFeed where
- reps e =
- [ ("application/atom+xml", response 200 [] $ show e)
- ]
-
-data AtomFeedEntry = AtomFeedEntry
- { atomEntryLink :: String
- , atomEntryUpdated :: UTCTime
- , atomEntryTitle :: String
- , atomEntryContent :: String
- }
-
-instance Show AtomFeed where
- show f = concat
- [ "\n"
- , ""
- , ""
- , encodeHtml $ atomTitle f
- , ""
- , ""
- , ""
- , ""
- , formatW3 $ atomUpdated f
- , ""
- , ""
- , encodeHtml $ atomLinkHome f
- , ""
- , concatMap show $ atomEntries f
- , ""
- ]
-
-instance Show AtomFeedEntry where
- show e = concat
- [ ""
- , ""
- , encodeHtml $ atomEntryLink e
- , ""
- , ""
- , ""
- , formatW3 $ atomEntryUpdated e
- , ""
- , ""
- , encodeHtml $ atomEntryTitle e
- , ""
- , ""
- , ""
- ]
-
-formatW3 :: UTCTime -> String
-formatW3 = formatTime defaultTimeLocale "%FT%X-08:00"
-
--- sitemaps
-data SitemapLoc = AbsLoc String | RelLoc String
-data SitemapChangeFreq = Always
- | Hourly
- | Daily
- | Weekly
- | Monthly
- | Yearly
- | Never
-instance Show SitemapChangeFreq where
- show Always = "always"
- show Hourly = "hourly"
- show Daily = "daily"
- show Weekly = "weekly"
- show Monthly = "monthly"
- show Yearly = "yearly"
- show Never = "never"
-
-data SitemapUrl = SitemapUrl
- { sitemapLoc :: SitemapLoc
- , sitemapLastMod :: UTCTime
- , sitemapChangeFreq :: SitemapChangeFreq
- , priority :: Double
- }
-data SitemapRequest = SitemapRequest String Int
-instance Request SitemapRequest where
- parseRequest = do
- env <- parseEnv
- return $! SitemapRequest (Hack.serverName env)
- (Hack.serverPort env)
-data SitemapResponse = SitemapResponse SitemapRequest [SitemapUrl]
-instance Show SitemapResponse where
- show (SitemapResponse (SitemapRequest host port) urls) =
- "\n" ++
- "" ++
- concatMap helper urls ++
- ""
- where
- prefix = "http://" ++ host ++
- case port of
- 80 -> ""
- _ -> ":" ++ show port
- helper (SitemapUrl loc modTime freq pri) = concat
- [ ""
- , encodeHtml $ showLoc loc
- , ""
- , formatW3 modTime
- , ""
- , show freq
- , ""
- , show pri
- , ""
- ]
- showLoc (AbsLoc s) = s
- showLoc (RelLoc s) = prefix ++ s
-
-instance Response SitemapResponse where
- reps res =
- [ ("text/xml", response 200 [] $ show res)
- ]
-
-sitemap :: IO [SitemapUrl] -> SitemapRequest -> IO SitemapResponse
-sitemap urls' req = do
- urls <- urls'
- return $ SitemapResponse req urls
-
data GenResponse = HtmlResponse String
| ObjectResponse Object
| HtmlOrObjectResponse String Object
@@ -230,36 +83,6 @@ instance Response GenResponse where
"'>" ++ encodeHtml url ++ "
"
reps (PermissionDeniedResult s) = [("text/plain", response 403 [] s)]
reps (NotFoundResponse s) = [("text/plain", response 404 [] s)]
-class ToObject a => ListDetail a where
- htmlDetail :: a -> String
- htmlDetail = treeToHtml . toObject
- detailTitle :: a -> String
- detailUrl :: a -> String
- htmlList :: [a] -> String
- htmlList l = "" ++ concatMap helper l ++ "
"
- where
- helper i = "" ++ encodeHtml (detailTitle i) ++
- ""
- -- | Often times for the JSON response of the list, we don't need all
- -- the information.
- treeList :: [a] -> Object -- FIXME
- treeList = Sequence . map treeListSingle
- treeListSingle :: a -> Object
- treeListSingle = toObject
-
-newtype ItemList a = ItemList [a]
-instance ListDetail a => Response (ItemList a) where
- reps (ItemList l) =
- [ ("text/html", response 200 [] $ htmlList l)
- , ("application/json", response 200 [] $ treeToJson $ treeList l)
- ]
-newtype ItemDetail a = ItemDetail a
-instance ListDetail a => Response (ItemDetail a) where
- reps (ItemDetail i) =
- [ ("text/html", response 200 [] $ htmlDetail i)
- , ("application/json", response 200 [] $ treeToJson $ toObject i)
- ]
-- FIXME remove treeTo functions, replace with Object instances
treeToJson :: Object -> String
@@ -296,3 +119,7 @@ instance Response Object where
instance Response [(String, Hack.Response)] where
reps = id
+
+-- FIXME put in a separate module (maybe Web.Encodings)
+formatW3 :: UTCTime -> String
+formatW3 = formatTime defaultTimeLocale "%FT%X-08:00"
diff --git a/Web/Restful/Response/AtomFeed.hs b/Web/Restful/Response/AtomFeed.hs
new file mode 100644
index 00000000..c79d9d3b
--- /dev/null
+++ b/Web/Restful/Response/AtomFeed.hs
@@ -0,0 +1,88 @@
+---------------------------------------------------------
+--
+-- Module : Web.Restful.Response.AtomFeed
+-- Copyright : Michael Snoyman
+-- License : BSD3
+--
+-- Maintainer : Michael Snoyman
+-- Stability : Stable
+-- Portability : portable
+--
+-- Generating atom news feeds.
+--
+---------------------------------------------------------
+
+module Web.Restful.Response.AtomFeed
+ ( AtomFeed (..)
+ , AtomFeedEntry (..)
+ ) where
+
+import Web.Restful.Response
+
+import Data.Time.Format
+import Data.Time.Clock
+import Web.Encodings
+import System.Locale
+
+data AtomFeed = AtomFeed
+ { atomTitle :: String
+ , atomLinkSelf :: String
+ , atomLinkHome :: String
+ , atomUpdated :: UTCTime
+ , atomEntries :: [AtomFeedEntry]
+ }
+instance Response AtomFeed where
+ reps e =
+ [ ("application/atom+xml", response 200 [] $ show e)
+ ]
+
+data AtomFeedEntry = AtomFeedEntry
+ { atomEntryLink :: String
+ , atomEntryUpdated :: UTCTime
+ , atomEntryTitle :: String
+ , atomEntryContent :: String
+ }
+
+instance Show AtomFeed where
+ show f = concat
+ [ "\n"
+ , ""
+ , ""
+ , encodeHtml $ atomTitle f
+ , ""
+ , ""
+ , ""
+ , ""
+ , formatW3 $ atomUpdated f
+ , ""
+ , ""
+ , encodeHtml $ atomLinkHome f
+ , ""
+ , concatMap show $ atomEntries f
+ , ""
+ ]
+
+instance Show AtomFeedEntry where
+ show e = concat
+ [ ""
+ , ""
+ , encodeHtml $ atomEntryLink e
+ , ""
+ , ""
+ , ""
+ , formatW3 $ atomEntryUpdated e
+ , ""
+ , ""
+ , encodeHtml $ atomEntryTitle e
+ , ""
+ , ""
+ , ""
+ ]
diff --git a/Web/Restful/Response/Sitemap.hs b/Web/Restful/Response/Sitemap.hs
new file mode 100644
index 00000000..0167a9f5
--- /dev/null
+++ b/Web/Restful/Response/Sitemap.hs
@@ -0,0 +1,90 @@
+---------------------------------------------------------
+--
+-- Module : Web.Restful.Response.AtomFeed
+-- Copyright : Michael Snoyman
+-- License : BSD3
+--
+-- Maintainer : Michael Snoyman
+-- Stability : Stable
+-- Portability : portable
+--
+-- Generating Google sitemap files.
+--
+---------------------------------------------------------
+
+module Web.Restful.Response.Sitemap
+ ( sitemap
+ , SitemapUrl (..)
+ , SitemapLoc (..)
+ , SitemapChangeFreq (..)
+ ) where
+
+import Web.Restful.Response
+import Web.Encodings
+import qualified Hack
+import Web.Restful.Request
+
+data SitemapLoc = AbsLoc String | RelLoc String
+data SitemapChangeFreq = Always
+ | Hourly
+ | Daily
+ | Weekly
+ | Monthly
+ | Yearly
+ | Never
+instance Show SitemapChangeFreq where
+ show Always = "always"
+ show Hourly = "hourly"
+ show Daily = "daily"
+ show Weekly = "weekly"
+ show Monthly = "monthly"
+ show Yearly = "yearly"
+ show Never = "never"
+
+data SitemapUrl = SitemapUrl
+ { sitemapLoc :: SitemapLoc
+ , sitemapLastMod :: UTCTime
+ , sitemapChangeFreq :: SitemapChangeFreq
+ , priority :: Double
+ }
+data SitemapRequest = SitemapRequest String Int
+instance Request SitemapRequest where
+ parseRequest = do
+ env <- parseEnv
+ return $! SitemapRequest (Hack.serverName env)
+ (Hack.serverPort env)
+data SitemapResponse = SitemapResponse SitemapRequest [SitemapUrl]
+instance Show SitemapResponse where
+ show (SitemapResponse (SitemapRequest host port) urls) =
+ "\n" ++
+ "" ++
+ concatMap helper urls ++
+ ""
+ where
+ prefix = "http://" ++ host ++
+ case port of
+ 80 -> ""
+ _ -> ":" ++ show port
+ helper (SitemapUrl loc modTime freq pri) = concat
+ [ ""
+ , encodeHtml $ showLoc loc
+ , ""
+ , formatW3 modTime
+ , ""
+ , show freq
+ , ""
+ , show pri
+ , ""
+ ]
+ showLoc (AbsLoc s) = s
+ showLoc (RelLoc s) = prefix ++ s
+
+instance Response SitemapResponse where
+ reps res =
+ [ ("text/xml", response 200 [] $ show res)
+ ]
+
+sitemap :: IO [SitemapUrl] -> SitemapRequest -> IO SitemapResponse
+sitemap urls' req = do
+ urls <- urls'
+ return $ SitemapResponse req urls
diff --git a/restful.cabal b/restful.cabal
index b8ee84d9..8a8efa37 100644
--- a/restful.cabal
+++ b/restful.cabal
@@ -42,5 +42,8 @@ library
Web.Restful.Resource,
Data.Object.Instances,
Hack.Middleware.MethodOverride,
- Web.Restful.Helpers.Auth
+ Web.Restful.Helpers.Auth,
+ Web.Restful.Response.AtomFeed,
+ Web.Restful.Response.Sitemap,
+ Web.Restful.Generic.ListDetail
ghc-options: -Wall