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"