I18N and exception catching

This commit is contained in:
Michael Snoyman 2009-10-09 14:17:09 +02:00
parent ec355d5811
commit 1b643b93e4
7 changed files with 83 additions and 19 deletions

1
TODO
View File

@ -1 +0,0 @@
Catch exceptions and return as 500 errors

View File

@ -41,6 +41,7 @@ import Control.Applicative
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import qualified Data.ByteString.Lazy as B import qualified Data.ByteString.Lazy as B
import qualified Hack import qualified Hack
import qualified Control.OldException
------ Handler monad ------ Handler monad
newtype HandlerT m a = newtype HandlerT m a =
@ -55,7 +56,9 @@ runHandler :: (ErrorResult -> Reps)
-> RawRequest -> RawRequest
-> IO Hack.Response -> IO Hack.Response
runHandler eh wrapper ctypesAll (HandlerT inside) rr = do runHandler eh wrapper ctypesAll (HandlerT inside) rr = do
(x, headers') <- inside rr (x, headers') <- Control.OldException.catch
(inside rr)
(\e -> return (Left $ InternalError $ show e, []))
let extraHeaders = let extraHeaders =
case x of case x of
Left r -> getHeaders r Left r -> getHeaders r
@ -67,14 +70,15 @@ runHandler eh wrapper ctypesAll (HandlerT inside) rr = do
Left r -> getStatus r Left r -> getStatus r
Right _ -> 200 Right _ -> 200
(ctype, selectedRep) <- chooseRep outReps ctypesAll (ctype, selectedRep) <- chooseRep outReps ctypesAll
finalRep <- wrapper ctype selectedRep let languages = [] -- FIXME
finalRep <- wrapper ctype $ selectedRep languages
let headers'' = ("Content-Type", ctype) : headers let headers'' = ("Content-Type", ctype) : headers
return $! Hack.Response statusCode headers'' finalRep return $! Hack.Response statusCode headers'' finalRep
chooseRep :: Monad m chooseRep :: Monad m
=> [(ContentType, B.ByteString)] => Reps
-> [ContentType] -> [ContentType]
-> m (ContentType, B.ByteString) -> m Rep
chooseRep rs cs chooseRep rs cs
| null rs = fail "All reps must have at least one representation" | null rs = fail "All reps must have at least one representation"
| otherwise = do | otherwise = do

58
Web/Restful/I18N.hs Normal file
View File

@ -0,0 +1,58 @@
{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}
---------------------------------------------------------
--
-- Module : Web.Restful.I18N
-- Copyright : Michael Snoyman
-- License : BSD3
--
-- Maintainer : Michael Snoyman <michael@snoyman.com>
-- Stability : Stable
-- Portability : portable
--
-- Simple method for internationalization.
--
---------------------------------------------------------
module Web.Restful.I18N
( Language
, Translator
, I18N (..)
, toTranslator
) where
import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString as BS
import Data.ByteString.Class
type Language = String
type Translator = [Language] -> B.ByteString
class I18N a where
translate :: a -> Translator
instance I18NString a => I18N a where
translate a langs = toLazyByteString $ helper langs where
helper [] = defTrans a
helper (l:ls) =
case tryTranslate a l of
Nothing -> helper ls
Just s -> s
class I18NString a where
defTrans :: a -> String
tryTranslate :: a -> Language -> Maybe String
toTranslator :: LazyByteString lbs => lbs -> Translator
toTranslator = translate . toLazyByteString
instance I18N B.ByteString where
translate = const
instance I18N BS.ByteString where
translate bs _ = toLazyByteString bs
instance I18NString String where
defTrans = id
tryTranslate = const . Just

View File

