@@ -17,7 +17,9 @@ import Test.Spec.Runner (runSpec)
1717import Data.Either (Either(..))
1818import Data.Variant (match)
1919import Yoga.Om as Om
20−import Yoga.Om.Layer (OmLayer, Scope, makeLayer, makeScopedLayer, bracketLayer, fresh, combineRequirements, runLayer, runScoped, runScopedWith, withScoped, provide)
20+import Data.Time.Duration (Milliseconds(..))
21+import Effect.Aff.Retry (RetryStatus(..), limitRetries, constantDelay)
22+import Yoga.Om.Layer (OmLayer, Scope, makeLayer, makeScopedLayer, bracketLayer, fresh, combineRequirements, runLayer, runScoped, runScopedWith, withScoped, provide, recovering, repeating)
2123
2224-- Example types
2325type Config = { port :: Int, host :: String }
@@ -516,3 +518,101 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
516518 result.b `shouldEqual` "B"
517519 finalLog <- liftEffect $ Ref.read log
518520 finalLog `shouldEqual` [ "a", "b" ]
521+
522+ describe "Layer recovering" do
523+
524+ it "retries layer construction on matching errors" do
525+ attemptsRef <- liftEffect $ Ref.new 0
526+ let
527+ layer :: OmLayer () (dbError :: String) { db :: String }
528+ layer = makeLayer do
529+ attempts <- Ref.modify (_ + 1) attemptsRef # liftEffect
530+ when (attempts < 3) do
531+ Om.throw { dbError: "connection refused" }
532+ pure { db: "connected" }
533+
534+ retried = layer
535+ # recovering (constantDelay (Milliseconds 0.0) <> limitRetries 5)
536+ (\_ -> { dbError: \_ -> pure true })
537+
538+ result <- Om.runOm {}
539+ { exception: \_ -> pure { db: "" }, dbError: \_ -> pure { db: "" } }
540+ (runLayer {} retried)
541+ result.db `shouldEqual` "connected"
542+ attempts <- liftEffect $ Ref.read attemptsRef
543+ attempts `shouldEqual` 3
544+
545+ it "does not retry on non-matching errors" do
546+ attemptsRef <- liftEffect $ Ref.new 0
547+ let
548+ layer :: OmLayer () (dbError :: String, authError :: String) { db :: String }
549+ layer = makeLayer do
550+ Ref.modify_ (_ + 1) attemptsRef # liftEffect
551+ Om.throw { authError: "unauthorized" }
552+
553+ retried = layer
554+ # recovering (constantDelay (Milliseconds 0.0) <> limitRetries 3)
555+ (\_ -> { dbError: \_ -> pure true })
556+
557+ _ <- Om.runOm {}
558+ { exception: \_ -> pure { db: "" }
559+ , dbError: \_ -> pure { db: "" }
560+ , authError: \_ -> pure { db: "" }
561+ }
562+ (runLayer {} retried)
563+ attempts <- liftEffect $ Ref.read attemptsRef
564+ attempts `shouldEqual` 1
565+
566+ it "uses RetryStatus in the check" do
567+ attemptsRef <- liftEffect $ Ref.new 0
568+ let
569+ layer :: OmLayer () (dbError :: String) { db :: String }
570+ layer = makeLayer do
571+ Ref.modify_ (_ + 1) attemptsRef # liftEffect
572+ Om.throw { dbError: "fail" }
573+
574+ retried = layer
575+ # recovering (constantDelay (Milliseconds 0.0) <> limitRetries 10)
576+ (\(RetryStatus s) -> { dbError: \_ -> pure (s.iterNumber < 2) })
577+
578+ _ <- Om.runOm {}
579+ { exception: \_ -> pure { db: "" }, dbError: \_ -> pure { db: "" } }
580+ (runLayer {} retried)
581+ attempts <- liftEffect $ Ref.read attemptsRef
582+ attempts `shouldEqual` 3
583+
584+ describe "Layer repeating" do
585+
586+ it "repeats layer construction while condition holds" do
587+ counterRef <- liftEffect $ Ref.new 0
588+ let
589+ layer :: OmLayer () () { count :: Int }
590+ layer = makeLayer do
591+ count <- Ref.modify (_ + 1) counterRef # liftEffect
592+ pure { count }
593+
594+ repeated = layer
595+ # repeating (constantDelay (Milliseconds 0.0) <> limitRetries 10)
596+ (\_ r -> pure (r.count < 5))
597+
598+ result <- Om.runOm {}
599+ { exception: \_ -> pure { count: 0 } }
600+ (runLayer {} repeated)
601+ result.count `shouldEqual` 5
602+
603+ it "stops when policy exhausted" do
604+ counterRef <- liftEffect $ Ref.new 0
605+ let
606+ layer :: OmLayer () () { count :: Int }
607+ layer = makeLayer do
608+ count <- Ref.modify (_ + 1) counterRef # liftEffect
609+ pure { count }
610+
611+ repeated = layer
612+ # repeating (constantDelay (Milliseconds 0.0) <> limitRetries 3)
613+ (\_ _ -> pure true)
614+
615+ result <- Om.runOm {}
616+ { exception: \_ -> pure { count: 0 } }
617+ (runLayer {} repeated)
618+ result.count `shouldEqual` 4