{-| Solana Beach API requests & responses.

TODO: Extract into a @solana-beach-api@ package.
-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Console.SolanaStaking.Api
    ( -- * Configuration
      Config(..)
    , mkConfig
      -- * Requests / Responses
    , APIResponse(..)
    , APIError(..)
    , raiseAPIError
      -- ** Get Stake Accounts
    , getAccountStakes
    , StakingAccounts(..)
    , StakingAccount(..)
      -- ** Get Staking Rewards
    , getAllStakeRewards
    , getStakeRewards
    , StakeReward(..)
      -- ** Get Block
    , getBlock
    , Block(..)
      -- * General API Types
    , Lamports(..)
    , renderLamports
    , scientificLamports
    , StakingPubKey(..)
    ) where

import           Control.Concurrent             ( threadDelay )
import           Control.Monad.Except           ( MonadError(throwError)
                                                , runExceptT
                                                )
import           Control.Monad.Reader           ( MonadIO
                                                , MonadReader
                                                , asks
                                                , liftIO
                                                )
import           Data.Aeson                     ( (.:)
                                                , (.:?)
                                                , FromJSON(parseJSON)
                                                , Value(Object)
                                                , withObject
                                                )
import           Data.Scientific                ( FPFormat(Fixed)
                                                , Scientific
                                                , formatScientific
                                                )
import           Data.Text.Encoding             ( encodeUtf8 )
import           Data.Time.Clock.POSIX          ( POSIXTime )
import           GHC.Generics                   ( Generic )
import           Network.HTTP.Req               ( (/:)
                                                , (/~)
                                                , GET(GET)
                                                , NoReqBody(NoReqBody)
                                                , Option
                                                , Scheme(Https)
                                                , Url
                                                , defaultHttpConfig
                                                , header
                                                , https
                                                , jsonResponse
                                                , queryParam
                                                , renderUrl
                                                , req
                                                , responseBody
                                                , runReq
                                                )
import           System.IO                      ( hPutStrLn
                                                , stderr
                                                )

import qualified Data.Text                     as T

-- | Solana Beach API Configuration
data Config = Config
    { Config -> Text
cApiKey        :: T.Text
    -- ^ Your API Key.
    -- Get one here: https://github.com/solana-beach/api
    , Config -> Text
cAccountPubKey :: T.Text
    -- ^ TODO: probably drop this when solana-beach-api is extracted to
    -- separate package.
    }
    deriving (Int -> Config -> ShowS
[Config] -> ShowS
Config -> String
(Int -> Config -> ShowS)
-> (Config -> String) -> ([Config] -> ShowS) -> Show Config
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Config] -> ShowS
$cshowList :: [Config] -> ShowS
show :: Config -> String
$cshow :: Config -> String
showsPrec :: Int -> Config -> ShowS
$cshowsPrec :: Int -> Config -> ShowS
Show, ReadPrec [Config]
ReadPrec Config
Int -> ReadS Config
ReadS [Config]
(Int -> ReadS Config)
-> ReadS [Config]
-> ReadPrec Config
-> ReadPrec [Config]
-> Read Config
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Config]
$creadListPrec :: ReadPrec [Config]
readPrec :: ReadPrec Config
$creadPrec :: ReadPrec Config
readList :: ReadS [Config]
$creadList :: ReadS [Config]
readsPrec :: Int -> ReadS Config
$creadsPrec :: Int -> ReadS Config
Read, Config -> Config -> Bool
(Config -> Config -> Bool)
-> (Config -> Config -> Bool) -> Eq Config
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Config -> Config -> Bool
$c/= :: Config -> Config -> Bool
== :: Config -> Config -> Bool
$c== :: Config -> Config -> Bool
Eq)

-- | Create a program config from the API key & the target account's
-- pubkey.
mkConfig :: String -> String -> Config
mkConfig :: String -> String -> Config
mkConfig (String -> Text
T.pack -> Text
cApiKey) (String -> Text
T.pack -> Text
cAccountPubKey) = Config :: Text -> Text -> Config
Config { Text
cAccountPubKey :: Text
cApiKey :: Text
cAccountPubKey :: Text
cApiKey :: Text
.. }

-- | Base URL to Solana Beach's API
baseUrl :: Url 'Https
baseUrl :: Url 'Https
baseUrl = Text -> Url 'Https
https Text
"api.solanabeach.io" Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"v1"


-- | Get the staking accounts for the 'cAccountPubKey'.
getAccountStakes
    :: (MonadIO m, MonadReader Config m) => m (APIResponse StakingAccounts)
getAccountStakes :: m (APIResponse StakingAccounts)
getAccountStakes = do
    Text
pubkey <- (Config -> Text) -> m Text
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Config -> Text
cAccountPubKey
    Url 'Https -> Option 'Https -> m (APIResponse StakingAccounts)
forall (m :: * -> *) a.
(MonadReader Config m, MonadIO m, FromJSON a) =>
Url 'Https -> Option 'Https -> m (APIResponse a)
getReq (Url 'Https
baseUrl Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"account" Url 'Https -> Text -> Url 'Https
forall a (scheme :: Scheme).
ToHttpApiData a =>
Url scheme -> a -> Url scheme
/~ Text
pubkey Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"stakes") Option 'Https
forall a. Monoid a => a
mempty

