packages feed

warlock-0.1.0.0: test/Warlock/VirtualFieldsSpec.hs

{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE UndecidableInstances #-}
module Warlock.VirtualFieldsSpec (spec) where

import Test.Hspec
import Warlock
import qualified Witch as W
import GHC.Records (HasField(..))

-- Virtual field example: Person with computed fullName
data Person = Person
  { firstName :: String
  , lastName :: String
  , age :: Int
  } deriving (Show, Eq)

instance HasField "fullName" Person String where
  getField (Person first last _) = first ++ " " ++ last

instance HasField "category" Person String where
  getField (Person _ _ a) = if a < 18 then "minor" else "adult"

data Employee = Employee
  { empName :: String
  , empAge :: Int
  , ageGroup :: String
  } deriving (Show, Eq)

deriveAutomap
  ( ByName $
    defaultConfig
    `withRules`
    [ virtualField 'empName "fullName"
    , rename 'empAge 'age
    , virtualField 'ageGroup "category"
    ]
  )
  ''Person
  ''Employee

-- Rectangle with computed fields
data Rectangle = Rectangle
  { width :: Double
  , height :: Double
  } deriving (Show, Eq)

instance HasField "area" Rectangle Double where
  getField (Rectangle w h) = w * h

instance HasField "perimeter" Rectangle Double where
  getField (Rectangle w h) = 2 * (w + h)

instance HasField "aspectRatio" Rectangle Double where
  getField (Rectangle w h) = w / h

data RectangleInfo = RectangleInfo
  { size :: Double
  , border :: Double
  , ratio :: Double
  } deriving (Show, Eq)

deriveAutomap
  ( ByName $
    defaultConfig
    { staticRules =
        [ virtualField 'size "area"
        , virtualField 'border "perimeter"
        , virtualField 'ratio "aspectRatio"
        ]
    }
  )
  ''Rectangle
  ''RectangleInfo

spec :: Spec
spec = do
  describe "Virtual fields (HasField)" $ do
    it "can map from computed fullName field" $ do
      let person = Person "Alice" "Smith" 30
      let employee = W.from person :: Employee
      empName employee `shouldBe` "Alice Smith"
      empAge employee `shouldBe` 30
      ageGroup employee `shouldBe` "adult"

    it "computes category based on age" $ do
      let minor = Person "Bob" "Jones" 15
      let emp = W.from minor :: Employee
      ageGroup emp `shouldBe` "minor"

    it "can map multiple computed fields at once" $ do
      let rect = Rectangle 4.0 3.0
      let info = W.from rect :: RectangleInfo
      size info `shouldBe` 12.0
      border info `shouldBe` 14.0
      ratio info `shouldSatisfy` (\r -> abs (r - 1.333333) < 0.001)