cryptsy-api 0.1 → 0.2
raw patch · 6 files changed
+108/−82 lines, 6 filesdep +http-clientdep +http-client-tlsdep +pipes-attoparsecdep −HTTPdep −errorsdep −networkdep ~aesondep ~eitherdep ~text
Dependencies added: http-client, http-client-tls, pipes-attoparsec, pipes-http
Dependencies removed: HTTP, errors, network
Dependency ranges changed: aeson, either, text
Files
- cryptsy-api.cabal +11/−10
- src/Cryptsy/API/Public.hs +43/−0
- src/Cryptsy/API/Public/Internal.hs +36/−26
- src/Cryptsy/API/Public/Types/Error.hs +8/−8
- src/Cryptsy/API/Public/Types/Monad.hs +8/−8
- src/Main.hs +2/−30
cryptsy-api.cabal view
@@ -1,5 +1,5 @@ name: cryptsy-api-version: 0.1+version: 0.2 synopsis: Bindings for Cryptsy cryptocurrency exchange API. description: Bindings for Cryptsy cryptocurrency exchange API. license: OtherLicense@@ -39,18 +39,19 @@ , Cryptsy.API.Public.Types.Internal build-depends: base ==4.6.*- , HTTP ==4000.2.*- , either ==4.0.*- , text ==0.11.*- , transformers ==0.3.*- , unordered-containers ==0.2.*- , vector ==0.10.*- , aeson ==0.6.*- , errors ==1.4.*- , network ==2.4.*+ , aeson >= 0.6 && < 0.8 , bytestring ==0.10.* , deepseq ==1.3.*+ , either >= 4.0 && < 4.2+ , http-client ==0.2.*+ , http-client-tls ==0.2.* , old-locale ==1.0.*+ , pipes-attoparsec ==0.4.*+ , pipes-http ==1.0.*+ , text >= 0.11 && < 1.2 , time ==1.4.*+ , transformers ==0.3.*+ , unordered-containers ==0.2.*+ , vector ==0.10.* hs-source-dirs: src
src/Cryptsy/API/Public.hs view
@@ -6,12 +6,55 @@ , module Cryptsy.API.Public.Market , module Cryptsy.API.Public.OrderData , module Cryptsy.API.Public.OrderBook+ , defaultEvalPubCryptsy+ , defaultRunPubCryptsy ) where +-- base+import Data.Maybe (fromMaybe)+import Data.Monoid (mempty)++-- either+import Control.Monad.Trans.Either (runEitherT)++-- http-client+import Network.HTTP.Client (CookieJar, withManager)++-- http-client-tls+import Network.HTTP.Client.TLS (tlsManagerSettings)++-- transformers+import Control.Monad.Trans.Reader (runReaderT)+import Control.Monad.Trans.State (runStateT)++-- this package import Cryptsy.API.Public.Market import Cryptsy.API.Public.MarketData.New import Cryptsy.API.Public.MarketData.Old import Cryptsy.API.Public.OrderBook import Cryptsy.API.Public.OrderData import Cryptsy.API.Public.Types++{-|+Like 'defaultRunPubCryptsy', but the resulting cookir jar is ignored.+-}+defaultEvalPubCryptsy :: PubCryptsy a -> IO (Either CryptsyError a)+defaultEvalPubCryptsy = fmap fst . defaultRunPubCryptsy+{-# INLINABLE defaultEvalPubCryptsy #-}++{-|+Converts a public cryptsy action to an action in the IO Monad, using a new+manager created from 'tlsManagerSettings' and closed as the last part of the IO+action and starting from an empty cookie jar. Both the resulting cookie jar+and either the resolt of the public cryptsy action or the first cryptsy error+encountered are the results of the IO action.+-}+defaultRunPubCryptsy :: PubCryptsy a -> IO (Either CryptsyError a, CookieJar)+defaultRunPubCryptsy = withManager tlsManagerSettings+ . (defaultRunInReader .) . runReaderT+ where+ defaultRunInReader = fmap (msnd (fromMaybe mempty)) . ($ Just mempty)+ . runStateT . runEitherT+ msnd f (x, y) = (x, f y)+{-# INLINABLE defaultRunPubCryptsy #-}
src/Cryptsy/API/Public/Internal.hs view
@@ -2,29 +2,36 @@ -- |Implementations shared across two or more modules. module Cryptsy.API.Public.Internal where --- HTTP-import Network.Browser (request)-import Network.HTTP.Base (defaultGETRequest_, rspBody, rspCode)+-- base+import Control.Exception (try)+import Data.Functor ((<$)) -- aeson-import Data.Aeson (Value(Object), eitherDecode, withObject)+import Data.Aeson (Value(Object), withObject, json') import Data.Aeson.Types (Parser, parseEither) -- either-import Control.Monad.Trans.Either (hoistEither, left, right)+import Control.Monad.Trans.Either (EitherT(..), hoistEither) import Data.Either.Combinators (mapLeft) --- errors-import Control.Error.Util ((??))+-- http-client+import Network.HTTP.Client+ ( cookieJar, parseUrl, responseBody, responseCookieJar+ ) --- network-import Network.URI (parseAbsoluteURI)+-- pipes-attoparsec+import Pipes.Attoparsec (parse) +-- pipes-http+import Pipes.HTTP (withHTTP)+ -- text import Data.Text (Text, pack) -- transformers-import Control.Monad.Trans.Class (lift)+import Control.Monad.Trans.Reader (ReaderT(..))+import Control.Monad.Trans.State (StateT(..))+import Control.Monad.Trans.State.Strict (evalStateT) -- unordered-containers import qualified Data.HashMap.Strict as HM (lookup)@@ -55,22 +62,25 @@ pubCryptsy :: String -- ^ URL -> (Value -> Parser a) -> PubCryptsy a-pubCryptsy apiurl parser = do- uri <- parseAbsoluteURI apiurl ?? BadURL apiurl- let req = defaultGETRequest_ uri- (_, resp) <- lift $ request req- bodyBytes <- case rspCode resp of- (2, 0, 0) -> right $ rspBody resp- _ -> left $ BadResponse resp- valueJSON <- hoistEither . mapLeft (FailParseResponse bodyBytes)- $ eitherDecode bodyBytes- returnData <- case valueJSON of- Object (HM.lookup dataKey -> Just dat) -> right dat- Object (HM.lookup errMsgKey -> Just errMsg) ->- left $ ErrorResponse errMsg- _ -> left $ UnsuccessfulResponse valueJSON- hoistEither . mapLeft (FailParseReturn returnData)- $ parseEither parser returnData+pubCryptsy apiurl parser = ReaderT $ \manager -> do+ reqSansCookies <- hoistEither . mapLeft (BadURL apiurl) $ parseUrl apiurl+ parseResult <- EitherT . StateT $ \beforeCookies -> do+ let req = reqSansCookies { cookieJar = beforeCookies }+ thttp <- try . withHTTP req manager $ \resp -> do+ tpr <- try . evalStateT (parse json') $ responseBody resp -- discard lo+ return (tpr, responseCookieJar resp <$ beforeCookies)+ return $ case thttp of+ Left he -> (Left $ FailReadResponse req he, beforeCookies)+ Right (Left he, nc) -> (Left $ FailReadResponse req he, nc)+ Right (Right pr, nc) -> (Right pr, nc)+ hoistEither $ do+ value <- mapLeft FailParseResponse parseResult+ dat <- case value of+ Object (HM.lookup dataKey -> Just d) -> Right d+ Object (HM.lookup errMsgKey -> Just errMsg) ->+ Left $ ErrorResponse errMsg+ _ -> Left $ UnsuccessfulResponse value+ mapLeft (FailParseReturn dat) $ parseEither parser dat {-# INLINABLE pubCryptsy #-} -- |unpacked 'marketsKey'
src/Cryptsy/API/Public/Types/Error.hs view
@@ -1,19 +1,19 @@ -- |Sum type for any error conditions in a API request. module Cryptsy.API.Public.Types.Error where --- HTTP-import Network.HTTP.Base (Response)- -- aeson import Data.Aeson (Value) --- bytestring-import Data.ByteString.Lazy (ByteString)+-- http-client+import Network.HTTP.Client (HttpException, Request) +-- pipes-attoparsec+import Pipes.Attoparsec (ParsingError)+ -- |error conditions w/ debugging information for an API request-data CryptsyError = BadURL { badURL :: String }- | BadResponse { badResponse :: Response ByteString }- | FailParseResponse { responseBody :: ByteString, errorMessage :: String }+data CryptsyError = BadURL { badURL :: String, httpException :: HttpException }+ | FailReadResponse { request :: Request, httpException :: HttpException }+ | FailParseResponse { parsingError :: ParsingError } | ErrorResponse { errorValue :: Value } | UnsuccessfulResponse { jsonResponse :: Value } | FailParseReturn { dataValue :: Value, errorMessage :: String }
src/Cryptsy/API/Public/Types/Monad.hs view
@@ -5,18 +5,18 @@ ) where --- HTTP-import Network.Browser (BrowserAction)-import Network.TCP (HandleStream)---- bytestring-import Data.ByteString.Lazy (ByteString)- -- either import Control.Monad.Trans.Either (EitherT) +-- http-client+import Network.HTTP.Client (CookieJar, Manager)++-- transformers+import Control.Monad.Trans.Reader (ReaderT)+import Control.Monad.Trans.State (StateT)+ -- this package import Cryptsy.API.Public.Types.Error -- |request monad-type PubCryptsy = EitherT CryptsyError (BrowserAction (HandleStream ByteString))+type PubCryptsy = ReaderT Manager (EitherT CryptsyError (StateT (Maybe CookieJar) IO))
src/Main.hs view
@@ -1,27 +1,13 @@-module Main where+module Main (main) where -- base import Control.Monad ((<=<)) --- HTTP-import Network.HTTP.Base (httpPackageVersion)--import Network.Browser- ( Proxy(NoProxy), BrowserAction- , browse, setAllowRedirects, setMaxRedirects, setAuthorities- , setAllowBasicAuth, setMaxErrorRetries, setMaxPoolSize- , setMaxAuthAttempts, setProxy, setOutHandler, setUserAgent- )---- either-import Control.Monad.Trans.Either (runEitherT)- -- text import Data.Text (pack) -- transformers import Control.Monad.IO.Class (liftIO)-import Control.Monad.Trans.Class (lift) -- unordered-containers import qualified Data.HashMap.Strict as HM@@ -32,22 +18,8 @@ -- this package import Cryptsy.API.Public -initBrowser :: BrowserAction t ()-initBrowser = do- setAllowRedirects True- setMaxRedirects $ Just 10- setAuthorities []- setAllowBasicAuth False- setMaxErrorRetries $ Just 0- setMaxPoolSize $ Just 2- setMaxAuthAttempts $ Just 0- setProxy NoProxy- setOutHandler . const $ return ()- setUserAgent $ "Network.Browser/" ++ httpPackageVersion- main :: IO ()-main = either print return <=< browse . runEitherT $ do- lift initBrowser+main = either print return <=< defaultEvalPubCryptsy $ do books <- orderData liftIO . print . HM.size $ orderBooks (books :: OrderData) omkts <- oldMarketData