--- /dev/null
+{-# 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