packages feed

salvia-0.1: src/Network/Protocol/Http/Data.hs

{-# LANGUAGE TemplateHaskell #-}
module Network.Protocol.Http.Data where

import Data.List (intercalate)
import Data.Map (lookup, insert, Map, empty)
import Data.Record.Label
import Misc.Misc (safeRead, normalCase, split)
import Network.Protocol.Http.Status (Status (..))
import Network.Protocol.Uri
import Prelude hiding (lookup)

{- | List of HTTP request methods. -}

data Method =
    OPTIONS
  | GET
  | HEAD
  | POST
  | PUT
  | DELETE
  | TRACE
  | CONNECT
  deriving (Show, Eq)

{- | All `Method` constructors as a list. -}

methods :: [Method]
methods = [OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT]

{- | HTTP protocol version. -}

data Version = Version {_major :: Int, _minor :: Int}

{- | Create HTTP 1.0 version. -}

http10 :: Version
http10 = Version 1 0

{- | Create HTTP 1.1 version. -}

http11 :: Version
http11 = Version 1 1

type HeaderKey   = String
type HeaderValue = String

{- | HTTP headers as mapping from keys to values. -}

type Headers     = Map HeaderKey HeaderValue

{- | Request or response specific part of HTTP messages. -}

data Direction =
    Request  {__method :: Method, __uri :: URI}
  | Response {__status :: Status}

{- | An HTTP message. -}

data Message =
  Message {
    _direction :: Direction
  , _version   :: Version
  , _headers   :: Headers
  , _body      :: String
  }

{- | Create an empty HTTP request object. -}

emptyRequest :: Message
emptyRequest = Message (Request GET (mkURI)) http11 empty ""

{- | Create an empty HTTP response object. -}

emptyResponse :: Message
emptyResponse = Message (Response OK) http11 empty ""

$(mkLabels [''Version, ''Direction, ''Message])

{- | Label to access the major part of the version. -}

major :: Label Version Int

{- | Label to access the minor part of the version. -}

minor :: Label Version Int

_status   :: Label Direction Status
_uri      :: Label Direction URI
_method   :: Label Direction Method

{- | Label to access the body part of an HTTP message. -}

body :: Label Message String

{- | Label to access the header of an HTTP message. -}

headers :: Label Message Headers

{- | Label to access the version part of an HTTP message. -}

version :: Label Message Version

{- | Label to access the direction part of an HTTP message. -}

direction :: Label Message Direction

{- | Label to access the method part of an HTTP message. -}

method :: Label Message Method
method = _method % direction

{- | Label to access the URI part of an HTTP message. -}

uri :: Label Message URI
uri = _uri % direction

{- | Label to access the status part of an HTTP message. -}

status :: Label Message Status
status = _status % direction





{- | Normalize the capitalization of an HTTP header key. -}

normalizeHeader :: String -> String
normalizeHeader = (intercalate "-") . (map normalCase) . (split '-')

{- | Generic label to access an HTTP header field by key. -}

header :: HeaderKey -> Label Message HeaderValue
header key =
  Label {
    lget = maybe "" id . lookup (normalizeHeader key) . lget headers
  , lset = lmod headers . insert (normalizeHeader key)
  }

{- | Simply /utf-8/. -}

utf8 :: String
utf8 = "utf-8"

{- | Access the /Content-Length/ header field. -}

contentLength :: (Read i, Integral i) => Label Message (Maybe i)
contentLength = comp safeRead (maybe "" show) (header "Content-Length")

{- | Access the /Connection/ header field. -}

connection :: Label Message String
connection = header "Connection"

{- | Access the /Keep-Alive/ header field. -}

keepAlive :: (Read i, Integral i) => Label Message (Maybe i)
keepAlive = comp safeRead (maybe "" show) (header "Keep-Alive")

{- | Access the /Cookie/ and /Set-Cookie/ header fields. -}

cookie :: Label Message String
cookie = Label (lget $ header "Cookie") (lset $ header "Set-Cookie")

{- | Access the /Location/ header field. -}

location :: Label Message (Maybe URI)
location = Label
  (parseURI . lget (header "Location"))
  (lset (header "Location") . maybe "" show)

{- | Access the /Content-Type/ header field. -}

contentType :: Label Message (String, Maybe String)
contentType = comp pa pr (header "Content-Type")
  where pr (t, c) = t ++ maybe "" ("; charset="++) c
        pa = error "no getter for contentType yet"

{- | Access the /Data/ header field. -}

date :: Label Message String
date = header "Date"

{- | Access the /Host/ header field. -}

hostname :: Label Message (Maybe Authority)
hostname = Label
  (parseAuthority . lget (header "Host"))
  (lset (header "Host") . maybe "" show)

{- | Access the /Server/ header field. -}

server :: Label Message String
server = header "Server"