-- | Single Result Page of Staking Accounts Query.
data StakingAccounts = StakingAccounts
    { StakingAccounts -> [StakingAccount]
saResults    :: [StakingAccount]
    -- ^ The returned staking accounts
    , StakingAccounts -> Integer
saTotalPages :: Integer
    -- ^ The total number of pages for the Account's PubKey.
    }
    deriving (Int -> StakingAccounts -> ShowS
[StakingAccounts] -> ShowS
StakingAccounts -> String
(Int -> StakingAccounts -> ShowS)
-> (StakingAccounts -> String)
-> ([StakingAccounts] -> ShowS)
-> Show StakingAccounts
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StakingAccounts] -> ShowS
$cshowList :: [StakingAccounts] -> ShowS
show :: StakingAccounts -> String
$cshow :: StakingAccounts -> String
showsPrec :: Int -> StakingAccounts -> ShowS
$cshowsPrec :: Int -> StakingAccounts -> ShowS
Show, ReadPrec [StakingAccounts]
ReadPrec StakingAccounts
Int -> ReadS StakingAccounts
ReadS [StakingAccounts]
(Int -> ReadS StakingAccounts)
-> ReadS [StakingAccounts]
-> ReadPrec StakingAccounts
-> ReadPrec [StakingAccounts]
-> Read StakingAccounts
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [StakingAccounts]
$creadListPrec :: ReadPrec [StakingAccounts]
readPrec :: ReadPrec StakingAccounts
$creadPrec :: ReadPrec StakingAccounts
readList :: ReadS [StakingAccounts]
$creadList :: ReadS [StakingAccounts]
readsPrec :: Int -> ReadS StakingAccounts
$creadsPrec :: Int -> ReadS StakingAccounts
Read, StakingAccounts -> StakingAccounts -> Bool
(StakingAccounts -> StakingAccounts -> Bool)
-> (StakingAccounts -> StakingAccounts -> Bool)
-> Eq StakingAccounts
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: StakingAccounts -> StakingAccounts -> Bool
$c/= :: StakingAccounts -> StakingAccounts -> Bool
== :: StakingAccounts -> StakingAccounts -> Bool
$c== :: StakingAccounts -> StakingAccounts -> Bool
Eq, (forall x. StakingAccounts -> Rep StakingAccounts x)
-> (forall x. Rep StakingAccounts x -> StakingAccounts)
-> Generic StakingAccounts
forall x. Rep StakingAccounts x -> StakingAccounts
forall x. StakingAccounts -> Rep StakingAccounts x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep StakingAccounts x -> StakingAccounts
$cfrom :: forall x. StakingAccounts -> Rep StakingAccounts x
Generic)

instance FromJSON StakingAccounts where
    parseJSON :: Value -> Parser StakingAccounts
parseJSON = String
-> (Object -> Parser StakingAccounts)
-> Value
-> Parser StakingAccounts
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"StakingAccounts" ((Object -> Parser StakingAccounts)
 -> Value -> Parser StakingAccounts)
-> (Object -> Parser StakingAccounts)
-> Value
-> Parser StakingAccounts
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
        [StakingAccount]
saResults    <- Object
o Object -> Text -> Parser [StakingAccount]
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"data"
        Integer
saTotalPages <- Object
o Object -> Text -> Parser Integer
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"totalPages"
        StakingAccounts -> Parser StakingAccounts
forall (m :: * -> *) a. Monad m => a -> m a
return StakingAccounts :: [StakingAccount] -> Integer -> StakingAccounts
StakingAccounts { Integer
[StakingAccount]
saTotalPages :: Integer
saResults :: [StakingAccount]
saTotalPages :: Integer
saResults :: [StakingAccount]
.. }

-- | A single Staking Account.
data StakingAccount = StakingAccount
    { StakingAccount -> StakingPubKey
saPubKey        :: StakingPubKey
    -- ^ The Staking Accounts PubKey
    , StakingAccount -> Lamports
saLamports      :: Lamports
    -- ^ The Balance of the Staking Account
    , StakingAccount -> Text
saValidatorName :: T.Text
    -- ^ The Name of the Staking Account's Validator
    }
    deriving (Int -> StakingAccount -> ShowS
[StakingAccount] -> ShowS
StakingAccount -> String
(Int -> StakingAccount -> ShowS)
-> (StakingAccount -> String)
-> ([StakingAccount] -> ShowS)
-> Show StakingAccount
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StakingAccount] -> ShowS
$cshowList :: [StakingAccount] -> ShowS
show :: StakingAccount -> String
$cshow :: StakingAccount -> String
showsPrec :: Int -> StakingAccount -> ShowS
$cshowsPrec :: Int -> StakingAccount -> ShowS
Show, ReadPrec [StakingAccount]
ReadPrec StakingAccount
Int -> ReadS StakingAccount
ReadS [StakingAccount]
(Int -> ReadS StakingAccount)
-> ReadS [StakingAccount]
-> ReadPrec StakingAccount
-> ReadPrec [StakingAccount]
-> Read StakingAccount
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [StakingAccount]
$creadListPrec :: ReadPrec [StakingAccount]
readPrec :: ReadPrec StakingAccount
$creadPrec :: ReadPrec StakingAccount
readList :: ReadS [StakingAccount]
$creadList :: ReadS [StakingAccount]
readsPrec :: Int -> ReadS StakingAccount
$creadsPrec :: Int -> ReadS StakingAccount
Read, StakingAccount -> StakingAccount -> Bool
(StakingAccount -> StakingAccount -> Bool)
-> (StakingAccount -> StakingAccount -> Bool) -> Eq StakingAccount
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: StakingAccount -> StakingAccount -> Bool
$c/= :: StakingAccount -> StakingAccount -> Bool
== :: StakingAccount -> StakingAccount -> Bool
$c== :: StakingAccount -> StakingAccount -> Bool
Eq, (forall x. StakingAccount -> Rep StakingAccount x)
-> (forall x. Rep StakingAccount x -> StakingAccount)
-> Generic StakingAccount
forall x. Rep StakingAccount x -> StakingAccount
forall x. StakingAccount -> Rep StakingAccount x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep StakingAccount x -> StakingAccount
$cfrom :: forall x. StakingAccount -> Rep StakingAccount x
Generic)

instance FromJSON StakingAccount where
    parseJSON :: Value -> Parser StakingAccount