@ -15,7 +15,8 @@
--------------------------------------------------------- ---------------------------------------------------------
module Web.Restful.Response module Web.Restful.Response
( -- * Representations ( -- * Representations
Reps Rep
, Reps
, HasReps (..) , HasReps (..)
, ContentType , ContentType
-- * Abnormal responses -- * Abnormal responses
@ -32,21 +33,24 @@ module Web.Restful.Response
, objectResponse , objectResponse
-- * Tests -- * Tests
, testSuite , testSuite
-- * Re-export
, module Web.Restful.I18N
) where ) where
import Data.ByteString.Class import Data.ByteString.Class
import Data.Time.Clock import Data.Time.Clock
import Data.Object hiding (testSuite) import Data.Object hiding (testSuite)
import qualified Data.ByteString.Lazy as B
import Data.Object.Instances import Data.Object.Instances
import Web.Encodings (formatW3) import Web.Encodings (formatW3)
import Web.Restful.I18N
import Test.Framework (testGroup, Test) import Test.Framework (testGroup, Test)
type ContentType = String type ContentType = String
type Reps = [(ContentType, B.ByteString)] type Rep = (ContentType, Translator)
type Reps = [Rep]
-- | Something which can be represented as multiple content types. -- | Something which can be represented as multiple content types.
-- Each content type is called a representation of the data. -- Each content type is called a representation of the data.
@ -105,7 +109,7 @@ genResponse :: (Monad m, LazyByteString lbs)
=> ContentType => ContentType
-> lbs -> lbs
-> m Reps -> m Reps
genResponse ct lbs = return [(ct, toLazyByteString lbs)] genResponse ct lbs = return [(ct, toTranslator lbs)]
-- | Return a response with a text/html content type. -- | Return a response with a text/html content type.
htmlResponse :: (Monad m, LazyByteString lbs) => lbs -> m Reps htmlResponse :: (Monad m, LazyByteString lbs) => lbs -> m Reps
@ -117,15 +121,15 @@ objectResponse = return . reps . toRawObject
-- HasReps instances -- HasReps instances
instance HasReps () where instance HasReps () where
reps _ = [("text/plain", toLazyByteString "")] reps _ = [("text/plain", translate "")]
instance HasReps RawObject where instance HasReps RawObject where
reps o = reps o =
[ ("text/html", unHtml $ safeFromObject o) [ ("text/html", translate $ unHtml $ safeFromObject o)
, ("application/json", unJson $ safeFromObject o) , ("application/json", translate $ unJson $ safeFromObject o)
, ("text/yaml", unYaml $ safeFromObject o) , ("text/yaml", translate $ unYaml $ safeFromObject o)
] ]
instance HasReps [(ContentType, B.ByteString)] where instance HasReps Reps where
reps = id reps = id
----- Testing ----- Testing

View File

@ -21,7 +21,6 @@ import Web.Restful.Response
import Data.Time.Clock import Data.Time.Clock
import Web.Encodings import Web.Encodings
import Data.ByteString.Class
data AtomFeed = AtomFeed data AtomFeed = AtomFeed
{ atomTitle :: String { atomTitle :: String
@ -32,7 +31,7 @@ data AtomFeed = AtomFeed
} }
instance HasReps AtomFeed where instance HasReps AtomFeed where
reps e = reps e =
[ ("application/atom+xml", toLazyByteString $ show e) [ ("application/atom+xml", translate $ show e)
] ]
data AtomFeedEntry = AtomFeedEntry data AtomFeedEntry = AtomFeedEntry

View File

@ -24,7 +24,6 @@ import Web.Restful.Response
import Web.Encodings import Web.Encodings
import qualified Hack import qualified Hack
import Web.Restful.Request import Web.Restful.Request
import Data.ByteString.Class
import Data.Time (UTCTime) import Data.Time (UTCTime)
data SitemapLoc = AbsLoc String | RelLoc String data SitemapLoc = AbsLoc String | RelLoc String
@ -79,7 +78,7 @@ instance Show SitemapResponse where
instance HasReps SitemapResponse where instance HasReps SitemapResponse where
reps res = reps res =
[ ("text/xml", toLazyByteString $ show res) [ ("text/xml", translate $ show res)
] ]
sitemap :: IO [SitemapUrl] -> Handler sitemap :: IO [SitemapUrl] -> Handler

View File

@ -1,5 +1,5 @@
name: restful name: restful
version: 0.1.5 version: 0.1.6
license: BSD3 license: BSD3
license-file: LICENSE license-file: LICENSE
author: Michael Snoyman <michael@snoyman.com> author: Michael Snoyman <michael@snoyman.com>
@ -47,6 +47,7 @@ library
Web.Restful.Handler, Web.Restful.Handler,
Web.Restful.Application, Web.Restful.Application,
Web.Restful.Resource, Web.Restful.Resource,
Web.Restful.I18N,
Data.Object.Instances, Data.Object.Instances,
Hack.Middleware.MethodOverride, Hack.Middleware.MethodOverride,
Web.Restful.Helpers.Auth, Web.Restful.Helpers.Auth,