1 {-# LANGUAGE ViewPatterns, GeneralizedNewtypeDeriving #-}
2 module Cryptsy
.API
.Public
3 ( CryptsyError
(..), CryptsyNum
(..), E8
, PubCryptsy
4 , parseCryptsyNum
, pubCryptsy
, pubURL
5 , module Network
.HTTP
.Base
6 -- |Re-exported from Data.Aeson
8 -- |Re-exported from Data.Aeson.Types
10 -- |Re-exported from Data.Fixed
12 -- |Re-exported from Data.Text
18 import Network
.Browser
(BrowserAction
, defaultGETRequest_
, request
)
19 import Network
.HTTP
.Base
(Response
(..))
20 import Network
.TCP
(HandleStream
)
23 import Data
.Aeson
(FromJSON
(..), Value
(Object
), json
, withText
)
24 import Data
.Aeson
.Types
(Parser
)
27 import Data
.Attoparsec
.ByteString
.Lazy
(eitherResult
, parse
)
30 import Data
.Fixed
(HasResolution
(..), Fixed
)
31 import Data
.Functor
((<$>))
32 import Numeric
(readFloat)
35 import qualified Data
.ByteString
.Lazy
as L
(ByteString
)
38 import Control
.Monad
.Trans
.Either (EitherT
, left
, right
)
41 import Network
.URI
(parseAbsoluteURI
)
44 import Data
.Text
(Text
, pack
, unpack
)
47 import Control
.Monad
.Trans
(lift
)
49 -- unordered-containers
50 import qualified Data
.HashMap
.Strict
as HM
(lookup)
52 -- |error conditions w/ debugging information for an API request
53 data CryptsyError
= BadUrl
{ url
:: String }
54 | BadResponse
{ response
:: Response L
.ByteString
}
55 | FailParseResponse
{ body
:: L
.ByteString
, message
:: String }
56 | ErrorResponse
{ error :: Value
}
57 | UnsuccessfulResponse
{ jsonResponse
:: Value
}
58 | FailParseReturn
{ value :: Value
, message
:: String }
62 type PubCryptsy
= EitherT CryptsyError
(BrowserAction
(HandleStream L
.ByteString
))
64 -- |generates public API URL
65 pubURL
:: String -- ^ method value
66 -> String -- ^ complete URL
67 pubURL
= ("http://pubapi.cryptsy.com/api.php?method=" ++)
69 -- |key in JSON object for return data
71 dataKey
= pack
"return"
73 -- |key in JSON object for error message
75 errMsgKey
= pack
"error"
77 -- |common request implementation
78 pubCryptsy
:: String -- ^ URL
80 pubCryptsy apiurl
= do
81 uri
<- maybe (left
$ BadUrl apiurl
) right
$ parseAbsoluteURI apiurl
82 let req
= defaultGETRequest_ uri
83 (_
, resp
) <- lift
$ request req
84 bodyBytes
<- case rspCode resp
of
85 (2, 0, 0) -> right
$ rspBody resp
86 _
-> left
$ BadResponse resp
87 valueJSON
<- either (left
. FailParseResponse bodyBytes
) right
88 . eitherResult
$ parse json bodyBytes
90 Object
(HM
.lookup dataKey
-> Just dat
) -> right dat
91 Object
(HM
.lookup errMsgKey
-> Just errMsg
) ->
92 left
$ ErrorResponse errMsg
93 _
-> left
$ UnsuccessfulResponse valueJSON
95 -- |"Native" number type for the API, newtype for custom FromJSON instance
96 newtype CryptsyNum
= CryptsyNum
(Fixed E8
) deriving
97 (Eq
, Ord
, Show, Read, Num
, Real
, Fractional
, Enum
, RealFrac
)
99 -- |Custom resolution for 'Fixed'
102 instance HasResolution E8
where
104 resolution
= const 100000000
106 parseCryptsyNum
:: Text
-> Parser CryptsyNum
107 parseCryptsyNum
(unpack
-> str
) =
108 CryptsyNum
<$> case readFloat str
of
109 [] -> fail "No parse."
110 [(f
, "")] -> return f
111 [(_
, _
)] -> fail "Incomplete parse."
112 _
-> fail "Ambiguous parse."
114 instance FromJSON CryptsyNum
where
115 -- |Only accepts Text values
116 parseJSON
= withText
"CrypstyNum" parseCryptsyNum