parseJSON = String
-> (Object -> Parser StakingAccount)
-> Value
-> Parser StakingAccount
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"StakingAccount" ((Object -> Parser StakingAccount)
 -> Value -> Parser StakingAccount)
-> (Object -> Parser StakingAccount)
-> Value
-> Parser StakingAccount
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
        StakingPubKey
saPubKey        <- Object
o Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"pubkey" Parser Object
-> (Object -> Parser StakingPubKey) -> Parser StakingPubKey
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Object -> Text -> Parser StakingPubKey
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"address")
        Lamports
saLamports      <- Object
o Object -> Text -> Parser Lamports
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"lamports"
        Text
saValidatorName <-
            Object
o
            Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.:  Text
"data"
            Parser Object -> (Object -> Parser Object) -> Parser Object
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"stake")
            Parser Object -> (Object -> Parser Object) -> Parser Object
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"delegation")
            Parser Object -> (Object -> Parser Object) -> Parser Object
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"validatorInfo")
            Parser Object -> (Object -> Parser Text) -> Parser Text
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Object -> Text -> Parser Text
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"name")
        StakingAccount -> Parser StakingAccount
forall (m :: * -> *) a. Monad m => a -> m a
return StakingAccount :: StakingPubKey -> Lamports -> Text -> StakingAccount
StakingAccount { Text
Lamports
StakingPubKey
saValidatorName :: Text
saLamports :: Lamports
saPubKey :: StakingPubKey
saValidatorName :: Text
saLamports :: Lamports
saPubKey :: StakingPubKey
.. }

-- | A PubKey for a Staking Account.
newtype StakingPubKey =
    StakingPubKey { StakingPubKey -> Text
fromStakingPubKey :: T.Text } deriving (Int -> StakingPubKey -> ShowS
[StakingPubKey] -> ShowS
StakingPubKey -> String
(Int -> StakingPubKey -> ShowS)
-> (StakingPubKey -> String)
-> ([StakingPubKey] -> ShowS)
-> Show StakingPubKey
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StakingPubKey] -> ShowS
$cshowList :: [StakingPubKey] -> ShowS
show :: StakingPubKey -> String
$cshow :: StakingPubKey -> String
showsPrec :: Int -> StakingPubKey -> ShowS
$cshowsPrec :: Int -> StakingPubKey -> ShowS
Show, ReadPrec [StakingPubKey]
ReadPrec StakingPubKey
Int -> ReadS StakingPubKey
ReadS [StakingPubKey]
(Int -> ReadS StakingPubKey)
-> ReadS [StakingPubKey]
-> ReadPrec StakingPubKey
-> ReadPrec [StakingPubKey]
-> Read StakingPubKey
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [StakingPubKey]
$creadListPrec :: ReadPrec [StakingPubKey]
readPrec :: ReadPrec StakingPubKey
$creadPrec :: ReadPrec StakingPubKey
readList :: ReadS [StakingPubKey]
$creadList :: ReadS [StakingPubKey]
readsPrec :: Int -> ReadS StakingPubKey
$creadsPrec :: Int -> ReadS StakingPubKey
Read, StakingPubKey -> StakingPubKey -> Bool
(StakingPubKey -> StakingPubKey -> Bool)
-> (StakingPubKey -> StakingPubKey -> Bool) -> Eq StakingPubKey
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: StakingPubKey -> StakingPubKey -> Bool
$c/= :: StakingPubKey -> StakingPubKey -> Bool
== :: StakingPubKey -> StakingPubKey -> Bool
$c== :: StakingPubKey -> StakingPubKey -> Bool
Eq, (forall x. StakingPubKey -> Rep StakingPubKey x)
-> (forall x. Rep StakingPubKey x -> StakingPubKey)
-> Generic StakingPubKey
forall x. Rep StakingPubKey x -> StakingPubKey
forall x. StakingPubKey -> Rep StakingPubKey x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep StakingPubKey x -> StakingPubKey
$cfrom :: forall x. StakingPubKey -> Rep StakingPubKey x
Generic, Value -> Parser [StakingPubKey]
Value -> Parser StakingPubKey
(Value -> Parser StakingPubKey)
-> (Value -> Parser [StakingPubKey]) -> FromJSON StakingPubKey
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
parseJSONList :: Value -> Parser [StakingPubKey]
$cparseJSONList :: Value -> Parser [StakingPubKey]
parseJSON :: Value -> Parser StakingPubKey
$cparseJSON :: Value -> Parser StakingPubKey
FromJSON)

-- | An amount of Lamports, each of which represent 0.000000001 SOL.
newtype Lamports =
    Lamports { Lamports -> Integer
fromLamports :: Integer } deriving (Int -> Lamports -> ShowS
[Lamports] -> ShowS
Lamports -> String
(Int -> Lamports -> ShowS)
-> (Lamports -> String) -> ([Lamports] -> ShowS) -> Show Lamports
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Lamports] -> ShowS
$cshowList :: [Lamports] -> ShowS
show :: Lamports -> String
$cshow :: Lamports -> String
showsPrec :: Int -> Lamports -> ShowS
$cshowsPrec :: Int -> Lamports -> ShowS
Show, ReadPrec [Lamports]
ReadPrec Lamports
Int -> ReadS Lamports
ReadS [Lamports]
(Int -> ReadS Lamports)
-> ReadS [Lamports]
-> ReadPrec Lamports
-> ReadPrec [Lamports]
-> Read Lamports
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Lamports]
$creadListPrec :: ReadPrec [Lamports]
readPrec :: ReadPrec Lamports
$creadPrec :: ReadPrec Lamports
readList :: ReadS [Lamports]
$creadList :: ReadS [Lamports]
readsPrec :: Int -> ReadS Lamports
$creadsPrec :: Int -> ReadS Lamports
Read, Lamports -> Lamports -> Bool
(Lamports -> Lamports -> Bool)
-> (Lamports -> Lamports -> Bool) -> Eq Lamports
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Lamports -> Lamports -> Bool
$c/= :: Lamports -> Lamports -> Bool
== :: Lamports -> Lamports -> Bool
$c== :: Lamports -> Lamports -> Bool
Eq, (forall x. Lamports -> Rep Lamports x)
-> (forall x. Rep Lamports x -> Lamports) -> Generic Lamports
forall x. Rep Lamports x -> Lamports
forall x. Lamports -> Rep Lamports x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Lamports x -> Lamports
$cfrom :: forall x. Lamports -> Rep Lamports x
Generic, Value -> Parser [Lamports]
Value -> Parser Lamports
(Value -> Parser Lamports)
-> (Value -> Parser [Lamports]) -> FromJSON Lamports
forall a.
(Value -> Parser a) -> (Value -> Parser [a]) -> FromJSON a
parseJSONList :: Value -> Parser [Lamports]
$cparseJSONList :: Value -> Parser [Lamports]
parseJSON :: Value -> Parser Lamports
$cparseJSON :: Value -> Parser Lamports
FromJSON)

