packages feed

faktory 1.1.0.0 → 1.1.1.0

raw patch · 5 files changed

+188/−2 lines, 5 filesdep +aeson-qqPVP ok

version bump matches the API change (PVP)

Dependencies added: aeson-qq

API changes (from Hackage documentation)

+ Faktory.Job: jobRetriesRemaining :: Job arg -> Int
+ Faktory.JobFailure: JobFailure :: Int -> UTCTime -> Maybe UTCTime -> Maybe Text -> Maybe Text -> Maybe [Text] -> JobFailure
+ Faktory.JobFailure: [jfBacktrace] :: JobFailure -> Maybe [Text]
+ Faktory.JobFailure: [jfErrorMessage] :: JobFailure -> Maybe Text
+ Faktory.JobFailure: [jfErrorType] :: JobFailure -> Maybe Text
+ Faktory.JobFailure: [jfFailedAt] :: JobFailure -> UTCTime
+ Faktory.JobFailure: [jfNextAt] :: JobFailure -> Maybe UTCTime
+ Faktory.JobFailure: [jfRetryCount] :: JobFailure -> Int
+ Faktory.JobFailure: data JobFailure
+ Faktory.JobFailure: instance Data.Aeson.Types.FromJSON.FromJSON Faktory.JobFailure.JobFailure

Files

