packages feed

hackernews 0.2.3.2 → 0.3.0.0

raw patch · 15 files changed

+441/−154 lines, 15 filesdep +eitherPVP ok

version bump matches the API change (PVP)

Dependencies added: either

API changes (from Hackage documentation)

- Web.HackerNews: type MaxItem = Int
- Web.HackerNews: type TopStories = [Int]
+ Web.HackerNews: ConnectionError :: HackerNewsError
+ Web.HackerNews: ItemComment :: Comment -> Item
+ Web.HackerNews: ItemId :: Int -> ItemId
+ Web.HackerNews: ItemJob :: Job -> Item
+ Web.HackerNews: ItemPoll :: Poll -> Item
+ Web.HackerNews: ItemPollOpt :: PollOpt -> Item
+ Web.HackerNews: ItemStory :: Story -> Item
+ Web.HackerNews: ParseError :: HackerNewsError
+ Web.HackerNews: RequestError :: HackerNewsError
+ Web.HackerNews: commentDead :: Comment -> Bool
+ Web.HackerNews: data HackerNewsError
+ Web.HackerNews: data Item
+ Web.HackerNews: data MaxItem
+ Web.HackerNews: data TopStories
+ Web.HackerNews: getItem :: ItemId -> HackerNews Item
+ Web.HackerNews: jobDead :: Job -> Bool
+ Web.HackerNews: newtype ItemId
+ Web.HackerNews: pollDead :: Poll -> Bool
+ Web.HackerNews: pollOptDead :: PollOpt -> Bool
+ Web.HackerNews: storyDead :: Story -> Bool
+ Web.HackerNews: updateDead :: Update -> Bool
+ Web.HackerNews: userDead :: User -> Bool
- Web.HackerNews: Comment :: Text -> CommentId -> Maybe [Int] -> Int -> Text -> UTCTime -> Text -> Bool -> Comment
+ Web.HackerNews: Comment :: Text -> CommentId -> Maybe [Int] -> Int -> Text -> UTCTime -> Text -> Bool -> Bool -> Comment
- Web.HackerNews: Job :: Text -> JobId -> Int -> Text -> UTCTime -> Text -> Text -> Text -> Bool -> Job
+ Web.HackerNews: Job :: Text -> JobId -> Int -> Text -> UTCTime -> Text -> Text -> Text -> Bool -> Bool -> Job
- Web.HackerNews: Poll :: Text -> PollId -> [Int] -> [Int] -> Int -> Text -> UTCTime -> Text -> Text -> Bool -> Poll
+ Web.HackerNews: Poll :: Text -> PollId -> [Int] -> [Int] -> Int -> Text -> UTCTime -> Text -> Text -> Bool -> Bool -> Poll
- Web.HackerNews: PollOpt :: Text -> PollOptId -> Int -> Int -> Text -> UTCTime -> Text -> Bool -> PollOpt
+ Web.HackerNews: PollOpt :: Text -> PollOptId -> Int -> Int -> Text -> UTCTime -> Text -> Bool -> Bool -> PollOpt
- Web.HackerNews: Story :: Text -> Int -> [Int] -> Int -> UTCTime -> Text -> Text -> Text -> Bool -> Story
+ Web.HackerNews: Story :: Text -> StoryId -> [Int] -> Int -> UTCTime -> Text -> Text -> Text -> Bool -> Bool -> Story
- Web.HackerNews: Update :: [Int] -> [Text] -> Bool -> Update
+ Web.HackerNews: Update :: [Int] -> [Text] -> Bool -> Bool -> Update
- Web.HackerNews: User :: Maybe Text -> UTCTime -> Int -> UserId -> Int -> [Int] -> Bool -> User
+ Web.HackerNews: User :: Maybe Text -> UTCTime -> Int -> UserId -> Int -> [Int] -> Bool -> Bool -> User
- Web.HackerNews: getComment :: CommentId -> HackerNews (Maybe Comment)
+ Web.HackerNews: getComment :: CommentId -> HackerNews Comment
- Web.HackerNews: getJob :: JobId -> HackerNews (Maybe Job)
+ Web.HackerNews: getJob :: JobId -> HackerNews Job
- Web.HackerNews: getMaxItem :: HackerNews (Maybe MaxItem)
+ Web.HackerNews: getMaxItem :: HackerNews MaxItem
- Web.HackerNews: getPoll :: PollId -> HackerNews (Maybe Poll)
+ Web.HackerNews: getPoll :: PollId -> HackerNews Poll
- Web.HackerNews: getPollOpt :: PollOptId -> HackerNews (Maybe PollOpt)
+ Web.HackerNews: getPollOpt :: PollOptId -> HackerNews PollOpt
- Web.HackerNews: getStory :: StoryId -> HackerNews (Maybe Story)
+ Web.HackerNews: getStory :: StoryId -> HackerNews Story
- Web.HackerNews: getTopStories :: HackerNews (Maybe TopStories)
+ Web.HackerNews: getTopStories :: HackerNews TopStories
- Web.HackerNews: getUpdates :: HackerNews (Maybe Update)
+ Web.HackerNews: getUpdates :: HackerNews Update
- Web.HackerNews: getUser :: UserId -> HackerNews (Maybe User)
+ Web.HackerNews: getUser :: UserId -> HackerNews User
- Web.HackerNews: hackerNews :: (Show a, FromJSON a) => HackerNews a -> IO a
+ Web.HackerNews: hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a)
- Web.HackerNews: storyId :: Story -> Int
+ Web.HackerNews: storyId :: Story -> StoryId
- Web.HackerNews: type HackerNews a = ReaderT Connection IO a
+ Web.HackerNews: type HackerNews a = EitherT HackerNewsError (ReaderT Connection IO) a

