packages feed

warlock-0.1.0.0: test/Warlock/DisassembleFieldsSpec.hs

{-# 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"