packages feed

pg-schema-0.5.0.0: test/PgTagJsonSpec.hs

-- | JSON encode/decode checks for `PgTag ann r` (replaces legacy HList doctest tests).
module PgTagJsonSpec (runTests) where

import Control.Monad (unless)
import Data.Aeson (Result (..), decode, encode, fromJSON, toJSON)
import Data.ByteString.Lazy.Char8 (pack)
import Data.Text (Text)
import GHC.Int (Int16)
import PgSchema.DML
import PgSchema.Schema.Catalog (PGC, PgCatalog)
import PgSchema.Schema.Info (PgClassShort (..), PgEnum (..), PgRelation (..))

data RenSnake :: Renamer

type instance ApplyRenamer RenSnake s = CamelToSnake s

type AnnCons = 'Ann RenSnake PgCatalog 3 (PGC "pg_constraint")

type AnnEnum = 'Ann RenSnake PgCatalog 3 (PGC "pg_enum")

runTests :: IO ()
runTests = do
  pgEnumEncodeShape
  pgEnumDecodeRoundTrip
  pgEnumMissingKeyRejected
  pgEnumExtraKeysIgnored
  pgConstraintJsonRoundTrip
  pgConstraintDuplicateFieldRoundTrip
  putStrLn "PgTag JSON (Ann / PgCatalog / CamelToSnake) checks passed."

-- | Stable field order follows Generics: @enumlabel@ then @enumsortorder@.
pgEnumEncodeShape :: IO ()
pgEnumEncodeShape = do
  let
    val :: PgTag AnnEnum PgEnum
    val = PgTag PgEnum { enumlabel = "lbl", enumsortorder = 42 :: Double }
    enc = encode val
  unless
    ( enc
        == pack "{\"enumlabel\":\"lbl\",\"enumsortorder\":42}"
        || enc == pack "{\"enumlabel\":\"lbl\",\"enumsortorder\":42.0}"
    )
    $ error $ "PgEnum encode: got " ++ show enc

pgEnumDecodeRoundTrip :: IO ()
pgEnumDecodeRoundTrip = do
  let
    val :: PgTag AnnEnum PgEnum
    val = PgTag PgEnum { enumlabel = "a", enumsortorder = 1.5 }
  case decode (encode val) of
    Nothing -> error "PgEnum decode: expected Just"
    Just v ->
      unless (v == val)
        $ error $ "PgEnum decode: got " ++ show v

pgEnumMissingKeyRejected :: IO ()
pgEnumMissingKeyRejected =
  case decode @(PgTag AnnEnum PgEnum) (pack "{\"enumsortorder\":1}") of
    Just _ -> error "PgEnum: expected decode failure for missing enumlabel"
    Nothing -> pure ()

pgEnumExtraKeysIgnored :: IO ()
pgEnumExtraKeysIgnored = do
  let
    val :: PgTag AnnEnum PgEnum
    val = PgTag PgEnum { enumlabel = "x", enumsortorder = 1 :: Double }
  case decode (pack "{\"enumlabel\":\"x\",\"enumsortorder\":1,\"extra\":99}") of
    Nothing -> error "PgEnum extra keys: expected Just"
    Just v ->
      unless (v == val)
        $ error $ "PgEnum extra keys: got " ++ show v

pgConstraintJsonRoundTrip :: IO ()
pgConstraintJsonRoundTrip = do
  let
    rel :: PgRelation
    rel =
      PgRelation
        { constraint__namespace = "nspname" =: ("a" :: Text)
        , conname = "b"
        , constraint__class = PgClassShort ("nspname" =: ("c" :: Text)) "d"
        , constraint__fclass = PgClassShort ("nspname" =: ("e" :: Text)) "f"
        , conkey = pgArr' [1 :: Int16, 2]
        , confkey = pgArr' []
        }
    tagged :: PgTag AnnCons PgRelation
    tagged = PgTag rel
  case fromJSON (toJSON tagged) of
    Error e -> error $ "PgRelation fromJSON: " ++ e
    Success r ->
      unless (r == tagged)
        $ error $ "PgRelation round-trip: got " ++ show r

pgConstraintDuplicateFieldRoundTrip :: IO ()
pgConstraintDuplicateFieldRoundTrip = do
  let
    rel :: PgRelation
    rel =
      PgRelation
        { constraint__namespace = "nspname" =: ("a" :: Text)
        , conname = "b"
        , constraint__class = PgClassShort ("nspname" =: ("c" :: Text)) "d"
        , constraint__fclass = PgClassShort ("nspname" =: ("e" :: Text)) "f"
        , conkey = pgArr' [1 :: Int16, 2]
        , confkey = pgArr' []
        }
    r = "conname" =: ("x" :: Text) :. "conname" =: ("z" :: Text)
      :. rel :. "conname" =: ("y" :: Text)
    tagged = PgTag @AnnCons r
  case fromJSON (toJSON tagged) of
    Error e -> error $ "PgRelation duplicate conname fromJSON: " ++ e
    Success res ->
      unless (res == tagged)
        $ error $ "PgRelation duplicate conname round-trip: got " ++ show res