packages feed

railroad-0.1.0.0: test/RailroadSpec.hs

{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeApplications #-}
module RailroadSpec where


import           Data.Validation
import           Effectful
import           Effectful.Error.Dynamic
import           Railroad
import           Test.Hspec

-- Helper to run the Railroad effects in a pure context
runRail :: Eff '[Error String] a -> Either String a
runRail = runPureEff . runErrorNoCallStack

spec :: Spec
spec = do
  describe "Bifurcate Instances" $ do
    it "bifurcates Bool" $ do
      bifurcate True  `shouldBe` (Right () :: Either () ())
      bifurcate False `shouldBe` (Left ()  :: Either () ())

    it "bifurcates Maybe" $ do
      bifurcate (Just 5) `shouldBe` (Right 5 :: Either () Int)
      bifurcate (Nothing :: Maybe Int) `shouldBe` (Left () :: Either () Int)

    it "bifurcates Either" $ do
      bifurcate (Right 5 :: Either String Int) `shouldBe` Right 5
      bifurcate (Left "fire" :: Either String Int) `shouldBe` Left "fire"

    it "bifurcates Validation" $ do
      bifurcate (Success 10 :: Validation String Int) `shouldBe` Right 10
      bifurcate (Failure "ice" :: Validation String Int) `shouldBe` Left "ice"

    it "bifurcates List of Bool" $ do
        bifurcate [True, True] `shouldBe` (Right [(), ()] :: Either () [()])
        bifurcate [True, False, True] `shouldBe` (Left () :: Either () [()])

    it "bifurcates List of Maybe" $ do
      bifurcate [Just 1, Just 2] `shouldBe` (Right [1, 2] :: Either () [Int])
      bifurcate [Just 1, Nothing] `shouldBe` (Left () :: Either () [Int])

    it "bifurcates List of Either" $ do
      let input0 = [Right 1, Right 2] :: [Either String Int]
      bifurcate input0 `shouldBe` Right [1, 2]
      let input1 = [Right 1, Left "first", Left "second"] :: [Either String Int]
      bifurcate input1 `shouldBe` Left "first"

    it "List of Validation (Error Accumulation)" $ do
      let input = [Success 1, Success 2] :: [Validation String Int]
      bifurcate input `shouldBe` Right [1, 2]
        -- Note: Validation accumulates because String is a Semigroup
      let input = [Success 1, Failure "Fail A ", Failure "Fail B"] :: [Validation String Int]
      bifurcate input `shouldBe` Left "Fail A Fail B"

  describe "Basic Operators (? and ??)" $ do
    it "unwraps success values with (?)" $ do
      runRail (pure (Just 10 :: Maybe Int) ? "missing") `shouldBe` Right 10

    it "throws constant error on failure with (?)" $ do
      runRail (pure (Nothing :: Maybe ()) ? "missing") `shouldBe` Left "missing"

    it "maps internal errors with (??)" $ do
      let action = pure (Left "original" :: Either String String)
      runRail (action ?? reverse) `shouldBe` Left "lanigiro"

  describe "Predicate Operator (?>)" $ do
    it "passes when predicate is met" $ do
      runRail (pure 10 ?> (> 5) $ const "too small") `shouldBe` Right 10

    it "fails when predicate is not met" $ do
      runRail (pure 4 ?> (> 5) $ const "too small") `shouldBe` Left "too small"

  describe "Recovery Operators (?~ and ??~)" $ do
    it "recovers to a constant value with (?~)" $ do
      runPureEff (pure Nothing ?~ 0) `shouldBe` (0 :: Int)

    it "recovers using a function with (??~)" $ do
      runPureEff (pure (Left "err") ??~ length) `shouldBe` 3

  describe "Cardinality Operators" $ do
    describe "(?+)" $ do
      it "succeeds on non-empty list" $ do
        runRail (pure [1, 2, 3] ?+ "empty") `shouldBe` Right [1, 2, 3]
      it "fails on empty list" $ do
        runRail (pure ([] :: [Int]) ?+ "empty") `shouldBe` Left "empty"

    describe "(?!)" $ do
      let toErr = cardinalityErr "none" (const "too many")
      it "extracts the single element" $ do
        runRail (pure [42] ?! toErr) `shouldBe` Right 42
      it "fails on empty" $ do
        runRail (pure ([] :: [Int]) ?! toErr) `shouldBe` Left "none"
      it "fails on multiple elements" $ do
        runRail (pure [1, 2] ?! toErr) `shouldBe` Left "too many"

    describe "(?∅)" $ do
      it "succeeds on empty" $ do
        runRail (pure [] ?∅ const "not empty") `shouldBe` Right ()
      it "fails on non-empty" $ do
        runRail (pure [1] ?∅ const "not empty") `shouldBe` Left "not empty"