{-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE MultiParamTypeClasses #-} module Warlock.DisassembleFieldsSpec (spec) where import Test.Hspec import Warlock import Language.Haskell.TH (varE) import qualified Witch as W -- V1: combined field data PersonV1 = PersonV1 { fullName :: String , age :: Int } deriving (Show, Eq) -- V2: separate fields data PersonV2 = PersonV2 { firstName :: String , lastName :: String , personAge :: Int } deriving (Show, Eq) -- Split fullName into firstName and lastName deriveAutomap ( ByName $ defaultConfig `withRules` ( disassembleFields 'fullName [ 'firstName .= do src <- getSource pure [| case words $src of (f:_:_) -> f [single] -> single _ -> "" |] , 'lastName .= do src <- getSource pure [| case words $src of (_:l:_) -> l _ -> "" |] ] ++ [ rename 'personAge 'age ] ) ) ''PersonV1 ''PersonV2 -- Another example: address parsing data AddressV1 = AddressV1 { fullAddress :: String , city :: String } deriving (Show, Eq) data AddressV2 = AddressV2 { street :: String , zipCode :: String , addressCity :: String } deriving (Show, Eq) -- Split fullAddress into street and zipCode deriveAutomap ( ByName $ defaultConfig `withRules` ( disassembleFields 'fullAddress [ 'street .= do src <- getSource pure [| let parts = reverse $ words $src in case parts of (_:rest) -> unwords (reverse rest) _ -> $src |] , 'zipCode .= do src <- getSource pure [| let parts = reverse $ words $src in case parts of (zip:_) -> zip _ -> "" |] ] ++ [ rename 'addressCity 'city ] ) ) ''AddressV1 ''AddressV2 spec :: Spec spec = do describe "disassembleFields" $ do it "splits fullName into firstName and lastName" $ do let person = PersonV1 "John Doe" 30 let result = W.from @PersonV1 @PersonV2 person result `shouldBe` PersonV2 "John" "Doe" 30 it "handles single-word names" $ do let person = PersonV1 "Madonna" 65 let result = W.from @PersonV1 @PersonV2 person result `shouldBe` PersonV2 "Madonna" "" 65 it "splits address into street and zipCode" $ do let addr = AddressV1 "123 Main St 12345" "Springfield" let result = W.from @AddressV1 @AddressV2 addr result `shouldBe` AddressV2 "123 Main St" "12345" "Springfield" it "handles address without zipCode" $ do let addr = AddressV1 "Downtown" "Chicago" let result = W.from @AddressV1 @AddressV2 addr -- "Downtown" is treated as zipCode when there's only one word result `shouldBe` AddressV2 "" "Downtown" "Chicago"