packages feed

pinboard 0.9.4 → 0.9.5

raw patch · 5 files changed

+170/−14 lines, 5 filesdep +QuickCheckdep +hspecdep +pinboardPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: QuickCheck, hspec, pinboard, semigroups

API changes (from Hackage documentation)

- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0DoneResult
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0JsonTagMap
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0Note
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0NoteList
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0Post
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0PostDates
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0Posts
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0Suggested
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0TextResult
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_0UpdateTime
- Pinboard.ApiTypes: instance GHC.Generics.Constructor Pinboard.ApiTypes.C1_1Suggested
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1DoneResult
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1JsonTagMap
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1Note
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1NoteList
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1Post
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1PostDates
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1Posts
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1Suggested
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1TextResult
- Pinboard.ApiTypes: instance GHC.Generics.Datatype Pinboard.ApiTypes.D1UpdateTime
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0DoneResult
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0JsonTagMap
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0NoteList
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0PostDates
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0Posts
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0TextResult
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_0UpdateTime
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1NoteList
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1PostDates
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_1Posts
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_2Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_2NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_2Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_2PostDates
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_2Posts
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_3Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_3NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_3Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_4Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_4NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_4Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_5Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_5NoteListItem
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_5Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_6Note
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_6Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_7Post
- Pinboard.ApiTypes: instance GHC.Generics.Selector Pinboard.ApiTypes.S1_0_8Post
- Pinboard.Client: runPinboardSingleJson :: (Functor m, MonadIO m, FromJSON a) => PinboardConfig -> PinboardRequest -> m (Either PinboardError a)
+ Pinboard.Client: runPinboardSingleJson :: (MonadIO m, FromJSON a) => PinboardConfig -> PinboardRequest -> m (Either PinboardError a)

Files