-- | Render an amount of 'Lamports' as text, converting it to SOL.
renderLamports :: Lamports -> T.Text
renderLamports :: Lamports -> Text
renderLamports = String -> Text
T.pack (String -> Text) -> (Lamports -> String) -> Lamports -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. FPFormat -> Maybe Int -> Scientific -> String
formatScientific FPFormat
Fixed (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
9) (Scientific -> String)
-> (Lamports -> Scientific) -> Lamports -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Lamports -> Scientific
scientificLamports

-- | Convert Lamports into Scientific representation of SOL.
scientificLamports :: Lamports -> Scientific
scientificLamports :: Lamports -> Scientific
scientificLamports = (Scientific -> Scientific -> Scientific
forall a. Num a => a -> a -> a
* Scientific
0.000000001) (Scientific -> Scientific)
-> (Lamports -> Scientific) -> Lamports -> Scientific
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Integer -> Scientific
forall a. Num a => Integer -> a
fromInteger (Integer -> Scientific)
-> (Lamports -> Integer) -> Lamports -> Scientific
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Lamports -> Integer
fromLamports


-- | Get the staking rewards with a staking account's pubkey.
getStakeRewards
    :: (MonadIO m, MonadReader Config m)
    => StakingPubKey
    -> Maybe Integer
    -> m (APIResponse [StakeReward])
getStakeRewards :: StakingPubKey -> Maybe Integer -> m (APIResponse [StakeReward])
getStakeRewards (StakingPubKey Text
stakeAccountPubkey) Maybe Integer
mbEpochCursor = do
    Url 'Https -> Option 'Https -> m (APIResponse [StakeReward])
forall (m :: * -> *) a.
(MonadReader Config m, MonadIO m, FromJSON a) =>
Url 'Https -> Option 'Https -> m (APIResponse a)
getReq (Url 'Https
baseUrl Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"account" Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
stakeAccountPubkey Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"stake-rewards")
           (Text -> Maybe Integer -> Option 'Https
forall param a.
(QueryParam param, ToHttpApiData a) =>
Text -> Maybe a -> param
queryParam Text
"cursor" Maybe Integer
mbEpochCursor)

-- | Get all the staking rewards for the given account.
--
-- The API's @stake-rewards@ route only returns a maximum of 5 rewards, so
-- we have to use the earliest epoch as the @cursor@ in an additional
-- request to see if there are any more rewards.
getAllStakeRewards
    :: forall m
     . (MonadIO m, MonadReader Config m)
    => StakingPubKey
    -> m ([APIError], [StakeReward])
getAllStakeRewards :: StakingPubKey -> m ([APIError], [StakeReward])
getAllStakeRewards StakingPubKey
pubkey =
    StakingPubKey -> Maybe Integer -> m (APIResponse [StakeReward])
forall (m :: * -> *).
(MonadIO m, MonadReader Config m) =>
StakingPubKey -> Maybe Integer -> m (APIResponse [StakeReward])
getStakeRewards StakingPubKey
pubkey Maybe Integer
forall a. Maybe a
Nothing m (APIResponse [StakeReward])
-> (APIResponse [StakeReward] -> m (Either APIError [StakeReward]))
-> m (Either APIError [StakeReward])
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ExceptT APIError m [StakeReward]
-> m (Either APIError [StakeReward])
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT (ExceptT APIError m [StakeReward]
 -> m (Either APIError [StakeReward]))
-> (APIResponse [StakeReward] -> ExceptT APIError m [StakeReward])
-> APIResponse [StakeReward]
-> m (Either APIError [StakeReward])
forall b c a. (b -> c) -> (a -> b) -> a -> c
. APIResponse [StakeReward] -> ExceptT APIError m [StakeReward]
forall (m :: * -> *) a.
MonadError APIError m =>
APIResponse a -> m a
raiseAPIError m (Either APIError [StakeReward])
-> (Either APIError [StakeReward] -> m ([APIError], [StakeReward]))
-> m ([APIError], [StakeReward])
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ([APIError], [StakeReward])
-> Either APIError [StakeReward] -> m ([APIError], [StakeReward])
go
        ([], [])
  where
    go
        :: ([APIError], [StakeReward])
        -> Either APIError [StakeReward]
        -> m ([APIError], [StakeReward])
    go :: ([APIError], [StakeReward])
-> Either APIError [StakeReward] -> m ([APIError], [StakeReward])
go ([APIError]
errs, [StakeReward]
rws) = \case
        Left  APIError
err     -> ([APIError], [StakeReward]) -> m ([APIError], [StakeReward])
forall (m :: * -> *) a. Monad m => a -> m a
return (APIError
err APIError -> [APIError] -> [APIError]
forall a. a -> [a] -> [a]
: [APIError]
errs, [StakeReward]
rws)
        Right [StakeReward]
