libstackexchange-0.1.0.0: src/Network/StackExchange/Request.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE UnicodeSyntax #-}
{-# LANGUAGE ViewPatterns #-}
-- | StackExchange API request manipulation routines
module Network.StackExchange.Request
( -- * Type
Request(..), Auth(..), SE(..), Object(..)
-- * Constructing requests
, host, path, method, parse
, query, token, key, site, filter, state, Scope(..), scope
, client, redirectURI, secret, code
) where
import Data.Monoid (Monoid(..), (<>))
import GHC.TypeLits
import Prelude hiding (filter)
import Control.Lens hiding (query)
import Data.ByteString.Lazy (ByteString)
import Data.Aeson (FromJSON)
import Data.Aeson.Types (Value)
import Data.Default (Default(..))
import Data.Map (Map)
import qualified Data.Map as M
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Lazy.Builder.Int (decimal)
-- | Whether to use authentication at all. Currently isn't used
data Auth = RequireToken | Ready
-- | SE response type
data Object =
AccessToken -- ^ <https://api.stackexchange.com/docs/types/access-token>
| AccountMerge -- ^ <https://api.stackexchange.com/docs/types/account-merge>
| Answer -- ^ <https://api.stackexchange.com/docs/types/answer>
| Badge -- ^ <https://api.stackexchange.com/docs/types/badge>
| Comment -- ^ <https://api.stackexchange.com/docs/types/comment>
| Error -- ^ <https://api.stackexchange.com/docs/types/error>
| Event -- ^ <https://api.stackexchange.com/docs/types/event>
| Filter -- ^ <https://api.stackexchange.com/docs/types/filter>
| InboxItem -- ^ <https://api.stackexchange.com/docs/types/inbox-item>
| Info -- ^ <https://api.stackexchange.com/docs/types/info>
| NetworkUser -- ^ <https://api.stackexchange.com/docs/types/network-user>
| Notification -- ^ <https://api.stackexchange.com/docs/types/notification>
| Post -- ^ <https://api.stackexchange.com/docs/types/post>
| Privilege -- ^ <https://api.stackexchange.com/docs/types/privilege>
| Question -- ^ <https://api.stackexchange.com/docs/types/question>
| QuestionTimeline -- ^ <https://api.stackexchange.com/docs/types/question-timeline>
| Reputation -- ^ <https://api.stackexchange.com/docs/types/reputation>
| ReputationHistory -- ^ <https://api.stackexchange.com/docs/types/reputation-history>
| Revision -- ^ <https://api.stackexchange.com/docs/types/revision>
| Site -- ^ <https://api.stackexchange.com/docs/types/site>
| SuggestedEdit -- ^ <https://api.stackexchange.com/docs/types/suggested-edit>
| Tag -- ^ <https://api.stackexchange.com/docs/types/tag>
| TagScore -- ^ <https://api.stackexchange.com/docs/types/tag-score>
| TagSynonym -- ^ <https://api.stackexchange.com/docs/types/tag-synonym>
| TagWiki -- ^ <https://api.stackexchange.com/docs/types/tag-wiki>
| TopTag -- ^ <https://api.stackexchange.com/docs/types/top-tag>
| User -- ^ <https://api.stackexchange.com/docs/types/user>
| UserTimeline -- ^ <https://api.stackexchange.com/docs/types/user-timeline>
| WritePermission -- ^ <https://api.stackexchange.com/docs/types/write-permission>
-- | SE response value wrapper
newtype SE (a ∷ Object) = SE { unSE ∷ Value } deriving (Show, FromJSON)
-- | StackExchange API Request data type.
--
-- @a@ is a phantom type showing whether authentication is enabled
--
-- @n@ is a phantom type dissallowing combination of
-- different API calls in one request
--
-- @r@ is a type of parsed API call result
data Request (a ∷ Auth) (n ∷ Symbol) r = Request
{ _host ∷ Text -- ^ API host link
, _path ∷ Text -- ^ API call link
, _method ∷ Text -- ^ API call method (GET/POST)
, _query ∷ Map Text Text -- ^ API call query parameters
, _parse ∷ Maybe (ByteString → r) -- ^ API call result parsing function
}
makeLensesFor
[ ("_host", "__host")
, ("_path", "__path")
, ("_method", "__method")
, ("_query", "__query")
, ("_parse", "__parse")
]
''Request
-- | Subject to monoid and idempotent laws, they all are checked in request test suite
instance Monoid (Request a n r) where
mempty = Request
{ _host = mempty
, _path = mempty
, _method = mempty
, _query = mempty
, _parse = Nothing
}
l `mappend` r = Request
{ _host = _host $ if T.null $ _host r then l else r
, _path = _path $ if T.null $ _path r then l else r
, _method = _method $ if T.null $ _method r then l else r
, _query = _query r <> _query l
, _parse = _parse $ case _parse r of Just _ → r; Nothing → l
}
-- | Useful if what's needed is immediate result parse
instance Functor (Request a n) where
fmap f = over __parse (fmap (f .))
{-# INLINE fmap #-}
-- | Default StackExchange API request, defines only host link
instance Default (Request a n r) where
def = mempty % __host .~ "https://api.stackexchange.com/2.1" % __method .~ "GET"
{-# INLINE def #-}
-- | Request defining only API call host
--
-- Primarily used in Auth, not intended for usage by library user
host ∷ Text → Request a n r
host p = mempty % __host .~ p
{-# INLINE host #-}
-- | Request defining only API call path
--
-- Primarily used in API call wrappers, not intended for usage by library user
path ∷ Text → Request a n r
path p = mempty % __path .~ p
{-# INLINE path #-}
-- | Request defining only call method
--
-- Primarily used in API call wrappers, not intended for usage by library user
method ∷ Text → Request a n r
method m = mempty % __method .~ m
{-# INLINE method #-}
-- | Request defining only API call result parsing function
--
-- Primarily used in API call wrappers, not intended for usage by library user
parse ∷ (ByteString → r) → Request a n r
parse f = mempty % __parse ?~ f
{-# INLINE parse #-}
-- | Request defining only API call query parameters
--
-- Rather low level interface. For more specific usage 'site',
-- 'filter', etc calls may be more convenient
--
--
-- Takes a list of (key, value) parameters such as @[("order", "asc"), ("sort", "rank")]@
query ∷ [(Text, Text)] → Request a n r
query q = mempty % __query .~ M.fromList q
{-# INLINE query #-}
-- | Convert token requiring Request into ready one
token ∷ Text → Request RequireToken n r → Request Ready n r
token t = over __query (M.insert "access_token" t)
{-# INLINE token #-}
-- | Request defining only App key
key ∷ Text → Request a n r
key s = mempty % __query .~ M.singleton "key" s
{-# INLINE key #-}
-- | Request defining only API call site query parameter
site ∷ Text → Request a n r
site s = mempty % __query .~ M.singleton "site" s
{-# INLINE site #-}
-- | Request defining only API call filter query parameter
filter ∷ Text → Request a n r
filter f = mempty % __query .~ M.singleton "filter" f
{-# INLINE filter #-}
-- | Request defining only API call state query parameter
state ∷ Text → Request a n r
state s = mempty % __query .~ M.singleton "state" s
{-# INLINE state #-}
-- | Scope defines permission granted for application by user
data Scope = ReadInbox | NoExpiry | WriteAccess | PrivateInfo
-- | Request defining only API call scope query parameter
scope ∷ [Scope] → Request a n r
scope ss = mempty % __query .~ (M.singleton "scope" $ scopie ss)
where
scopie xs = T.intercalate "," . flip map xs $ \case
ReadInbox → "read_inbox"
NoExpiry → "no_expiry"
WriteAccess → "write_access"
PrivateInfo → "private_info"
-- | Request defining only Authentication API call application id
--
-- Primarily used in Authentication API call wrappers, not intended for usage by library user
client ∷ Int → Request a n r
client (toLazyText . decimal → c) = mempty % __query .~ M.singleton "client_id" c
{-# INLINE client #-}
-- | Request defining only Authentication API call redirect url
--
-- Primarily used in Authentication API call wrappers, not intended for usage by library user
redirectURI ∷ Text → Request a n r
redirectURI r = mempty % __query .~ M.singleton "redirect_uri" r
{-# INLINE redirectURI #-}
-- | Request defining only Authentication API call application secret
--
-- Primarily used in Authentication API call wrappers, not intended for usage by library user
secret ∷ Text → Request a n r
secret c = mempty % __query .~ M.singleton "client_secret" c
{-# INLINE secret #-}
-- | Request defining only Authentication API call code
--
-- Primarily used in Authentication API call wrappers, not intended for usage by library user
code ∷ Text → Request a n r
code c = mempty % __query .~ M.singleton "code" c
{-# INLINE code #-}