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 +5/−1
- faktory.cabal +4/−1
- library/Faktory/Job.hs +19/−0
- library/Faktory/JobFailure.hs +28/−0
- tests/Faktory/JobSpec.hs +132/−0
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