fb-1.0.9: src/Facebook/Types.hs
{-# LANGUAGE CPP, DeriveDataTypeable, GADTs, StandaloneDeriving #-}
module Facebook.Types
( Credentials(..)
, appIdBS
, appSecretBS
, AccessToken(..)
, UserAccessToken
, AppAccessToken
, AccessTokenData
, Id(..)
, UserId
, accessTokenData
, accessTokenExpires
, accessTokenUserId
, UserKind
, AppKind
, Argument
, (<>)
, FbUTCTime(..)
) where
import Control.Applicative ((<$>), (<*>), pure)
import Control.Monad (mzero)
import Data.ByteString (ByteString)
import Data.Int (Int64)
import Data.Monoid (Monoid, mappend)
import Data.String (IsString)
import Data.Text (Text)
import Data.Time (UTCTime, parseTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Typeable (Typeable, Typeable1)
#if MIN_VERSION_time(1,5,0)
import Data.Time (defaultTimeLocale)
#else
import System.Locale (defaultTimeLocale)
#endif
import qualified Data.Aeson as A
import qualified Data.Aeson.Types as A
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Lazy.Builder.Int as TLBI
-- | Credentials that you get for your app when you register on
-- Facebook.
data Credentials =
Credentials { appName :: Text -- ^ Your application name (e.g. for Open Graph calls).
, appId :: Text -- ^ Your application ID.
, appSecret :: Text -- ^ Your application secret key.
}
deriving (Eq, Ord, Show, Read, Typeable)
-- | 'appId' for 'ByteString'.
appIdBS :: Credentials -> ByteString
appIdBS = TE.encodeUtf8 . appId
-- | 'appSecret' for 'ByteString'.
appSecretBS :: Credentials -> ByteString
appSecretBS = TE.encodeUtf8 . appSecret
-- | An access token. While you can make some API calls without
-- an access token, many require an access token and some will
-- give you more information with an appropriate access token.
--
-- There are two kinds of access tokens:
--
-- [User access token] An access token obtained after an user
-- accepts your application. Let's you access more information
-- about that user and act on their behalf (depending on which
-- permissions you've asked for).
--
-- [App access token] An access token that allows you to take
-- administrative actions for your application.
--
-- These two kinds of access tokens are distinguished by the
-- phantom type on 'AccessToken', which can be 'UserKind' or
-- 'AppKind'.
data AccessToken kind where
UserAccessToken :: UserId -> AccessTokenData -> UTCTime -> AccessToken UserKind
AppAccessToken :: AccessTokenData -> AccessToken AppKind
-- | Type synonym for @'AccessToken' 'UserKind'@.
type UserAccessToken = AccessToken UserKind
-- | Type synonym for @'AccessToken' 'AppKind'@.
type AppAccessToken = AccessToken AppKind
deriving instance Eq (AccessToken kind)
deriving instance Ord (AccessToken kind)
deriving instance Show (AccessToken kind)
deriving instance Typeable1 AccessToken
-- | The access token data that is passed to Facebook's API
-- calls.
type AccessTokenData = Text
-- | The identification code of an object.
newtype Id = Id { idCode :: Text }
deriving (Eq, Ord, Show, Read, Typeable, IsString)
instance A.FromJSON Id where
parseJSON (A.Object v) = v A..: "id"
parseJSON (A.String s) = pure $ Id s
parseJSON (A.Number d) = pure $ Id $ from $ floor d
where from i = TL.toStrict $ TLB.toLazyText $ TLBI.decimal (i :: Int64)
parseJSON o = fail $ "Can't parse Facebook.Id from " ++ show o
instance A.ToJSON Id where
toJSON (Id t) = A.String t
-- | A Facebook user ID such as @1008905713901@.
type UserId = Id
-- | Get the access token data.
accessTokenData :: AccessToken anyKind -> AccessTokenData
accessTokenData (UserAccessToken _ d _) = d
accessTokenData (AppAccessToken d) = d
-- | Expire time of an access token. It may never expire, in
-- which case it will be @Nothing@.
accessTokenExpires :: AccessToken anyKind -> Maybe UTCTime
accessTokenExpires (UserAccessToken _ _ expt) = Just expt
accessTokenExpires (AppAccessToken _) = Nothing
-- | Get the user ID of an user access token.
accessTokenUserId :: UserAccessToken -> UserId
accessTokenUserId (UserAccessToken uid _ _) = uid
-- | Phantom type used mark an 'AccessToken' as an user access
-- token.
data UserKind deriving (Typeable)
-- | Phantom type used mark an 'AccessToken' as an app access
-- token.
data AppKind deriving (Typeable)
-- | An argument given to an API call.
type Argument = (ByteString, ByteString)
-- | Synonym for 'mappend'.
(<>) :: Monoid a => a -> a -> a
(<>) = mappend
----------------------------------------------------------------------
-- | /Since 0.14.9./ Not a Facebook JSON format, but a custom @fb@
-- format for convenience if you need to serialize access tokens.
instance A.ToJSON (AccessToken kind) where
toJSON (UserAccessToken uid data_ expires) =
A.object
[ "kind" A..= ("user" :: Text)
, "id" A..= uid
, "token" A..= data_
, "expires" A..= expires ]
toJSON (AppAccessToken data_) =
A.object
[ "kind" A..= ("app" :: Text)
, "token" A..= data_ ]
-- | (Internal) Since the user of 'parseJSON' is going to choose
-- via its @kind@ whether a 'UserAccessToken' or an
-- 'AppAccessToken' is wanted, we need this type class to
-- implement 'FromJSON'.
class ParseAccessToken kind where
parseTokenJSON :: A.Object -> A.Parser (AccessToken kind)
instance ParseAccessToken UserKind where
parseTokenJSON v =
checkKind v "user" $
UserAccessToken <$> v A..: "id"
<*> v A..: "token"
<*> v A..: "expires"
instance ParseAccessToken AppKind where
parseTokenJSON v =
checkKind v "app" $
AppAccessToken <$> v A..: "token"
-- | (Internal) Used to implement 'parseTokenJSON'.
checkKind :: A.Object -> Text -> A.Parser a -> A.Parser a
checkKind v kind ok = do
kind' <- v A..: "kind"
if kind == kind'
then ok
else fail $ "Expected access token kind " <> show kind <>
" but found " <> show kind' <> "."
-- | /Since 0.14.9./ Parses the format that 'ToJSON' produces.
-- Note that you need to statically decide whether you want to
-- parse a user access token or an app access token.
instance ParseAccessToken kind => A.FromJSON (AccessToken kind) where
parseJSON (A.Object v) = parseTokenJSON v
parseJSON _ = mzero
----------------------------------------------------------------------
-- | @newtype@ for 'UTCTime' that follows Facebook's
-- conventions of JSON parsing.
--
-- * As a string, while @aeson@ expects a format of @%FT%T%Q@,
-- Facebook gives time values formatted as @%FT%T%z@.
--
-- * As a number, 'FbUTCTime' accepts a number of seconds since
-- the Unix epoch.
newtype FbUTCTime = FbUTCTime { unFbUTCTime :: UTCTime }
deriving (Eq, Ord, Show, Read, Typeable)
instance A.FromJSON FbUTCTime where
parseJSON (A.String t) =
case parseTime defaultTimeLocale "%FT%T%z" (T.unpack t) of
Just d -> return (FbUTCTime d)
_ -> fail $ "could not parse FbUTCTime string " ++ show t
parseJSON (A.Number n) =
return $ FbUTCTime $ posixSecondsToUTCTime $ fromInteger $ floor n
parseJSON _ = fail "could not parse FbUTCTime from something which is not a string or number"