Files

hackernews.cabal view
@@ -1,5 +1,5 @@ name:                hackernews-version:             0.2.3.2+version:             0.3.0.0 description:         API for news.ycombinator.com license:             MIT synopsis:            API for Hacker News@@ -11,7 +11,7 @@ cabal-version:       >=1.10  library-  ghc-options:         -Wall -threaded+  ghc-options:         -Wall -threaded -rtsopts   exposed-modules:     Web.HackerNews   other-modules:       Web.HackerNews.Types                      , Web.HackerNews.Client@@ -22,13 +22,16 @@                      , Web.HackerNews.Comment                      , Web.HackerNews.Story                      , Web.HackerNews.Job+                     , Web.HackerNews.Item                      , Web.HackerNews.Util+                     , Web.HackerNews.Endpoint   hs-source-dirs:      src   build-depends:       HsOpenSSL    >= 0.10.5                      , aeson        >= 0.8.0.1                      , attoparsec   >= 0.12.1.2                      , base         >= 4.7 && <4.8                      , bytestring   >= 0.10.4.0+                     , either       >= 4.3.1                      , http-streams >= 0.7.2.2                      , io-streams   >= 1.1.4.6                      , text         >= 1.2.0.0@@ -38,7 +41,7 @@  Test-Suite tests     type:                exitcode-stdio-1.0-    ghc-options: -rtsopts -threaded+    ghc-options:         -rtsopts -threaded     hs-source-dirs:      tests     main-is:             Test.hs     build-depends:       base
src/Web/HackerNews.hs view
@@ -1,8 +1,15 @@ {-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      : Web.HackerNews+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews        ( -- * Hacker News Monad          hackerNews          -- * API Calls+       , getItem        , getStory        , getComment        , getPoll@@ -14,6 +21,9 @@        , getUpdates          -- * Types        , HackerNews+       , HackerNewsError (..)+       , Item      (..)+       , ItemId    (..)        , Comment   (..)        , CommentId (..)        , Poll      (..)@@ -31,54 +41,56 @@        , TopStories        ) where -import           Data.Monoid                ((<>))- import           Web.HackerNews.Types-import           Web.HackerNews.Util        (toText)-import           Web.HackerNews.Client+import           Web.HackerNews.Client (HackerNews, hackerNews, HackerNewsError(..))  ------------------------------------------------------------------------------+-- | Retrieve a `Item` by `ItemId`+getItem :: ItemId -> HackerNews Item+getItem = getEndpoint++------------------------------------------------------------------------------ -- | Retrieve a `Story` by `StoryId`-getStory :: StoryId -> HackerNews (Maybe Story)-getStory (StoryId storyid) = buildHNRequest $ "item/" <> toText storyid+getStory :: StoryId -> HackerNews Story+getStory = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve a `Comment` by `CommentId`-getComment :: CommentId -> HackerNews (Maybe Comment)-getComment (CommentId commentid) = buildHNRequest $ "item/" <> toText commentid+getComment :: CommentId -> HackerNews Comment+getComment = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve a `Poll` by `PollId`-getPoll :: PollId -> HackerNews (Maybe Poll)-getPoll (PollId pollid) = buildHNRequest $ "item/" <> toText pollid+getPoll :: PollId -> HackerNews Poll+getPoll = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve a `PollOpt` by `PollOptId`-getPollOpt :: PollOptId -> HackerNews (Maybe PollOpt)-getPollOpt (PollOptId polloptid) = buildHNRequest $ "item/" <> toText polloptid+getPollOpt :: PollOptId -> HackerNews PollOpt+getPollOpt = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve a `User` by `UserId`-getUser :: UserId -> HackerNews (Maybe User)-getUser (UserId userid) = buildHNRequest $ "user/" <> userid+getUser :: UserId -> HackerNews User+getUser = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve a Job-getJob :: JobId -> HackerNews (Maybe Job)-getJob (JobId jobid) = buildHNRequest $ "item/" <> toText jobid+getJob :: JobId -> HackerNews Job+getJob = getEndpoint  ------------------------------------------------------------------------------ -- | Retrieve the Top Stories on Hacker News-getTopStories :: HackerNews (Maybe TopStories)-getTopStories = buildHNRequest "topstories"+getTopStories :: HackerNews TopStories+getTopStories = getEndpoint TopStoriesId  ------------------------------------------------------------------------------ -- | Retrieve the largest ItemId-getMaxItem :: HackerNews (Maybe MaxItem)-getMaxItem = buildHNRequest "maxitem"+getMaxItem :: HackerNews MaxItem+getMaxItem = getEndpoint MaxItemId  ------------------------------------------------------------------------------ -- | Retrieve the latest updates-getUpdates :: HackerNews (Maybe Update)-getUpdates = buildHNRequest "updates"+getUpdates :: HackerNews Update+getUpdates = getEndpoint UpdateId 
src/Web/HackerNews/Client.hs view
@@ -1,60 +1,82 @@-{-# LANGUAGE BangPatterns      #-} {-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      : Web.HackerNews.Client+-- Copyright   : (c) David Johnson, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Client        ( hackerNews        , buildHNRequest        , HackerNews+       , HackerNewsError (..)        ) where  import           Data.Aeson                 hiding (Result) import           Data.Aeson.Parser          (value) import           Data.Attoparsec.ByteString (parseOnly) import           Data.Either                (rights)+import           Control.Monad.Trans.Either  +import           Control.Exception          (try, SomeException)  import           Control.Monad.IO.Class     (liftIO)+import           Control.Monad.Trans.Class  (lift) import           Control.Monad.Trans.Reader (ReaderT, ask, runReaderT) import           Data.Monoid                ((<>))-import           Data.Text                  (Text) import qualified Data.Text.Encoding         as T+import           Data.Text                 (Text) import           Network.Http.Client import           OpenSSL                    (withOpenSSL) import qualified System.IO.Streams          as Streams  ------------------------------------------------------------------------------ -- | Core Type-type HackerNews a = ReaderT Connection IO a+type HackerNews a = EitherT HackerNewsError (ReaderT Connection IO) a  ------------------------------------------------------------------------------+-- | Error Types+data HackerNewsError =+    ConnectionError+  | ParseError+  | RequestError+  deriving (Show, Eq)++------------------------------------------------------------------------------ -- | HackerNews API request method-hackerNews :: (Show a, FromJSON a) => HackerNews a -> IO a+hackerNews :: FromJSON a => HackerNews a -> IO (Either HackerNewsError a) hackerNews requests =   withOpenSSL $ do     ctx <- baselineContextSSL-    con <- openConnectionSSL ctx "hacker-news.firebaseio.com" 443-    result <- runReaderT requests con -    closeConnection con-    return result+    con <- try (openConnectionSSL ctx "hacker-news.firebaseio.com" 443) :: IO (Either SomeException Connection)+    case con of+     Left _ -> return $ Left ConnectionError+     Right conn -> do+       result <- flip runReaderT conn $ runEitherT requests+       closeConnection conn+       return result  ------------------------------------------------------------------------------ -- | Request Builder for API-buildHNRequest :: FromJSON a => Text -> HackerNews (Maybe a)+buildHNRequest :: FromJSON a => Text -> HackerNews a buildHNRequest url = do-    con <- ask-    liftIO $ do+    con <- lift ask+    bytes <- liftIO $ do       req <- buildRequest $ do         http GET $ "/v0/" <> T.encodeUtf8 url <> ".json"         setHeader "Connection" "Keep-Alive"         setAccept "application/json"       sendRequest con req emptyBody-      !bytes <- receiveResponse con $ const Streams.read-      return $ case bytes of-        Nothing -> Nothing-        Just bs -> do-          let xs = rights [parseOnly value bs, parseOnly json bs]-          case xs of-            []    -> Nothing-            x : _ ->-              case fromJSON x of-                Success a -> Just a-                _         -> Nothing+      receiveResponse con $ const Streams.read+    case bytes of+      Nothing -> left RequestError+      Just bs -> do+        let xs = rights [parseOnly value bs, parseOnly json bs]+        case xs of+          []    -> left ParseError+          x : _ ->+            case fromJSON x of+             Success jsonBody -> right jsonBody+             _                -> left ParseError+    + 
src/Web/HackerNews/Comment.hs view
@@ -1,33 +1,49 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.Comment+-- Copyright   : (c) David Johnson, Konstantin Zudov 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Comment where -import           Control.Applicative ((<$>), (<*>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.:?), (.!=))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Text               (Text)+import           Data.Time               (UTCTime) -import           Web.HackerNews.Util (fromSeconds)+import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)+import           Web.HackerNews.Util     (fromSeconds)  --------------------------------------------------------------------------------- | Types+-- | Comment Object data Comment = Comment {-    commentBy     :: Text-  , commentId     :: CommentId-  , commentKids   :: Maybe [Int]-  , commentParent :: Int-  , commentText   :: Text-  , commentTime   :: UTCTime-  , commentType   :: Text+    commentBy      :: Text+  , commentId      :: CommentId+  , commentKids    :: Maybe [Int]+  , commentParent  :: Int+  , commentText    :: Text+  , commentTime    :: UTCTime+  , commentType    :: Text   , commentDeleted :: Bool+  , commentDead    :: Bool   } deriving Show +------------------------------------------------------------------------------+-- | `CommentId` for a `Comment` Object newtype CommentId   = CommentId Int   deriving (Show, Eq)  ------------------------------------------------------------------------------+-- | Endpoint instances+instance Endpoint CommentId Comment where+    endpoint (CommentId id') = itemEndpoint id'++------------------------------------------------------------------------------ -- | JSON Instances instance FromJSON Comment where   parseJSON (Object o) =@@ -39,6 +55,7 @@              <*> (fromSeconds <$> o .: "time")              <*> o .: "type"              <*> o .:? "deleted" .!= False+             <*> o .:? "dead" .!= False   parseJSON _ = mzero  
+ src/Web/HackerNews/Endpoint.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE FunctionalDependencies #-}+{-# LANGUAGE MultiParamTypeClasses  #-}+{-# LANGUAGE OverloadedStrings      #-}+-- |+-- Module      : Web.HackerNews.Endpoint+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX+module Web.HackerNews.Endpoint where++import           Data.Aeson            (FromJSON)+import           Data.Text             (Text, append)++import           Web.HackerNews.Client (HackerNews, buildHNRequest)+import           Web.HackerNews.Util   (toText)++------------------------------------------------------------------------------+-- | Endpoint maps the id to the returned type on a type level+-- The function dependency @id -> resp@ specifies that @id@ uniquely determines @resp@+class Endpoint id resp | id -> resp where+    endpoint :: id -> Text -- ^ Turn @id@ into path that points to resource++------------------------------------------------------------------------------+-- | Endpoint for `Item`+itemEndpoint :: Int -> Text+itemEndpoint = append "item/" . toText++------------------------------------------------------------------------------+-- | Generic function for making requests+getEndpoint :: (Endpoint a b, FromJSON b) => a -> HackerNews b+getEndpoint id' = buildHNRequest $ endpoint id'
+ src/Web/HackerNews/Item.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.Stripe.Stripe+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX+module Web.HackerNews.Item where++import           Control.Applicative     ((<$>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.:))+import           Data.Text               (Text)++import           Web.HackerNews.Comment  (Comment)+import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)+import           Web.HackerNews.Job      (Job)+import           Web.HackerNews.Poll     (Poll, PollOpt)+import           Web.HackerNews.Story    (Story)++------------------------------------------------------------------------------+-- | Item Type+data Item = ItemComment Comment+          | ItemPoll Poll+          | ItemPollOpt PollOpt+          | ItemStory Story+          | ItemJob Job+          deriving (Show)++------------------------------------------------------------------------------+-- | Item ID for a `Item` object+newtype ItemId = ItemId Int deriving (Show,Eq)++------------------------------------------------------------------------------+-- | Max Item ID for a `Item`+data MaxItemId = MaxItemId deriving (Show, Eq)++------------------------------------------------------------------------------+-- | Max Item Int+newtype MaxItem = MaxItem Int deriving (Show, Eq)++------------------------------------------------------------------------------+-- | Endpoint Instances for `MaxItemId` and `MaxItem`+instance Endpoint MaxItemId MaxItem where+    endpoint _ = "maxitem"++------------------------------------------------------------------------------+-- | Endpoint Instances for `ItemId` & `Item`+instance Endpoint ItemId Item where+    endpoint (ItemId id') = itemEndpoint id'++------------------------------------------------------------------------------+-- | JSON Instances+instance FromJSON Item where+    parseJSON v@(Object o) = do+        itemType <- o .: "type"+        case (itemType :: Text) of+            "job"     -> ItemJob     <$> parseJSON v+            "story"   -> ItemStory   <$> parseJSON v+            "comment" -> ItemComment <$> parseJSON v+            "poll"    -> ItemPoll    <$> parseJSON v+            "pollopt" -> ItemPollOpt <$> parseJSON v+            _         -> mzero+    parseJSON _ = mzero++------------------------------------------------------------------------------+-- | JSON MaxItem Instance+instance FromJSON MaxItem where+    parseJSON = fmap MaxItem . parseJSON+
src/Web/HackerNews/Job.hs view
@@ -1,34 +1,50 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.Job+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Job where -import           Control.Applicative ((<*>), (<$>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.:?), (.!=))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Text               (Text)+import           Data.Time               (UTCTime) -import           Web.HackerNews.Util (fromSeconds)+import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)+import           Web.HackerNews.Util     (fromSeconds)  ------------------------------------------------------------------------------ -- | Types data Job = Job {-    jobBy        :: Text-  , jobId        :: JobId-  , jobScore     :: Int-  , jobText      :: Text-  , jobTime      :: UTCTime-  , jobTitle     :: Text-  , jobType      :: Text-  , jobUrl       :: Text-  , jobDeleted   :: Bool+    jobBy      :: Text+  , jobId      :: JobId+  , jobScore   :: Int+  , jobText    :: Text+  , jobTime    :: UTCTime+  , jobTitle   :: Text+  , jobType    :: Text+  , jobUrl     :: Text+  , jobDeleted :: Bool+  , jobDead    :: Bool   } deriving (Show) +------------------------------------------------------------------------------+-- | ID for a `Job` type newtype JobId       = JobId Int       deriving (Show, Eq)  ------------------------------------------------------------------------------+-- | Endpoint Instances+instance Endpoint JobId Job where+    endpoint (JobId id') = itemEndpoint id'++------------------------------------------------------------------------------ -- | JSON Instances instance FromJSON Job where   parseJSON (Object o) =@@ -41,5 +57,6 @@           <*> o .: "type"           <*> o .: "url"           <*> o .:? "deleted" .!= False+          <*> o .:? "dead" .!= False   parseJSON _ = mzero 
src/Web/HackerNews/Person.hs view
@@ -1,17 +1,23 @@ {-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      : Web.HackerNews.Person+-- Copyright   : (c) David Johnson, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Person where  import           Control.Applicative ((<$>), (<*>)) import           Control.Monad       (MonadPlus (mzero)) import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.:?), (.!=))+                                      (.!=), (.:), (.:?)) import           Data.Text           (Text) import           Data.Time           (UTCTime)  import           Web.HackerNews.Util (fromSeconds)  --------------------------------------------------------------------------------- | Types+-- | Person Object data Person = Person {     personBy      :: Text   , personId      :: PersonId@@ -22,8 +28,11 @@   , personType    :: Text   , personUrl     :: Maybe Text   , personDeleted :: Bool+  , personDead    :: Bool   } deriving (Show, Eq) +------------------------------------------------------------------------------+-- | Person ID for a `Person` Object newtype PersonId   = PersonId Text   deriving (Show, Eq)@@ -41,4 +50,5 @@             <*> o .: "type"             <*> o .:? "url"             <*> o .:? "deleted" .!= False+            <*> o .:? "dead" .!= False    parseJSON _ = mzero
src/Web/HackerNews/Poll.hs view
@@ -1,17 +1,25 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.Poll+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Poll where -import           Control.Applicative ((<$>), (<*>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.!=), (.:?))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Text               (Text)+import           Data.Time               (UTCTime) -import           Web.HackerNews.Util (fromSeconds)+import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)+import           Web.HackerNews.Util     (fromSeconds)  --------------------------------------------------------------------------------- | Types+-- | Poll Object data Poll = Poll {     pollBy      :: Text   , pollId      :: PollId@@ -23,8 +31,11 @@   , pollTitle   :: Text   , pollType    :: Text   , pollDeleted :: Bool+  , pollDead :: Bool   } deriving (Show, Eq) +------------------------------------------------------------------------------+-- | Poll Opt Object data PollOpt = PollOpt {     pollOptBy      :: Text   , pollOptId      :: PollOptId@@ -34,18 +45,33 @@   , pollOptTime    :: UTCTime   , pollOptType    :: Text   , pollOptDeleted :: Bool+  , pollOptDead       :: Bool     } deriving (Show, Eq) +------------------------------------------------------------------------------+-- | Poll Option Id for a `PollOpt` newtype PollOptId   = PollOptId Int   deriving (Show, Eq) +------------------------------------------------------------------------------+-- | Poll Id for a `Poll` newtype PollId   = PollId Int   deriving (Show, Eq)  --------------------------------------------------------------------------------- | JSON Instances+-- | `Endpoint` Instance for `PollOptId`+instance Endpoint PollOptId PollOpt where+    endpoint (PollOptId id') = itemEndpoint id'++------------------------------------------------------------------------------+-- | `Endpoint` Instance for `PollId`+instance Endpoint PollId Poll where+    endpoint (PollId id') = itemEndpoint id'++------------------------------------------------------------------------------+-- | Poll JSON Instances instance FromJSON Poll where   parseJSON (Object o) =      Poll <$> o .: "by"@@ -58,8 +84,11 @@           <*> o .: "title"           <*> o .: "type"           <*> o .:? "deleted" .!= False+          <*> o .:? "dead" .!= False             parseJSON _ = mzero +------------------------------------------------------------------------------+-- | Poll JSON Instances instance FromJSON PollOpt where   parseJSON (Object o) =      PollOpt <$> o .: "by"@@ -70,4 +99,5 @@              <*> (fromSeconds <$> o .: "time")              <*> o .: "type"              <*> o .:? "deleted" .!= False+             <*> o .:? "dead" .!= False                parseJSON _ = mzero
src/Web/HackerNews/Story.hs view
@@ -1,20 +1,28 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.Story+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Story where -import           Control.Applicative ((<$>), (<*>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.!=), (.:?))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Text               (Text)+import           Data.Time               (UTCTime) -import           Web.HackerNews.Util (fromSeconds)+import           Web.HackerNews.Endpoint (Endpoint (endpoint), itemEndpoint)+import           Web.HackerNews.Util     (fromSeconds)  --------------------------------------------------------------------------------- | Types+-- | Story Object data Story = Story {     storyBy      :: Text-  , storyId      :: Int+  , storyId      :: StoryId   , storyKids    :: [Int]   , storyScore   :: Int   , storyTime    :: UTCTime@@ -22,21 +30,39 @@   , storyType    :: Text   , storyUrl     :: Text   , storyDeleted :: Bool+  , storyDead    :: Bool   } deriving Show +------------------------------------------------------------------------------+-- | ID for a `Story` newtype StoryId   = StoryId Int   deriving (Show, Eq) -type TopStories = [Int]-type MaxItem    = Int+------------------------------------------------------------------------------+-- | `Story` ID+data TopStoriesId  = TopStoriesId deriving (Show, Eq)  ------------------------------------------------------------------------------+-- | TopStories List+newtype TopStories = TopStories [Int] deriving (Show, Eq)++------------------------------------------------------------------------------+-- | Endpoint Instances for `StoryID` & `Story`+instance Endpoint StoryId Story where+    endpoint (StoryId id') = itemEndpoint id'++------------------------------------------------------------------------------+-- | Endpoint Instances for `TopStoriesID` & `TopStories`+instance Endpoint TopStoriesId TopStories where+    endpoint _ = "topstories"++------------------------------------------------------------------------------ -- | JSON Instances instance FromJSON Story where    parseJSON (Object o) =      Story <$> o .: "by"-           <*> o .: "id"+           <*> (StoryId <$> o .: "id")            <*> o .: "kids"            <*> o .: "score"            <*> (fromSeconds <$> o .: "time")@@ -44,5 +70,11 @@            <*> o .: "type"            <*> o .: "url"            <*> o .:? "deleted" .!= False+           <*> o .:? "dead" .!= False    parseJSON _ = mzero++------------------------------------------------------------------------------+-- | JSON instances `TopStories`+instance FromJSON TopStories where+   parseJSON = fmap TopStories . parseJSON 
src/Web/HackerNews/Types.hs view
@@ -1,11 +1,11 @@-{-# LANGUAGE OverloadedStrings #-}+-- |+-- Module      : Web.HackerNews.Types+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Types        ( module H-       , StoryId   (..)-       , CommentId (..)-       , PollId    (..)-       , PollOptId (..)-       , UserId    (..)        ) where  import           Web.HackerNews.Comment as H@@ -13,7 +13,9 @@ import           Web.HackerNews.Story   as H import           Web.HackerNews.User    as H import           Web.HackerNews.Update  as H-import           Web.HackerNews.Job  as H+import           Web.HackerNews.Job     as H+import           Web.HackerNews.Item    as H+import           Web.HackerNews.Endpoint as H   
src/Web/HackerNews/Update.hs view
@@ -1,26 +1,46 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.Update+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Update where -import           Control.Applicative ((<$>), (<*>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.!=), (.:?))-import           Data.Text           (Text)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Text               (Text) +import           Web.HackerNews.Endpoint (Endpoint (endpoint))+ --------------------------------------------------------------------------------- | Types+-- | Update Object data Update = Update {     updateItems    :: [Int]   , updateProfiles :: [Text]   , updateDeleted  :: Bool+  , updateDead     :: Bool   } deriving (Show, Eq)  ------------------------------------------------------------------------------+-- | Update ID for an `Updated` Object+data UpdateId = UpdateId deriving (Show, Eq)++------------------------------------------------------------------------------+-- | Endpoint Instances+instance Endpoint UpdateId Update where+    endpoint _ = "updates"++------------------------------------------------------------------------------ -- | JSON Instances instance FromJSON Update where   parseJSON (Object o) =      Update <$> o .: "items"             <*> o .: "profiles"             <*> o .:? "deleted" .!= False+            <*> o .:? "dead" .!= False   parseJSON _ = mzero 
src/Web/HackerNews/User.hs view
@@ -1,17 +1,26 @@-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE OverloadedStrings     #-}+-- |+-- Module      : Web.HackerNews.User+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.User where -import           Control.Applicative ((<*>), (<$>))-import           Control.Monad       (MonadPlus (mzero))-import           Data.Aeson          (FromJSON (parseJSON), Value (Object),-                                      (.:), (.:?), (.!=))-import           Data.Text           (Text)-import           Data.Time           (UTCTime)+import           Control.Applicative     ((<$>), (<*>))+import           Control.Monad           (MonadPlus (mzero))+import           Data.Aeson              (FromJSON (parseJSON), Value (Object),+                                          (.!=), (.:), (.:?))+import           Data.Monoid             ((<>))+import           Data.Text               (Text)+import           Data.Time               (UTCTime) -import           Web.HackerNews.Util (fromSeconds, monoidToMaybe)+import           Web.HackerNews.Endpoint (Endpoint (endpoint))+import           Web.HackerNews.Util     (fromSeconds, monoidToMaybe)  --------------------------------------------------------------------------------- | Types+-- | `User` Object data User = User {     userAbout     :: Maybe Text   , userCreated   :: UTCTime@@ -20,13 +29,21 @@   , userKarma     :: Int   , userSubmitted :: [Int]   , userDeleted   :: Bool+  , userDead      :: Bool   } deriving (Show) +------------------------------------------------------------------------------+-- | User ID for a `User` Object newtype UserId       = UserId Text       deriving (Show, Eq)  ------------------------------------------------------------------------------+-- | Endpoint instances+instance Endpoint UserId User where+    endpoint (UserId id') = "user/" <> id'++------------------------------------------------------------------------------ -- | JSON Instances instance FromJSON User where   parseJSON (Object o) =@@ -37,5 +54,6 @@           <*> o .: "karma"           <*> o .: "submitted"           <*> o .:? "deleted" .!= False+          <*> o .:? "dead" .!= False   parseJSON _ = mzero 
src/Web/HackerNews/Util.hs view
@@ -1,10 +1,16 @@+-- |+-- Module      : Web.HackerNews.Util+-- Copyright   : (c) David Johnson, Konstantin Zudov, 2014+-- Maintainer  : djohnson.m@gmail.com+-- Stability   : experimental+-- Portability : POSIX module Web.HackerNews.Util where -import qualified Data.Text as T-import           Data.Text    (Text)+import           Data.Monoid           (Monoid, mempty)+import           Data.Text             (Text)+import qualified Data.Text             as T+import           Data.Time             (UTCTime) import           Data.Time.Clock.POSIX (posixSecondsToUTCTime)-import           Data.Time (UTCTime)-import           Data.Monoid (Monoid, mempty)  ------------------------------------------------------------------------------ -- | Convert `Integer` to `UTCTime`@@ -16,7 +22,8 @@ toText :: Show a => a -> Text toText = T.pack . show +------------------------------------------------------------------------------ -- | Turns empty monoids into Nothing--- | @Data.Maybe.listToMaybe@ generalized for monoids+-- `listToMaybe` generalized for monoids monoidToMaybe :: (Eq a, Monoid a) => a -> Maybe a monoidToMaybe m = if m == mempty then Nothing else Just m
tests/Test.hs view
@@ -1,34 +1,27 @@ {-# LANGUAGE OverloadedStrings #-} module Main where -import           Data.Maybe     (isJust)-import           Test.Hspec     (it, runIO, hspec, describe)+import           Data.Either    (isRight)+import           Test.Hspec     (it, runIO, hspec, describe, shouldSatisfy) import           Web.HackerNews import           Control.Applicative  main :: IO () main = hspec $ do   describe "Hacker News API Tests" $ do-    (story, comment, user, job, poll, pollOpt, topStories, maxItem, updates) <- -      runIO $ hackerNews $ (,,,,,,,,)  <$> -        getStory   (StoryId 8863)      <*>-        getComment (CommentId 2921983) <*>-        getUser    (UserId "dmjio")    <*>-        getJob     (JobId 8437631)     <*>-        getPoll    (PollId 126809)     <*>-        getPollOpt (PollOptId 160705)  <*>-        getTopStories                  <*>-        getMaxItem                     <*>+    it "Retrieves all" $ do+      result <- hackerNews $ (,,,,,,,,) <$> +        getStory   (StoryId 8863)       <*>+        getComment (CommentId 2921983)  <*>+        getUser    (UserId "dmjio")     <*>+        getJob     (JobId 8437631)      <*>+        getPoll    (PollId 126809)      <*>+        getPollOpt (PollOptId 160705)   <*>+        getTopStories                   <*>+        getMaxItem                      <*>         getUpdates-    it "Retrieves a Story"     $ isJust story-    it "Retrieves a Comment"   $ isJust comment-    it "Retrieves a User"      $ isJust user-    it "Retrieves a Job"       $ isJust job-    it "Retrieves a Poll"      $ isJust poll-    it "Retrieves a Pollopt"   $ isJust pollOpt-    it "Retrieves Top Stories" $ isJust topStories-    it "Retrieves Max Item"    $ isJust maxItem-    it "Retrieves Updates"     $ isJust updates+      result `shouldSatisfy` isRight+