@@ -13,6 +13,8 @@ import Test.Spec (describe, it)
1313import Test.Spec.Assertions (shouldEqual)
1414import Test.Spec.Reporter (consoleReporter)
1515import Test.Spec.Runner (runSpec)
16+import Data.Either (Either(..))
17+import Data.Variant (match)
1618import Yoga.Om as Om
1719import Yoga.Om.Layer (OmLayer, Scope, makeLayer, makeScopedLayer, bracketLayer, fresh, combineRequirements, runLayer, runScoped, withScoped, provide)
1820
@@ -305,3 +307,101 @@ main = launchAff_ $ runSpec [ consoleReporter ] do
305307 result.y `shouldEqual` "43"
306308 finalLog <- liftEffect $ Ref.read log
307309 finalLog `shouldEqual` [ "acquire", "release" ]
310+
311+ describe "Error Channel" do
312+
313+ it "composes layers with different error types" do
314+ let
315+ dbLayer :: OmLayer () (db :: String) (dbError :: String)
316+ dbLayer = makeLayer do
317+ pure { db: "connected" }
318+
319+ cacheLayer :: OmLayer () (cache :: String) (cacheError :: String)
320+ cacheLayer = makeLayer do
321+ pure { cache: "ready" }
322+
323+ combined :: OmLayer () (db :: String, cache :: String) (dbError :: String, cacheError :: String)
324+ combined = combineRequirements dbLayer cacheLayer
325+
326+ result <- Om.runOm {}
327+ { exception: \_ -> pure { db: "", cache: "" }
328+ , dbError: \_ -> pure { db: "", cache: "" }
329+ , cacheError: \_ -> pure { db: "", cache: "" }
330+ }
331+ (runLayer {} combined)
332+ result.db `shouldEqual` "connected"
333+ result.cache `shouldEqual` "ready"
334+
335+ it "propagates errors from the first layer" do
336+ let
337+ failingLayer :: OmLayer () (db :: String) (dbError :: String)
338+ failingLayer = makeLayer do
339+ Om.throw { dbError: "connection refused" }
340+
341+ okLayer :: OmLayer () (cache :: String) ()
342+ okLayer = makeLayer do
343+ pure { cache: "ready" }
344+
345+ combined = combineRequirements failingLayer okLayer
346+
347+ result <- Om.runReader {} (runLayer {} combined)
348+ # liftAff
349+ case result of
350+ Left err -> do
351+ let
352+ msg = err # match
353+ { exception: \_ -> "exception"
354+ , dbError: \e -> e
355+ }
356+ msg `shouldEqual` "connection refused"
357+ Right _ -> "should have failed" `shouldEqual` "but succeeded"
358+
359+ it "propagates errors from the second layer" do
360+ let
361+ okLayer :: OmLayer () (db :: String) ()
362+ okLayer = makeLayer do
363+ pure { db: "connected" }
364+
365+ failingLayer :: OmLayer () (cache :: String) (cacheError :: String)
366+ failingLayer = makeLayer do
367+ Om.throw { cacheError: "cache unavailable" }
368+
369+ combined = combineRequirements okLayer failingLayer
370+
371+ result <- Om.runReader {} (runLayer {} combined)
372+ # liftAff
373+ case result of
374+ Left err -> do
375+ let
376+ msg = err # match
377+ { exception: \_ -> "exception"
378+ , cacheError: \e -> e
379+ }
380+ msg `shouldEqual` "cache unavailable"
381+ Right _ -> "should have failed" `shouldEqual` "but succeeded"
382+
383+ it "propagates errors through vertical composition" do
384+ let
385+ baseLayer :: OmLayer () (base :: String) (baseError :: String)
386+ baseLayer = makeLayer do
387+ Om.throw { baseError: "base failed" }
388+
389+ upperLayer :: OmLayer (base :: String) (upper :: String) (upperError :: String)
390+ upperLayer = makeLayer do
391+ { base } <- Om.ask
392+ pure { upper: base <> "-extended" }
393+
394+ composed = upperLayer `provide` baseLayer
395+
396+ result <- Om.runReader {} (runLayer {} composed)
397+ # liftAff
398+ case result of
399+ Left err -> do
400+ let
401+ msg = err # match
402+ { exception: \_ -> "exception"
403+ , baseError: \e -> e
404+ , upperError: \_ -> "upper"
405+ }
406+ msg `shouldEqual` "base failed"
407+ Right _ -> "should have failed" `shouldEqual` "but succeeded"