{-# LANGUAGE ExistentialQuantification , FlexibleInstances , MultiParamTypeClasses , OverlappingInstances , OverloadedStrings , TemplateHaskell , UndecidableInstances , UnicodeSyntax , ViewPatterns #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Network.HTTP.Lucu.Response.StatusCode.Internal ( StatusCode(..) , SomeStatusCode , statusCodes ) where import Control.Applicative import Control.Applicative.Unicode import Control.Monad.Unicode import Data.Ascii (Ascii, AsciiBuilder) import qualified Data.Ascii as A import Data.Attoparsec.Char8 import qualified Data.Attoparsec.Lazy as LP import qualified Data.ByteString.Lazy.Char8 as Lazy import Data.Convertible.Base import Data.Convertible.Instances.Ascii () import Data.Convertible.Utils import Data.List import Data.Monoid import Language.Haskell.TH.Lib import Language.Haskell.TH.Syntax import Language.Haskell.TH.Quote import Network.HTTP.Lucu.OrphanInstances () import Network.HTTP.Lucu.Parser import Prelude.Unicode -- |Type class for HTTP status codes. -- -- Declaring types for each statuses is surely a pain. See: -- 'statusCodes' quasi-quoter. -- -- Minimal complete definition: 'numericCode' and 'textualStatus'. class (Eq sc, Show sc) ⇒ StatusCode sc where -- |Return the 3-digit integer for this status e.g. @200@ numericCode ∷ sc → Int -- |Return the combination of 3-digit integer and reason phrase -- for this status e.g. @200 OK@ textualStatus ∷ sc → AsciiBuilder -- |Wrap the status code into 'SomeStatusCode'. fromStatusCode ∷ sc → SomeStatusCode {-# INLINE CONLIKE fromStatusCode #-} fromStatusCode = SomeStatusCode instance StatusCode sc ⇒ ConvertSuccess sc SomeStatusCode where {-# INLINE convertSuccess #-} convertSuccess = fromStatusCode instance StatusCode sc ⇒ ConvertSuccess sc Ascii where {-# INLINE convertSuccess #-} convertSuccess = convertSuccessVia ((⊥) ∷ AsciiBuilder) instance StatusCode sc ⇒ ConvertSuccess sc AsciiBuilder where {-# INLINE convertSuccess #-} convertSuccess = textualStatus instance StatusCode sc ⇒ ConvertAttempt sc SomeStatusCode where {-# INLINE convertAttempt #-} convertAttempt = return ∘ cs instance StatusCode sc ⇒ ConvertAttempt sc Ascii where {-# INLINE convertAttempt #-} convertAttempt = return ∘ cs instance StatusCode sc ⇒ ConvertAttempt sc AsciiBuilder where {-# INLINE convertAttempt #-} convertAttempt = return ∘ cs -- |Container type for the 'StatusCode' type class. data SomeStatusCode = ∀sc. StatusCode sc ⇒ SomeStatusCode !sc -- |Equivalence of 'StatusCode's. Two 'StatusCode's @α@ and -- @β@ are said to be equivalent iff @'numericCode' α '==' -- 'numericCode' β@. instance Eq SomeStatusCode where {-# INLINE (==) #-} (==) = (∘ numericCode) ∘ (==) ∘ numericCode instance Show SomeStatusCode where show (SomeStatusCode sc) = show sc instance StatusCode SomeStatusCode where {-# INLINE numericCode #-} numericCode (SomeStatusCode sc) = numericCode sc {-# INLINE textualStatus #-} textualStatus (SomeStatusCode sc) = textualStatus sc {-# INLINE CONLIKE fromStatusCode #-} fromStatusCode = id -- |'QuasiQuoter' for 'StatusCode' declarations. -- -- Top-level splicing -- -- @ -- ['statusCodes'| -- 200 OK -- 400 Bad Request -- 405 Method Not Allowed -- |] -- @ -- -- becomes: -- -- @ -- data OK = OK deriving ('Eq', 'Show') -- instance 'StatusCode' OK where -- 'numericCode' _ = 200 -- 'textualStatus' _ = 'cs' (\"200 OK\" ∷ Ascii) -- -- data BadRequest = BadRequest deriving ('Eq', 'Show') -- instance 'StatusCode' BadRequest where -- 'numericCode' _ = 400 -- 'textualStatus' _ = 'cs' (\"400 Bad Request\" ∷ Ascii) -- -- data MethodNotAllowed = MethodNotAllowed deriving ('Eq', 'Show') -- instance 'StatusCode' MethodNotAllowed where -- 'numericCode' _ = 405 -- 'textualStatus' _ = 'cs' (\"405 Method Not Allowed\" ∷ Ascii) -- @ statusCodes ∷ QuasiQuoter statusCodes = QuasiQuoter { quoteExp = const unsupported , quotePat = const unsupported , quoteType = const unsupported , quoteDec = (concat <$>) ∘ (mapM statusDecl =≪) ∘ parseStatusCodes ∘ Lazy.pack } where unsupported ∷ Monad m ⇒ m α unsupported = fail "Unsupported usage of statusCodes quasi-quoter." parseStatusCodes ∷ Monad m ⇒ Lazy.ByteString → m [(Int, [Ascii])] parseStatusCodes src = case LP.parse pairs src of LP.Fail _ eCtx e → fail $ "Unparsable status codes: " ⧺ intercalate ", " eCtx ⧺ ": " ⧺ e LP.Done _ xs → return xs where pairs ∷ Parser [(Int, [Ascii])] pairs = do skipMany endOfLine xs ← sepBy pair (skipMany1 endOfLine) skipMany endOfLine endOfInput return xs "pairs" pair ∷ Parser (Int, [Ascii]) pair = do skipSpace num ← decimal skipSpace1 phrase ← sepBy1 word $ skipWhile1 (≡ '\x20') return (num, phrase) "pair" word ∷ Parser Ascii word = A.unsafeFromByteString <$> takeWhile1 isAlpha_ascii statusDecl ∷ (Int, [Ascii]) → Q [Dec] statusDecl (num, phrase) = (:) <$> dataDecl ⊛ instanceDecl where dataDecl ∷ Q Dec dataDecl = dataD (cxt []) name [] [con] [''Eq, ''Show] name ∷ Name name = mkName $ concatMap cs phrase con ∷ Q Con con = normalC name [] instanceDecl ∷ Q [Dec] instanceDecl = [d| instance StatusCode $typ where {-# INLINE CONLIKE numericCode #-} numericCode _ = $(lift num) {-# INLINE textualStatus #-} textualStatus _ = cs $(lift txt) |] typ ∷ Q Type typ = conT name txt ∷ Ascii txt = mconcat $ intersperse "\x20" $ A.unsafeFromString (show num) : phrase