rewards -> if [StakeReward] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [StakeReward]
rewards Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
5
            then ([APIError], [StakeReward]) -> m ([APIError], [StakeReward])
forall (m :: * -> *) a. Monad m => a -> m a
return ([APIError]
errs, [StakeReward]
rewards [StakeReward] -> [StakeReward] -> [StakeReward]
forall a. Semigroup a => a -> a -> a
<> [StakeReward]
rws)
            else
                let minEpoch :: Integer
minEpoch = [Integer] -> Integer
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum ([Integer] -> Integer) -> [Integer] -> Integer
forall a b. (a -> b) -> a -> b
$ (StakeReward -> Integer) -> [StakeReward] -> [Integer]
forall a b. (a -> b) -> [a] -> [b]
map StakeReward -> Integer
srEpoch [StakeReward]
rewards
                in  StakingPubKey -> Maybe Integer -> m (APIResponse [StakeReward])
forall (m :: * -> *).
(MonadIO m, MonadReader Config m) =>
StakingPubKey -> Maybe Integer -> m (APIResponse [StakeReward])
getStakeRewards StakingPubKey
pubkey (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
minEpoch)
                    m (APIResponse [StakeReward])
-> (APIResponse [StakeReward] -> m (Either APIError [StakeReward]))
-> m (Either APIError [StakeReward])
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ExceptT APIError m [StakeReward]
-> m (Either APIError [StakeReward])
forall e (m :: * -> *) a. ExceptT e m a -> m (Either e a)
runExceptT
                    (ExceptT APIError m [StakeReward]
 -> m (Either APIError [StakeReward]))
-> (APIResponse [StakeReward] -> ExceptT APIError m [StakeReward])
-> APIResponse [StakeReward]
-> m (Either APIError [StakeReward])
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   APIResponse [StakeReward] -> ExceptT APIError m [StakeReward]
forall (m :: * -> *) a.
MonadError APIError m =>
APIResponse a -> m a
raiseAPIError
                    m (Either APIError [StakeReward])
-> (Either APIError [StakeReward] -> m ([APIError], [StakeReward]))
-> m ([APIError], [StakeReward])
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= ([APIError], [StakeReward])
-> Either APIError [StakeReward] -> m ([APIError], [StakeReward])
go ([APIError]
errs, [StakeReward]
rewards [StakeReward] -> [StakeReward] -> [StakeReward]
forall a. Semigroup a => a -> a -> a
<> [StakeReward]
rws)



-- | A Staking Reward Payment.
data StakeReward = StakeReward
    { StakeReward -> Integer
srEpoch     :: Integer
    -- ^ The Epoch the reward was paid.
    , StakeReward -> Integer
srSlot      :: Integer
    -- ^ The 'Block' number of the reward.
    , StakeReward -> Lamports
srAmount    :: Lamports
    -- ^ The total number of 'Lamports' awarded.
    , StakeReward -> POSIXTime
srTimestamp :: POSIXTime
    }
    deriving (Int -> StakeReward -> ShowS
[StakeReward] -> ShowS
StakeReward -> String
(Int -> StakeReward -> ShowS)
-> (StakeReward -> String)
-> ([StakeReward] -> ShowS)
-> Show StakeReward
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [StakeReward] -> ShowS
$cshowList :: [StakeReward] -> ShowS
show :: StakeReward -> String
$cshow :: StakeReward -> String
showsPrec :: Int -> StakeReward -> ShowS
$cshowsPrec :: Int -> StakeReward -> ShowS
Show, ReadPrec [StakeReward]
ReadPrec StakeReward
Int -> ReadS StakeReward
ReadS [StakeReward]
(Int -> ReadS StakeReward)
-> ReadS [StakeReward]
-> ReadPrec StakeReward
-> ReadPrec [StakeReward]
-> Read StakeReward
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [StakeReward]
$creadListPrec :: ReadPrec [StakeReward]
readPrec :: ReadPrec StakeReward
$creadPrec :: ReadPrec StakeReward
readList :: ReadS [StakeReward]
$creadList :: ReadS [StakeReward]
readsPrec :: Int -> ReadS StakeReward
$creadsPrec :: Int -> ReadS StakeReward
Read, StakeReward -> StakeReward -> Bool
(StakeReward -> StakeReward -> Bool)
-> (StakeReward -> StakeReward -> Bool) -> Eq StakeReward
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: StakeReward -> StakeReward -> Bool
$c/= :: StakeReward -> StakeReward -> Bool
== :: StakeReward -> StakeReward -> Bool
$c== :: StakeReward -> StakeReward -> Bool
Eq, (forall x. StakeReward -> Rep StakeReward x)
-> (forall x. Rep StakeReward x -> StakeReward)
-> Generic StakeReward
forall x. Rep StakeReward x -> StakeReward
forall x. StakeReward -> Rep StakeReward x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep StakeReward x -> StakeReward
$cfrom :: forall x. StakeReward -> Rep StakeReward x
Generic)

instance FromJSON StakeReward where
    parseJSON :: Value -> Parser StakeReward
parseJSON = String
-> (Object -> Parser StakeReward) -> Value -> Parser StakeReward
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"StakeReward" ((Object -> Parser StakeReward) -> Value -> Parser StakeReward)
-> (Object -> Parser StakeReward) -> Value -> Parser StakeReward
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
        Integer
srEpoch     <- Object
o Object -> Text -> Parser Integer
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"epoch"
        Integer
srSlot      <- Object
o Object -> Text -> Parser Integer
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"effectiveSlot"
        Lamports
srAmount    <- Object
o Object -> Text -> Parser Lamports
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"amount"
        POSIXTime
srTimestamp <- Object
o Object -> Text -> Parser POSIXTime
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"timestamp"
        StakeReward -> Parser StakeReward