changelog.md view
@@ -1,3 +1,7 @@+__v0.9.5__++add JSON roundtrip tests+ __v0.9.4__  derive generic
pinboard.cabal view
@@ -1,5 +1,5 @@ name:                pinboard-version:             0.9.4+version:             0.9.5 synopsis:            Access to the Pinboard API license:             MIT license-file:        LICENSE@@ -53,10 +53,31 @@                        Pinboard.Error                        Pinboard.Types                        Pinboard.Util-  ghc-options:         -Wall -rtsopts+  ghc-options:         -Wall                        -fwarn-incomplete-patterns                        -funbox-strict-fields                        -fexpose-all-unfoldings+test-suite tests+  ghc-options: -Wall -fno-warn-orphans+  type: exitcode-stdio-1.0+  main-is: tests.hs+  hs-source-dirs: tests+  build-depends:       base >=4.6 && < 5.0,+                       pinboard,+                       bytestring,+                       containers,+                       hspec                >= 1.8 ,+                       text,+                       time,+                       aeson,+                       semigroups,+                       QuickCheck,+                       mtl >= 2.2.1,+                       unordered-containers,+                       transformers >= 0.4.0.0+  ghc-options:         -Wall +                       -fno-warn-orphans+  default-language:    Haskell2010  source-repository head   type:     git
src/Pinboard/ApiTypes.hs view
@@ -206,7 +206,7 @@     , "hash"       .= toJSON noteHash     , "title"      .= toJSON noteTitle     , "text"       .= toJSON noteText-    , "length"     .= toJSON (show noteLength)+    , "length"     .= toJSON noteLength     , "created_at" .= toJSON (showNoteTime noteCreatedAt)     , "updated_at" .= toJSON (showNoteTime noteUpdatedAt) ] 
src/Pinboard/Client.hs view
@@ -43,9 +43,7 @@     ,createParserErr     ,httpStatusPinboardError      -- * Client Dependencies-    , module Pinboard.Error-    , module Pinboard.Types-    , module Pinboard.Util+    , module X     ) where  @@ -67,9 +65,9 @@ import           Network.HTTP.Client.TLS  -import Pinboard.Types-import Pinboard.Error-import Pinboard.Util+import Pinboard.Types as X+import Pinboard.Error as X+import Pinboard.Util as X  import qualified Data.ByteString.Lazy        as LBS import qualified Data.Text                   as T@@ -127,7 +125,7 @@     responseBody r <$ checkStatusCodeResponse r  runPinboardSingleJson-    :: (Functor m, MonadIO m, FromJSON a)+    :: (MonadIO m, FromJSON a)     => PinboardConfig            -> PinboardRequest     -> m (Either PinboardError a)@@ -146,8 +144,7 @@                       , "?"                        , T.decodeUtf8 $ paramsToByteString $ ("auth_token", urlEncode False apiToken) : encodeParams requestParams ]    req <- buildReq $ T.unpack url-   res <- liftIO $ httpLbs req mgr-   return res+   liftIO $ httpLbs req mgr  -------------------------------------------------------------------------------- @@ -155,7 +152,7 @@ buildReq url = do   req <- liftIO $ parseUrl $ "https://api.pinboard.in/v1/" <> url   return $ req -    { requestHeaders = [("User-Agent","pinboard.hs/0.9.4")]+    { requestHeaders = [("User-Agent","pinboard.hs/0.9.5")]     , checkStatus = \_ _ _ -> Nothing     } @@ -177,7 +174,7 @@     -> m a decodeJSONResponse s =    let r = eitherDecodeStrict' (LBS.toStrict s) -  in either (throwError . createParserErr . toText) (return . id) r+  in either (throwError . createParserErr . toText) return r  -------------------------------------------------------------------------------- 
+ tests/tests.hs view
@@ -0,0 +1,134 @@+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE FlexibleContexts #-}++module Main where++import           Data.Aeson+import           Data.Aeson.Types (parseEither)+import           Data.Char (isSpace)+import           Data.Monoid+import           Data.Text (Text, pack)+import           Data.List+import           Data.Ord+import           Data.Time.Calendar (Day(..))+import           Data.Time.Clock (UTCTime(..), secondsToDiffTime)+import           Data.Typeable+import           Test.Hspec+import           Test.Hspec.QuickCheck (prop)+import           Test.QuickCheck+import qualified Data.ByteString.Lazy.Char8 as BL8+import qualified Data.HashMap.Strict as HM+import qualified Data.Set as Set++import ApproxEq++import           Pinboard++propJSON :: forall a b. (Arbitrary a, ToJSON a, FromJSON a, Show a, Typeable a, Testable b) +         => (Either String a -> Either String a -> b) +         -> Proxy a +         -> Spec+propJSON eq _ = prop (show (typeOf (undefined :: a)) <> " FromJSON/ToJSON roundtrip") $ \(x :: a) ->+  let actual = parseEither parseJSON (toJSON x)+      expected = Right x+      failMsg = "ACTUAL: " <> show actual <> "\nJSON: " <> BL8.unpack (encode x)+  in counterexample failMsg (actual `eq` expected)++propJSONEq :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, Typeable a, Eq a) => Proxy a -> Spec+propJSONEq = propJSON (==)++propJSONApproxEq :: forall a. (Arbitrary a, ToJSON a, FromJSON a, Show a, Typeable a, ApproxEq a) => Proxy a -> Spec+propJSONApproxEq = propJSON (==~)++instance Arbitrary Text where+  arbitrary = pack <$> arbitrary++instance Arbitrary Day where+  arbitrary = ModifiedJulianDay . (2000 +) <$> arbitrary+  shrink = (ModifiedJulianDay <$>) . shrink . toModifiedJulianDay++instance Arbitrary UTCTime where+  arbitrary = UTCTime <$> arbitrary <*> (secondsToDiffTime <$> choose (0, 86401))++instance Arbitrary Note where+  arbitrary = Note <$> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary++instance Arbitrary NoteList where+  arbitrary = NoteList <$> arbitrary <*> resize 15 arbitrary++instance Arbitrary NoteListItem where+  arbitrary = NoteListItem <$> arbitrary+                           <*> arbitrary+                           <*> arbitrary+                           <*> arbitrary+                           <*> arbitrary+                           <*> arbitrary++instance Arbitrary Posts where+  arbitrary = Posts <$> arbitrary <*> arbitrary <*> (resize 15 arbitrary)++instance Arbitrary Post where+  arbitrary = Post <$> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitrary+                   <*> arbitraryTags++instance Arbitrary JsonTagMap where+  arbitrary = ToJsonTagMap <$> (HM.fromList <$> (listOf $ (,) <$> arbitraryTag <*> arbitrary))++arbitraryTags :: Gen [Tag]+arbitraryTags = listOf arbitraryTag++arbitraryTag :: Gen Tag+arbitraryTag = pack <$> listOf1 (arbitrary `suchThat` (\c -> (not . isSpace) c && (',' /= c)))++-- | Checks if a given list has no duplicates in _O(n log n)_.+hasNoDups :: (Ord a) => [a] -> Bool+hasNoDups = go Set.empty+  where+    go _ [] = True+    go s (x:xs)+      | s' <- Set.insert x s,+        Set.size s' > Set.size s+      = go s' xs+      | otherwise = False++instance Arbitrary PostDates where+  arbitrary = PostDates <$> arbitrary <*> arbitrary <*> (arbitrary `suchThat` isValidDateCount)+    where+      isValidDateCount xs = hasNoDups (fst <$> xs) && all (> 0) (snd <$> xs)++instance ApproxEq PostDates where+  (=~) a b =+    postDatesUser a == postDatesUser b+    && postDatesTag a == postDatesTag b+    && sorted (postDatesCount a) == sorted (postDatesCount b)+    where sorted = sortBy (comparing fst <> comparing snd)++instance Arbitrary Suggested where+  arbitrary = arbitrary >>= \a -> elements [Popular a, Recommended a]++main :: IO ()+main = hspec $ do+  prop "UTCTime" $ \(x :: UTCTime) -> (readNoteTime . showNoteTime) x == x+  describe "JSON instances" $ do+    propJSONEq (Proxy :: Proxy UTCTime)+    propJSONEq (Proxy :: Proxy Post)+    propJSONEq (Proxy :: Proxy Posts)+    propJSONEq (Proxy :: Proxy Note)+    propJSONEq (Proxy :: Proxy NoteList)+    propJSONEq (Proxy :: Proxy NoteListItem)+    propJSONEq (Proxy :: Proxy JsonTagMap)+    propJSONEq (Proxy :: Proxy Suggested)+    propJSONApproxEq (Proxy :: Proxy PostDates)