CHANGELOG.md view
@@ -1,6 +1,10 @@-## [*Unreleased*](https://github.com/frontrowed/faktory_worker_haskell/compare/v1.1.0.0...main)+## [*Unreleased*](https://github.com/frontrowed/faktory_worker_haskell/compare/v1.1.1.0...main)  None++## [v1.1.1.0](https://github.com/frontrowed/faktory_worker_haskell/compare/v1.1.0.1...v1.1.1.0)++- Add `jobRemainingRetries`  ## [v1.1.0.0](https://github.com/frontrowed/faktory_worker_haskell/compare/v1.0.3.1...v1.1.0.0) 
faktory.cabal view
@@ -1,6 +1,6 @@ cabal-version:   1.18 name:            faktory-version:         1.1.0.0+version:         1.1.1.0 license:         MIT license-file:    LICENSE copyright:       2018 Freckle Education@@ -58,6 +58,7 @@         Faktory.Ent.Tracking         Faktory.Job         Faktory.Job.Custom+        Faktory.JobFailure         Faktory.JobOptions         Faktory.JobState         Faktory.Prelude@@ -178,6 +179,7 @@         Faktory.Ent.BatchSpec         Faktory.Ent.TrackingSpec         Faktory.JobOptionsSpec+        Faktory.JobSpec         Faktory.Test         FaktorySpec         Paths_faktory@@ -200,6 +202,7 @@      build-depends:         aeson >=1.5.5.1,+        aeson-qq >=0.8.3,         async >=2.2.2,         base >=4.13 && <5,         faktory -any,
library/Faktory/Job.hs view
@@ -15,6 +15,7 @@   , jobJid   , jobArg   , jobOptions+  , jobRetriesRemaining   ) where  import Faktory.Prelude@@ -22,9 +23,11 @@ import Data.Aeson import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NE+import Data.Semigroup (Last(..)) import Data.Time (UTCTime) import Faktory.Client (Client(..)) import Faktory.Connection (ConnectionInfo(..))+import Faktory.JobFailure import Faktory.JobOptions import Faktory.Producer (Producer(..), pushJob) import Faktory.Settings (Namespace, Settings(..))@@ -39,6 +42,7 @@   -- ^ Faktory needs to serialize args as a list, but we like a single-argument   -- interface so that's what we expose. See @'jobArg'@.   , jobOptions :: JobOptions+  , jobFailure :: Maybe JobFailure   }  -- | Perform a Job with the given options@@ -85,11 +89,18 @@     , jobAt = Nothing     , jobArgs = pure arg     , jobOptions = jobtype "Default"+    , jobFailure = Nothing     }  jobArg :: Job arg -> arg jobArg Job {..} = NE.head jobArgs +jobRetriesRemaining :: Job arg -> Int+jobRetriesRemaining job = max 0 $ enqueuedRetry - attemptCount+ where+  enqueuedRetry = maybe faktoryDefaultRetry getLast $ joRetry $ jobOptions job+  attemptCount = maybe 0 ((+ 1) . jfRetryCount) $ jobFailure job+ instance ToJSON args => ToJSON (Job args) where   toJSON = object . toPairs   toEncoding = pairs . mconcat . toPairs@@ -113,5 +124,13 @@     <*> o .:? "at"     <*> o .: "args"     <*> parseJSON (Object o)+    <*> o .:? "failure"  type JobId = String++-- | https://github.com/contribsys/faktory/wiki/Job-Errors#the-process+--+-- > By default Faktory will retry a job 25 times+--+faktoryDefaultRetry :: Int+faktoryDefaultRetry = 25
+ library/Faktory/JobFailure.hs view
@@ -0,0 +1,28 @@+module Faktory.JobFailure+  ( JobFailure(..)+  ) where++import Faktory.Prelude++import Data.Aeson+import Data.Time (UTCTime)++data JobFailure = JobFailure+  { jfRetryCount :: Int+  , jfFailedAt :: UTCTime+  , jfNextAt :: Maybe UTCTime+  , jfErrorMessage :: Maybe Text+  , jfErrorType :: Maybe Text+  , jfBacktrace :: Maybe [Text]+  }++-- brittany-disable-next-binding++instance FromJSON JobFailure where+  parseJSON = withObject "Failure" $ \o -> JobFailure+    <$> o .: "retry_count"+    <*> o .: "failed_at"+    <*> o .:? "next_at"+    <*> o .:? "error_message"+    <*> o .:? "error_type"+    <*> o .:? "backtrace"
+ tests/Faktory/JobSpec.hs view
@@ -0,0 +1,132 @@+{-# LANGUAGE QuasiQuotes #-}++module Faktory.JobSpec+  ( spec+  ) where++import Faktory.Prelude++import Data.Aeson+import Data.Aeson.QQ+import Data.Time (getCurrentTime)+import Faktory.Job+import Test.Hspec++spec :: Spec+spec = do+  -- https://github.com/contribsys/faktory/issues/374#issuecomment-902075572+  describe "jobRetriesRemaining" $ do+    it "handles first consumed" $ do+      job <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 2+        }+      |]++      jobRetriesRemaining job `shouldBe` 2++    it "handles a first retry" $ do+      now <- getCurrentTime+      job <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 2+        , "failure":+          { "retry_count": 0+          , "failed_at": #{now}+          }+        }+      |]++      jobRetriesRemaining job `shouldBe` 1++    it "handles a final retry" $ do+      now <- getCurrentTime+      job <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 2+        , "failure":+          { "retry_count": 1+          , "failed_at": #{now}+          }+        }+      |]++      jobRetriesRemaining job `shouldBe` 0++    it "uses Faktory's default" $ do+      job <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        }+      |]++      jobRetriesRemaining job `shouldBe` 25++    it "handles retry -1" $ do+      now <- getCurrentTime+      job1 <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": -1+        }+      |]+      job2 <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": -1+        , "failure":+          { "retry_count": 1+          , "failed_at": #{now}+          }+        }+      |]++      jobRetriesRemaining job1 `shouldBe` 0+      jobRetriesRemaining job2 `shouldBe` 0++    it "handles retry 0" $ do+      now <- getCurrentTime+      job1 <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 0+        }+      |]+      job2 <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 0+        , "failure":+          { "retry_count": 1+          , "failed_at": #{now}+          }+        }+      |]++      jobRetriesRemaining job1 `shouldBe` 0+      jobRetriesRemaining job2 `shouldBe` 0++    it "handles nonsense" $ do+      now <- getCurrentTime+      job <- decodeJob [aesonQQ|+        { "jid": "abc"+        , "args": [""]+        , "retry": 20+        , "failure":+          { "retry_count": 1000+          , "failed_at": #{now}+          }+        }+      |]++      jobRetriesRemaining job `shouldBe` 0++decodeJob :: Value -> IO (Job Text)+decodeJob v = case fromJSON v of+  Error err -> do+    expectationFailure $ "Job JSON did not parse: " <> err+    error "unreachable" -- I hate that hspec makes ^ IO () and not IO a+  Success job -> pure job