forall (m :: * -> *) a. Monad m => a -> m a
return StakeReward :: Integer -> Integer -> Lamports -> POSIXTime -> StakeReward
StakeReward { Integer
POSIXTime
Lamports
srTimestamp :: POSIXTime
srAmount :: Lamports
srSlot :: Integer
srEpoch :: Integer
srTimestamp :: POSIXTime
srAmount :: Lamports
srSlot :: Integer
srEpoch :: Integer
.. }

-- | Get information about a specific block number.
getBlock
    :: (MonadIO m, MonadReader Config m) => Integer -> m (APIResponse Block)
getBlock :: Integer -> m (APIResponse Block)
getBlock Integer
blockNum = do
    Url 'Https -> Option 'Https -> m (APIResponse Block)
forall (m :: * -> *) a.
(MonadReader Config m, MonadIO m, FromJSON a) =>
Url 'Https -> Option 'Https -> m (APIResponse a)
getReq (Url 'Https
baseUrl Url 'Https -> Text -> Url 'Https
forall (scheme :: Scheme). Url scheme -> Text -> Url scheme
/: Text
"block" Url 'Https -> Integer -> Url 'Https
forall a (scheme :: Scheme).
ToHttpApiData a =>
Url scheme -> a -> Url scheme
/~ Integer
blockNum) Option 'Https
forall a. Monoid a => a
mempty

-- | A single block on the Solana blockchain.
data Block = Block
    { Block -> Integer
bNumber    :: Integer
    -- ^ The blocks number.
    , Block -> POSIXTime
bBlockTime :: POSIXTime
    -- ^ The blocks absolute timestamp.
    }
    deriving (Int -> Block -> ShowS
[Block] -> ShowS
Block -> String
(Int -> Block -> ShowS)
-> (Block -> String) -> ([Block] -> ShowS) -> Show Block
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [Block] -> ShowS
$cshowList :: [Block] -> ShowS
show :: Block -> String
$cshow :: Block -> String
showsPrec :: Int -> Block -> ShowS
$cshowsPrec :: Int -> Block -> ShowS
Show, ReadPrec [Block]
ReadPrec Block
Int -> ReadS Block
ReadS [Block]
(Int -> ReadS Block)
-> ReadS [Block]
-> ReadPrec Block
-> ReadPrec [Block]
-> Read Block
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [Block]
$creadListPrec :: ReadPrec [Block]
readPrec :: ReadPrec Block
$creadPrec :: ReadPrec Block
readList :: ReadS [Block]
$creadList :: ReadS [Block]
readsPrec :: Int -> ReadS Block
$creadsPrec :: Int -> ReadS Block
Read, Block -> Block -> Bool
(Block -> Block -> Bool) -> (Block -> Block -> Bool) -> Eq Block
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Block -> Block -> Bool
$c/= :: Block -> Block -> Bool
== :: Block -> Block -> Bool
$c== :: Block -> Block -> Bool
Eq, (forall x. Block -> Rep Block x)
-> (forall x. Rep Block x -> Block) -> Generic Block
forall x. Rep Block x -> Block
forall x. Block -> Rep Block x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep Block x -> Block
$cfrom :: forall x. Block -> Rep Block x
Generic)

instance FromJSON Block where
    parseJSON :: Value -> Parser Block
parseJSON = String -> (Object -> Parser Block) -> Value -> Parser Block
forall a. String -> (Object -> Parser a) -> Value -> Parser a
withObject String
"Block" ((Object -> Parser Block) -> Value -> Parser Block)
-> (Object -> Parser Block) -> Value -> Parser Block
forall a b. (a -> b) -> a -> b
$ \Object
o -> do
        Integer
bNumber    <- Object
o Object -> Text -> Parser Integer
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"blocknumber"
        POSIXTime
bBlockTime <-
            (Object
o Object -> Text -> Parser Object
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"blocktime")
            Parser Object -> (Object -> Parser POSIXTime) -> Parser POSIXTime
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= (Scientific -> POSIXTime) -> Parser Scientific -> Parser POSIXTime
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (Integer -> POSIXTime
forall a. Num a => Integer -> a
fromInteger (Integer -> POSIXTime)
-> (Scientific -> Integer) -> Scientific -> POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (forall b. (RealFrac Scientific, Integral b) => Scientific -> b
forall a b. (RealFrac a, Integral b) => a -> b
truncate @Scientific))
            (Parser Scientific -> Parser POSIXTime)
-> (Object -> Parser Scientific) -> Object -> Parser POSIXTime
forall b c a. (b -> c) -> (a -> b) -> a -> c
.   (Object -> Text -> Parser Scientific
forall a. FromJSON a => Object -> Text -> Parser a
.: Text
"absolute")
        Block -> Parser Block
forall (m :: * -> *) a. Monad m => a -> m a
return Block :: Integer -> POSIXTime -> Block
Block { Integer
POSIXTime
bBlockTime :: POSIXTime
bNumber :: Integer
bBlockTime :: POSIXTime
bNumber :: Integer
.. }


-- | Generic GET request to the Solana Beach API with up to 5 retries for
-- @ProcessingResponse@.
--
-- Note: Prints to 'stderr' when waiting for request to finish processing.
getReq
    :: forall m a
     . (MonadReader Config m, MonadIO m, FromJSON a)
    => Url 'Https
    -> Option 'Https
    -> m (APIResponse a)
getReq :: Url 'Https -> Option 'Https -> m (APIResponse a)
getReq Url 'Https
endpoint Option 'Https
options = Integer -> m (APIResponse a)
fetchWithRetries Integer
0
  where
    maxRetries :: Integer
    maxRetries :: Integer
maxRetries = Integer
5
    fetchWithRetries :: Integer -> m (APIResponse a)
    fetchWithRetries :: Integer -> m (APIResponse a)
fetchWithRetries Integer
retryCount = if Integer
retryCount Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
maxRetries
        then APIResponse a -> m (APIResponse a)
