packages feed

pinboard 0.1 → 0.2

raw patch · 5 files changed

+144/−87 lines, 5 filesdep +networkPVP ok

version bump matches the API change (PVP)

Dependencies added: network

API changes (from Hackage documentation)

+ Pinboard.Client.Error: HttpStatusFailure :: PinboardErrorType
+ Pinboard.Client.Internal: addErrMsg :: Text -> PinboardError -> PinboardError
+ Pinboard.Client.Internal: checkStatusCode :: StatusCode -> Either PinboardError ()
+ Pinboard.Client.Internal: connClose :: Either a Connection -> IO ()
+ Pinboard.Client.Internal: connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)
+ Pinboard.Client.Internal: connOpen :: IO (Either SomeException Connection)
+ Pinboard.Client.Internal: connOpenRaw :: IO Connection
+ Pinboard.Client.Internal: createParserErr :: Text -> PinboardError
+ Pinboard.Client.Internal: httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a
+ Pinboard.Client.Internal: parseJSONFromStream :: FromJSON a => InputStream ByteString -> IO (Either PinboardError a)
+ Pinboard.Client.Internal: parseJSONResponseStream :: FromJSON a => Response -> InputStream ByteString -> IO (Response, Either PinboardError a)
+ Pinboard.Client.Internal: sendPinboardRequest :: PinboardRequest -> PinboardConfig -> Connection -> (Response -> InputStream ByteString -> IO a) -> IO a
- Pinboard.Client: fromApiToken :: ByteString -> PinboardConfig
+ Pinboard.Client: fromApiToken :: String -> PinboardConfig

Files

