packages feed

valiant-0.1.0.0: test/Valiant/Binary/NewtypeSpec.hs

{-# LANGUAGE DerivingVia #-}

module Valiant.Binary.NewtypeSpec (spec) where

import Data.Int (Int32, Int64)
import Data.Proxy (Proxy (..))
import Data.Text (Text)
import Data.Vector qualified as V
import Valiant.Binary.Decode ()
import Valiant.Binary.Encode ()
import Valiant.FromRow (DecodeColumn (..), FromRow (..))
import Valiant.ToParams (EncodeField (..), ToParams (..))
import PgWire.Binary.Types (PgDecode (..), PgEncode (..))
import PgWire.Protocol.Oid (oidInt4, oidInt8, oidText)
import Test.Hspec

-- | A newtype that should inherit PgEncode/PgDecode via GeneralizedNewtypeDeriving.
newtype UserId = UserId Int32
  deriving stock (Show, Eq)
  deriving newtype (PgEncode, PgDecode)

-- | Another newtype wrapping Text.
newtype Email = Email Text
  deriving stock (Show, Eq)
  deriving newtype (PgEncode, PgDecode)

-- | A newtype wrapping Int64.
newtype PostId = PostId Int64
  deriving stock (Show, Eq)
  deriving newtype (PgEncode, PgDecode)

-- | A newtype using DerivingVia instead of GeneralizedNewtypeDeriving.
newtype OrgId = OrgId Int32
  deriving stock (Show, Eq)
  deriving (PgEncode, PgDecode) via Int32

spec :: Spec
spec = do
  describe "GeneralizedNewtypeDeriving for PgEncode" $ do
    it "UserId encodes identically to Int32" $ do
      pgEncode (UserId 42) `shouldBe` pgEncode (42 :: Int32)

    it "UserId has the same OID as Int32" $ do
      pgOid (Proxy @UserId) `shouldBe` oidInt4

    it "Email encodes identically to Text" $ do
      pgEncode (Email "alice@example.com") `shouldBe` pgEncode ("alice@example.com" :: Text)

    it "Email has the same OID as Text" $ do
      pgOid (Proxy @Email) `shouldBe` oidText

    it "PostId encodes identically to Int64" $ do
      pgEncode (PostId 999) `shouldBe` pgEncode (999 :: Int64)

    it "PostId has the same OID as Int64" $ do
      pgOid (Proxy @PostId) `shouldBe` oidInt8

  describe "GeneralizedNewtypeDeriving for PgDecode" $ do
    it "decodes UserId from Int32 bytes" $ do
      let bs = pgEncode (42 :: Int32)
      pgDecode bs `shouldBe` Right (UserId 42)

    it "decodes Email from Text bytes" $ do
      let bs = pgEncode ("test@example.com" :: Text)
      pgDecode bs `shouldBe` Right (Email "test@example.com")

    it "decodes PostId from Int64 bytes" $ do
      let bs = pgEncode (123 :: Int64)
      pgDecode bs `shouldBe` Right (PostId 123)

  describe "DerivingVia for PgEncode/PgDecode" $ do
    it "OrgId encodes identically to Int32" $ do
      pgEncode (OrgId 7) `shouldBe` pgEncode (7 :: Int32)

    it "OrgId has the same OID as Int32" $ do
      pgOid (Proxy @OrgId) `shouldBe` oidInt4

    it "OrgId round-trips" $ do
      let bs = pgEncode (OrgId 7)
      pgDecode bs `shouldBe` Right (OrgId 7)

  describe "Newtype round-trip" $ do
    it "UserId encode/decode is identity" $ do
      let uid = UserId 12345
      pgDecode (pgEncode uid) `shouldBe` Right uid

    it "Email encode/decode is identity" $ do
      let email = Email "hello@world.com"
      pgDecode (pgEncode email) `shouldBe` Right email

  describe "Newtypes work with EncodeField" $ do
    it "EncodeField encodes UserId" $ do
      encodeField (UserId 42) `shouldBe` Just (pgEncode (42 :: Int32))

    it "EncodeField encodes Maybe UserId (Just)" $ do
      encodeField (Just (UserId 42)) `shouldBe` Just (pgEncode (42 :: Int32))

    it "EncodeField encodes Maybe UserId (Nothing)" $ do
      encodeField (Nothing @UserId) `shouldBe` Nothing

  describe "Newtypes work with ToParams" $ do
    it "single UserId as ToParams" $ do
      let params = toParams (UserId 42)
      V.length params `shouldBe` 1
      V.head params `shouldBe` Just (pgEncode (42 :: Int32))

    it "tuple of newtypes as ToParams" $ do
      let params = toParams (UserId 1, Email "a@b.com")
      V.length params `shouldBe` 2

  describe "Newtypes work with DecodeColumn" $ do
    it "decodes UserId from a row" $ do
      let row = V.singleton (Just (pgEncode (42 :: Int32)))
      decodeColumn row 0 `shouldBe` Right (UserId 42)

    it "decodes Maybe UserId from NULL" $ do
      let row = V.singleton Nothing
      decodeColumn row 0 `shouldBe` Right (Nothing @UserId)

    it "decodes Maybe UserId from non-NULL" $ do
      let row = V.singleton (Just (pgEncode (42 :: Int32)))
      decodeColumn row 0 `shouldBe` Right (Just (UserId 42))

  describe "Newtypes work with FromRow" $ do
    it "decodes single UserId via FromRow" $ do
      let row = V.singleton (Just (pgEncode (42 :: Int32)))
      fromRow row `shouldBe` Right (UserId 42)

    it "decodes tuple with newtypes via FromRow" $ do
      let row = V.fromList [Just (pgEncode (1 :: Int32)), Just (pgEncode ("alice" :: Text))]
      fromRow row `shouldBe` Right (UserId 1, Email "alice")