forall (m :: * -> *) a. Monad m => a -> m a
return (APIResponse a -> m (APIResponse a))
-> APIResponse a -> m (APIResponse a)
forall a b. (a -> b) -> a -> b
$ APIError -> APIResponse a
forall a. APIError -> APIResponse a
ErrorResponse (APIError -> APIResponse a) -> APIError -> APIResponse a
forall a b. (a -> b) -> a -> b
$ Text -> APIError
RetriesExceeded (Text -> APIError) -> Text -> APIError
forall a b. (a -> b) -> a -> b
$ Url 'Https -> Text
forall (scheme :: Scheme). Url scheme -> Text
renderUrl Url 'Https
endpoint
        else do
            Text
apikey <- (Config -> Text) -> m Text
forall r (m :: * -> *) a. MonadReader r m => (r -> a) -> m a
asks Config -> Text
cApiKey
            let authHeader :: Option scheme
authHeader =
                    ByteString -> ByteString -> Option scheme
forall (scheme :: Scheme).
ByteString -> ByteString -> Option scheme
header ByteString
"Authorization" (ByteString -> Option scheme) -> ByteString -> Option scheme
forall a b. (a -> b) -> a -> b
$ ByteString
"Bearer: " ByteString -> ByteString -> ByteString
forall a. Semigroup a => a -> a -> a
<> Text -> ByteString
encodeUtf8 Text
apikey
            APIResponse a