pinboard.cabal view
@@ -1,5 +1,5 @@ name:                pinboard-version:             0.1+version:             0.2 synopsis:            Access to the Pinboard API license:             MIT license-file:        LICENSE@@ -42,6 +42,7 @@                      , http-types                      , io-streams                      , mtl >= 2.1.3.1+                     , network                      , old-locale                      , random >= 1.1                      , text
src/Pinboard/ApiTypes.hs view
@@ -2,7 +2,7 @@ {-# LANGUAGE OverloadedStrings #-}  -- |--- Module      : Pinboard.Types+-- Module      : Pinboard.ApiTypes -- Copyright   : (c) Jon Schoning, 2015 -- Maintainer  : jonschoning@gmail.com -- Stability   : experimental
src/Pinboard/Client.hs view
@@ -35,7 +35,7 @@ import Pinboard.Client.Types import Pinboard.Client.Error import Pinboard.Client.Util-import Data.ByteString (ByteString)+import Data.ByteString.Char8(pack) -fromApiToken :: ByteString -> PinboardConfig-fromApiToken token = PinboardConfig { debug = False, apiToken = token }+fromApiToken :: String -> PinboardConfig+fromApiToken token = PinboardConfig { debug = False, apiToken = pack token }
src/Pinboard/Client/Error.hs view
@@ -31,6 +31,7 @@ ------------------------------------------------------------------------------ data PinboardErrorType =         ConnectionFailure+        | HttpStatusFailure         | ParseFailure         | UnknownErrorType            deriving Show
src/Pinboard/Client/Internal.hs view
@@ -1,5 +1,8 @@+{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards   #-}+{-# LANGUAGE TupleSections #-}+ ------------------------------------------------------------------------------ -- |  -- Module      : Pinboard.Client.Internal@@ -11,48 +14,73 @@  module Pinboard.Client.Internal     ( -      runPinboardSingleRaw+      -- * Monadic+      pinboardJson+    , runPinboardJson+      -- * Single+    , runPinboardSingleRaw     , runPinboardSingleRawBS     , runPinboardSingleJson-    , runPinboardJson-    , pinboardJson+      -- * Sending+    , sendPinboardRequest     , sendPinboardRequestBS+      -- * Connections+    , connOpenRaw+    , connOpen+    , connClose+    , connFail+     -- * JSON Streams+    ,parseJSONResponseStream+    ,parseJSONFromStream+     -- * Status Codes+    ,checkStatusCode+     -- * Error Helpers+    ,addErrMsg+    ,createParserErr+    ,httpStatusPinboardError     ) where  -import           Control.Applicative        ((<$>))-import           Control.Exception          (catch, SomeException, try, bracket)-import           Control.Monad              (when)-import           Control.Monad.IO.Class     (MonadIO (liftIO))-import           Control.Monad.Reader       (ask, runReaderT)-import           Control.Monad.Trans.Either (left, runEitherT, right)-import           Data.Aeson                 (FromJSON, Value(..), eitherDecodeStrict)-import           Data.Monoid                ((<>))+import Control.Applicative        ((<$>))+import Control.Exception          (catch, SomeException, try, bracket)+import Control.Monad.IO.Class     (MonadIO (liftIO))+import Control.Monad.Reader       (ask, runReaderT)+import Control.Monad.Trans.Either (runEitherT, hoistEither)+import Data.Monoid                ((<>))+import Data.Aeson                 (parseJSON, json', FromJSON)+import Data.Aeson.Types           (parseEither)+import Network.Http.Client        (Request, Connection, Method (GET), baselineContextSSL, +                                   buildRequest, closeConnection, concatHandler, concatHandler', +                                   getStatusCode, http, openConnectionSSL, receiveResponse, sendRequest,+                                   setHeader, emptyBody, Response, StatusCode)+import Network                    (withSocketsDo)+import Network.HTTP.Types         (urlEncode)+import OpenSSL                    (withOpenSSL)+import System.IO.Streams          (InputStream)+import System.IO.Streams.Attoparsec (parseFromStream)++import Pinboard.Client.Error      (PinboardError (..),+                                   PinboardErrorHTTPCode (..),+                                   PinboardErrorType (..),+                                   defaultPinboardError)+import Pinboard.Client.Types      (Pinboard,+                                   PinboardConfig (..),+                                   PinboardRequest (..),+                                   Param (..))+import Pinboard.Client.Util       (encodeParams, paramsToByteString, toText)+ import qualified Data.ByteString             as S import qualified Data.Text                   as T import qualified Data.Text.Encoding          as T-import           Network.Http.Client        (Connection, Method (GET),-                                             baselineContextSSL, buildRequest,-                                             closeConnection, concatHandler, concatHandler', -                                             getStatusCode, http,-                                             openConnectionSSL,-                                             receiveResponse, sendRequest,-                                             setHeader, emptyBody, Response)-import Network.HTTP.Types(urlEncode)-import           OpenSSL                    (withOpenSSL)-import           System.IO.Streams          (InputStream)-import           Pinboard.Client.Error  (PinboardError (..),-                                             PinboardErrorHTTPCode (..),-                                             PinboardErrorType (..),-                                             defaultPinboardError)-import           Pinboard.Client.Types  (Pinboard,-                                             PinboardConfig (..),-                                             PinboardRequest (..),-                                             Param (..))-import           Pinboard.Client.Util    (encodeParams, paramsToByteString, toText) --------------------------------------------------------------------------------- +pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a+pinboardJson req = do +  let reqJson = req { requestParams = Format "json" : requestParams req } +  (config, conn)  <- ask+  (_, result) <- liftIO $ sendPinboardRequest reqJson config conn parseJSONResponseStream+  hoistEither result+ runPinboardJson     :: FromJSON a     => PinboardConfig@@ -63,6 +91,8 @@   where go conn = runReaderT (runEitherT requests) (config, conn)                    `catch` connFail UnknownErrorType +--------------------------------------------------------------------------------+ runPinboardSingleRaw     :: PinboardConfig            -> PinboardRequest@@ -86,55 +116,9 @@     -> IO (Either PinboardError a) runPinboardSingleJson config = runPinboardJson config . pinboardJson --------------------------------------------------------------------------------- -connOpenRaw :: IO Connection-connOpenRaw = do-  ctx <- baselineContextSSL-  openConnectionSSL ctx "api.pinboard.in" 443--connOpen :: IO (Either SomeException Connection)-connOpen = try connOpenRaw--connClose :: Either a Connection -> IO ()-connClose = either (const $ return ()) closeConnection--connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)-connFail e msg = return $ Left $ PinboardError e (toText msg) Nothing Nothing Nothing- -------------------------------------------------------------------------------- -pinboardJson :: FromJSON a => PinboardRequest -> Pinboard a-pinboardJson req = do -  (config, conn)  <- ask-  result <- liftIO (sendPinboardRequestBS reqJson config conn)-  handleResultBS (debug config) result-  where-    reqJson =  req { requestParams = Format "json" : requestParams req }-    handleDecodeError dbg resultBS msg = do-      when dbg $ liftIO $ print (eitherDecodeStrict resultBS :: Either String Value)-      left $ PinboardError ParseFailure (T.pack msg) Nothing Nothing Nothing -    handleResultBS dbg (response, resultBS) =-          case getStatusCode response of-            200 -> either (handleDecodeError dbg resultBS) right (eitherDecodeStrict resultBS)-            code | code >= 400 ->-                     let pinboardError err = left $ defaultPinboardError { errorMsg = toText resultBS, errorHTTP = Just err } in-                     case code of-                      400 -> pinboardError BadRequest-                      401 -> pinboardError UnAuthorized-                      402 -> pinboardError RequestFailed-                      403 -> pinboardError Forbidden-                      404 -> pinboardError NotFound-                      429 -> pinboardError TooManyRequests-                      500 -> pinboardError PinboardServerError-                      502 -> pinboardError PinboardServerError-                      503 -> pinboardError PinboardServerError-                      504 -> pinboardError PinboardServerError-                      _   -> pinboardError UnknownHTTPCode-            _ -> left defaultPinboardError----------------------------------------------------------------------------------- sendPinboardRequest       :: PinboardRequest        -> PinboardConfig @@ -148,12 +132,8 @@    req <- buildReq url    sendRequest conn req emptyBody    receiveResponse conn handler-  where-    buildReq url = buildRequest $ do-      http GET ("/v1/" <> url)-      setHeader "Connection" "Keep-Alive"  -      setHeader "User-Agent" "pinboard.hs/0.1"   + sendPinboardRequestBS    :: PinboardRequest    -> PinboardConfig @@ -162,4 +142,79 @@ sendPinboardRequestBS request config conn = sendPinboardRequest request config conn handler   where handler response responseInputStream = do resultBS <- concatHandler response responseInputStream                                                   return (response, resultBS)++--------------------------------------------------------------------------------++buildReq ::  S.ByteString -> IO Request+buildReq url = buildRequest $ do+  http GET ("/v1/" <> url)+  setHeader "Connection" "Keep-Alive"  +  setHeader "User-Agent" "pinboard.hs/0.2"  ++--------------------------------------------------------------------------------++parseJSONResponseStream +    :: FromJSON a +    => Response +    -> InputStream S.ByteString+    -> IO (Response, Either PinboardError a)+parseJSONResponseStream response stream = +  (response,) <$> either (return . Left . addErrMsg (toText response)) +                         (const $ parseJSONFromStream stream) +                         (checkStatusCode $ getStatusCode response)+++parseJSONFromStream +    :: FromJSON a +    => InputStream S.ByteString +    -> IO (Either PinboardError a)+parseJSONFromStream s = do +  r <- parseFromStream (parseEither parseJSON <$> json') s+  return $ either (Left . createParserErr . toText)  Right r+  `catch` connFail ParseFailure++--------------------------------------------------------------------------------++checkStatusCode :: StatusCode -> Either PinboardError ()+checkStatusCode = \case+  200 -> Right ()+  400 -> httpStatusPinboardError BadRequest+  401 -> httpStatusPinboardError UnAuthorized+  402 -> httpStatusPinboardError RequestFailed+  403 -> httpStatusPinboardError Forbidden+  404 -> httpStatusPinboardError NotFound+  429 -> httpStatusPinboardError TooManyRequests+  c | c >= 500 -> httpStatusPinboardError PinboardServerError+  _   -> httpStatusPinboardError UnknownHTTPCode++--------------------------------------------------------------------------------++httpStatusPinboardError :: PinboardErrorHTTPCode -> Either PinboardError a+httpStatusPinboardError err = Left $ defaultPinboardError +  { errorType = HttpStatusFailure+  , errorHTTP = Just err }++addErrMsg :: T.Text -> PinboardError -> PinboardError+addErrMsg msg err = err {errorMsg = msg}++createParserErr :: T.Text -> PinboardError+createParserErr msg = PinboardError ParseFailure msg Nothing Nothing Nothing ++--------------------------------------------------------------------------------+++connOpenRaw :: IO Connection+connOpenRaw = withSocketsDo $ do+  ctx <- baselineContextSSL+  openConnectionSSL ctx "api.pinboard.in" 443++connOpen :: IO (Either SomeException Connection)+connOpen = try connOpenRaw++connClose :: Either a Connection -> IO ()+connClose = either (const $ return ()) closeConnection++connFail :: PinboardErrorType -> SomeException -> IO (Either PinboardError b)+connFail e msg = return $ Left $ PinboardError e (toText msg) Nothing Nothing Nothing+