1 {-# LANGUAGE FlexibleInstances #-}
    2 {-# LANGUAGE OverloadedStrings #-}
    3 
    4 module Network.Craze.Types where
    5 
    6 import Control.Exception (SomeException)
    7 
    8 import Data.ByteString    (ByteString)
    9 import Data.Default.Class (Default, def)
   10 import Data.Text          (Text)
   11 import Network.Curl       (CurlOption, CurlResponse_, respBody)
   12 
   13 -- | A 'RacerHandler' is simply a function for transforming a response after it
   14 -- is received. The handler is only applied to successful requests before they
   15 -- are checked by the 'RacerChecker'.
   16 --
   17 -- This is primarily for extracting or parsing a 'CurlResponse_' before doing
   18 -- any further work. The type returned by the handler will be used as the
   19 -- input of the checker and will be the return type of functions like
   20 -- 'raceGet'.
   21 --
   22 type RacerHandler headerTy bodyTy a = CurlResponse_ headerTy bodyTy -> IO a
   23 
   24 -- | A function that computes whether or not a result is valid or not.
   25 --
   26 -- A racer will discard successful responses it get from its clients if they do
   27 -- not pass the checker.
   28 --
   29 -- This step allows the racer to potentially discard responses that, while
   30 -- technically successful, do not contain the expected result (e.g. APIs that
   31 -- return errors as HTTP 200s, rate limitting messages, or unexpected formats).
   32 --
   33 type RacerChecker a = a -> Bool
   34 
   35 -- | A provider is simply a factory function for 'ProviderOptions', which are
   36 -- used to configure a client.
   37 type RacerProvider = IO ProviderOptions
   38 
   39 -- | Configuration used to set up an individual client in the race.
   40 data ProviderOptions = ProviderOptions
   41   { -- | Options to pass down to Curl.
   42     poOptions :: [CurlOption]
   43     -- | Number of microseconds to delay the request by.
   44     --
   45     -- Delays can be used to give other clients a headstart. This is useful
   46     -- in cases were some clients are more costly to use than others (e.g.
   47     -- Bandwidth costs, resource usage, etc).
   48     --
   49   , poDelay   :: Maybe Int
   50     -- | A tag to identify this type provider.
   51     --
   52     -- Tags are not required to be unique, but they are generally more helpful
   53     -- if they are.
   54   , poTag     :: Text
   55   } deriving (Show)
   56 
   57 instance Default ProviderOptions where
   58   def = ProviderOptions
   59     { poOptions = []
   60     , poDelay = Nothing
   61     , poTag = "default"
   62     }
   63 
   64 -- | The status of running a single client.
   65 data ClientStatus a
   66   -- | A successful response (passed the checker). A race will usually only
   67   -- have one successful response.
   68   = Successful a
   69   -- | A response that was received but failed to pass the checker.
   70   | Failed a
   71   -- | An exception thrown while using the client.
   72   | Errored SomeException
   73   -- | The operation is still pending, was cancelled, or was never started.
   74   | Pending
   75   deriving (Show)
   76 
   77 -- | The result of a racing operation. This can be used to collect statistics
   78 -- on which providers win more often, etc.
   79 data RacerResult a = RacerResult
   80   { rrResponse  :: Maybe a
   81   , rrWinner    :: Maybe ProviderOptions
   82   , rrProviders :: [RacerProvider]
   83   , rrStatuses  :: [(Text, ClientStatus a)]
   84   }
   85 
   86 -- | A record describing the rules for racing requests.
   87 data Racer headerTy bodyTy a = Racer
   88   { racerHandler    :: RacerHandler headerTy bodyTy a
   89   , racerChecker    :: RacerChecker a
   90   -- | On a `Racer`, each `RaceProvider` represents a separate client
   91   -- configuration. When performing a race, each provider will be used to spwan
   92   -- a client and perform a request. This allows one to control the number of
   93   -- requests performed and with which `CurlOption`s.
   94   , racerProviders  :: [RacerProvider]
   95   -- | When set to `True`, debugging messages will be written to stdout.
   96   , racerDebug      :: Bool
   97   -- | When set to `True`, the Racer will attempt to return the last response
   98   -- in the event that all responses failed to pass the checker. This can be
   99   -- used for identifying error conditions.
  100   , racerReturnLast :: Bool
  101   }
  102 
  103 instance Default (Racer [(String,String)] ByteString ByteString) where
  104   def = Racer
  105     { racerHandler = pure . respBody
  106     , racerChecker = const True
  107     , racerProviders = []
  108     , racerDebug = False
  109     , racerReturnLast = False
  110     }