respBody <- JsonResponse (APIResponse a) -> APIResponse a
forall response.
HttpResponse response =>
response -> HttpResponseBody response
responseBody (JsonResponse (APIResponse a) -> APIResponse a)
-> m (JsonResponse (APIResponse a)) -> m (APIResponse a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> HttpConfig
-> Req (JsonResponse (APIResponse a))
-> m (JsonResponse (APIResponse a))
forall (m :: * -> *) a. MonadIO m => HttpConfig -> Req a -> m a
runReq
                HttpConfig
defaultHttpConfig
                (GET
-> Url 'Https
-> NoReqBody
-> Proxy (JsonResponse (APIResponse a))
-> Option 'Https
-> Req (JsonResponse (APIResponse a))
forall (m :: * -> *) method body response (scheme :: Scheme).
(MonadHttp m, HttpMethod method, HttpBody body,
 HttpResponse response,
 HttpBodyAllowed (AllowsBody method) (ProvidesBody body)) =>
method
-> Url scheme
-> body
-> Proxy response
-> Option scheme
-> m response
req GET
GET Url 'Https
endpoint NoReqBody
NoReqBody Proxy (JsonResponse (APIResponse a))
forall a. Proxy (JsonResponse a)
jsonResponse (Option 'Https -> Req (JsonResponse (APIResponse a)))
-> Option 'Https -> Req (JsonResponse (APIResponse a))
forall a b. (a -> b) -> a -> b
$ Option 'Https
forall (scheme :: Scheme). Option scheme
authHeader Option 'Https -> Option 'Https -> Option 'Https
forall a. Semigroup a => a -> a -> a
<> Option 'Https
options
                )
            case APIResponse a
respBody of
                APIResponse a
ProcessingResponse -> do
                    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Handle -> String -> IO ()
hPutStrLn
                        Handle
stderr
                        String
"Waiting for API to finish processing request..."
                    IO () -> m ()
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (IO () -> m ()) -> IO () -> m ()
forall a b. (a -> b) -> a -> b
$ Int -> IO ()
threadDelay (Int -> IO ()) -> Int -> IO ()
forall a b. (a -> b) -> a -> b
$ Int
10 Int -> Int -> Int
forall a. Num a => a -> a -> a
* Int
1000000
                    Integer -> m (APIResponse a)
fetchWithRetries (Integer
retryCount Integer -> Integer -> Integer
forall a. Num a => a -> a -> a
+ Integer
1)
                APIResponse a
_ -> APIResponse a -> m (APIResponse a)
forall (m :: * -> *) a. Monad m => a -> m a
return APIResponse a
respBody




-- | Wrapper around error & processing responses from the API.
data APIResponse a
    = SuccessfulReponse a
    | ProcessingResponse
    | ErrorResponse APIError
    deriving (Int -> APIResponse a -> ShowS
[APIResponse a] -> ShowS
APIResponse a -> String
(Int -> APIResponse a -> ShowS)
-> (APIResponse a -> String)
-> ([APIResponse a] -> ShowS)
-> Show (APIResponse a)
forall a. Show a => Int -> APIResponse a -> ShowS
forall a. Show a => [APIResponse a] -> ShowS
forall a. Show a => APIResponse a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [APIResponse a] -> ShowS
$cshowList :: forall a. Show a => [APIResponse a] -> ShowS
show :: APIResponse a -> String
$cshow :: forall a. Show a => APIResponse a -> String
showsPrec :: Int -> APIResponse a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> APIResponse a -> ShowS
Show, ReadPrec [APIResponse a]
ReadPrec (APIResponse a)
Int -> ReadS (APIResponse a)
ReadS [APIResponse a]
(Int -> ReadS (APIResponse a))
-> ReadS [APIResponse a]
-> ReadPrec (APIResponse a)
-> ReadPrec [APIResponse a]
-> Read (APIResponse a)
forall a. Read a => ReadPrec [APIResponse a]
forall a. Read a => ReadPrec (APIResponse a)
forall a. Read a => Int -> ReadS (APIResponse a)
forall a. Read a => ReadS [APIResponse a]
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [APIResponse a]
$creadListPrec :: forall a. Read a => ReadPrec [APIResponse a]
readPrec :: ReadPrec (APIResponse a)
$creadPrec :: forall a. Read a => ReadPrec (APIResponse a)
readList :: ReadS [APIResponse a]
$creadList :: forall a. Read a => ReadS [APIResponse a]
readsPrec :: Int -> ReadS (APIResponse a)
$creadsPrec :: forall a. Read a => Int -> ReadS (APIResponse a)
Read, APIResponse a -> APIResponse a -> Bool
(APIResponse a -> APIResponse a -> Bool)
-> (APIResponse a -> APIResponse a -> Bool) -> Eq (APIResponse a)
forall a. Eq a => APIResponse a -> APIResponse a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: APIResponse a -> APIResponse a -> Bool
$c/= :: forall a. Eq a => APIResponse a -> APIResponse a -> Bool
== :: APIResponse a -> APIResponse a -> Bool
$c== :: forall a. Eq a => APIResponse a -> APIResponse a -> Bool
Eq)

-- | Attempts to parse a processing response, then an error response,
-- & finally the inner @a@ response.
instance FromJSON a => FromJSON (APIResponse a) where
    parseJSON :: Value -> Parser (APIResponse a)
parseJSON Value
v = case Value
v of
        Object Object
o -> do
            Object
o Object -> Text -> Parser (Maybe Text)
forall a. FromJSON a => Object -> Text -> Parser (Maybe a)
.:? Text
"err" Parser (Maybe Text)
-> (Maybe Text -> Parser (APIResponse a)) -> Parser (APIResponse a)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
                Just Text
errMsg -> Object
o Object -> Text -> Parser (Maybe Bool)
forall a. FromJSON a => Object -> Text -> Parser (Maybe a)
.:? Text
"processing" Parser (Maybe Bool)
-> (Maybe Bool -> Parser (APIResponse a)) -> Parser (APIResponse a)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
                    Maybe Bool
Nothing          -> APIResponse a -> Parser (APIResponse a)
forall (m :: * -> *) a. Monad m => a -> m a
return (APIResponse a -> Parser (APIResponse a))
-> APIResponse a -> Parser (APIResponse a)
forall a b. (a -> b) -> a -> b
$ APIError -> APIResponse a
forall a. APIError -> APIResponse a
ErrorResponse (APIError -> APIResponse a) -> APIError -> APIResponse a
forall a b. (a -> b) -> a -> b
$ Text -> APIError
APIError Text
errMsg
                    Just (Bool
_ :: Bool) -> APIResponse a -> Parser (APIResponse a)
forall (m :: * -> *) a. Monad m => a -> m a
return APIResponse a
forall a. APIResponse a
ProcessingResponse
                Maybe Text
Nothing -> (a -> APIResponse a) -> Parser a -> Parser (APIResponse a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap a -> APIResponse a
forall a. a -> APIResponse a
SuccessfulReponse (Parser a -> Parser (APIResponse a))
-> (Value -> Parser a) -> Value -> Parser (APIResponse a)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Value -> Parser a
forall a. FromJSON a => Value -> Parser a
parseJSON (Value -> Parser (APIResponse a))
-> Value -> Parser (APIResponse a)
forall a b. (a -> b) -> a -> b
$ Object -> Value
Object Object
o
        Value
_ -> a -> APIResponse a
forall a. a -> APIResponse a
SuccessfulReponse (a -> APIResponse a) -> Parser a -> Parser (APIResponse a)
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Value -> Parser a
forall a. FromJSON a => Value -> Parser a
parseJSON Value
v

-- | Pull the inner value out of an 'APIResponse' or throw the respective
-- 'APIError'.
raiseAPIError :: MonadError APIError m => APIResponse a -> m a
raiseAPIError :: APIResponse a -> m a
raiseAPIError = \case
    SuccessfulReponse a
v -> a -> m a
forall (m :: * -> *) a. Monad m => a -> m a
return a
v
    APIResponse a
ProcessingResponse ->
        APIError -> m a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError (APIError -> m a) -> APIError -> m a
forall a b. (a -> b) -> a -> b
$ Text -> APIError
APIError Text
"Request cancelled during processing wait."
    ErrorResponse APIError
err -> APIError -> m a
forall e (m :: * -> *) a. MonadError e m => e -> m a
throwError APIError
err


-- | Potential error responses from the Solana Beach API.
data APIError
    = APIError T.Text
    -- ^ Generic API error with message.
    | RetriesExceeded T.Text
    -- ^ Exceeded maximum number of 'ProcessingResponse' retries.
    deriving (Int -> APIError -> ShowS
[APIError] -> ShowS
APIError -> String
(Int -> APIError -> ShowS)
-> (APIError -> String) -> ([APIError] -> ShowS) -> Show APIError
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [APIError] -> ShowS
$cshowList :: [APIError] -> ShowS
show :: APIError -> String
$cshow :: APIError -> String
showsPrec :: Int -> APIError -> ShowS
$cshowsPrec :: Int -> APIError -> ShowS
Show, ReadPrec [APIError]
ReadPrec APIError
Int -> ReadS APIError
ReadS [APIError]
(Int -> ReadS APIError)
-> ReadS [APIError]
-> ReadPrec APIError
-> ReadPrec [APIError]
-> Read APIError
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
readListPrec :: ReadPrec [APIError]
$creadListPrec :: ReadPrec [APIError]
readPrec :: ReadPrec APIError
$creadPrec :: ReadPrec APIError
readList :: ReadS [APIError]
$creadList :: ReadS [APIError]
readsPrec :: Int -> ReadS APIError
$creadsPrec :: Int -> ReadS APIError
Read, APIError -> APIError -> Bool
(APIError -> APIError -> Bool)
-> (APIError -> APIError -> Bool) -> Eq APIError
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: APIError -> APIError -> Bool
$c/= :: APIError -> APIError -> Bool
== :: APIError -> APIError -> Bool
$c== :: APIError -> APIError -> Bool
Eq, (forall x. APIError -> Rep APIError x)
-> (forall x. Rep APIError x -> APIError) -> Generic APIError
forall x. Rep APIError x -> APIError
forall x. APIError -> Rep APIError x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cto :: forall x. Rep APIError x -> APIError
$cfrom :: forall x. APIError -> Rep APIError x
Generic)