packages feed

hpqtypes-1.15.0.0: src/Database/PostgreSQL/PQTypes/JSON.hs

module Database.PostgreSQL.PQTypes.JSON
  ( -- * Helpers, to be used with @deriving via@ (@-XDerivingVia@).
    JSON (..)
  , JSONB (..)

    -- * Unparsed JSON values
  , RawJSON (..)
  , encodeRawJSON
  , decodeRawJSON
  , eitherDecodeRawJSON
  , RawJSONB (..)
  , encodeRawJSONB
  , decodeRawJSONB
  , eitherDecodeRawJSONB
  ) where

import Data.Aeson
import Data.ByteString.Char8 qualified as BS
import Data.ByteString.Lazy.Char8 qualified as BSL
import Data.Typeable
import Foreign.Ptr

import Database.PostgreSQL.PQTypes.Format
import Database.PostgreSQL.PQTypes.FromSQL
import Database.PostgreSQL.PQTypes.Internal.C.Types
import Database.PostgreSQL.PQTypes.Internal.Utils
import Database.PostgreSQL.PQTypes.ToSQL

-- | Wrapper that serializes and deserializes the underlying type as @json@
-- with its 'ToJSON' and 'FromJSON' instances.
--
-- /Note:/ To get the SQL instances for a type of your own, use
-- @DerivingVia@:
--
-- @
-- data Foo = ...
--   deriving anyclass (FromJSON, ToJSON)
--   deriving (PQFormat, ToSQL, FromSQL) via JSON Foo
-- @
newtype JSON a = JSON {unJSON :: a}
  deriving (Eq, Functor, Ord, Show)

instance PQFormat (JSON a) where
  pqFormat = BS.pack "%json"

instance (FromJSON a, Typeable a) => FromSQL (JSON a) where
  type PQBase (JSON a) = PGbytea
  fromSQL = fmap JSON . aesonFromSQL

instance ToJSON a => ToSQL (JSON a) where
  type PQDest (JSON a) = PGbytea
  toSQL = aesonToSQL . unJSON

----------------------------------------

-- | Wrapper that serializes and deserializes the underlying type as @jsonb@
-- with its 'ToJSON' and 'FromJSON' instances.
--
-- /Note:/ To get the SQL instances for a type of your own, use
-- @DerivingVia@:
--
-- @
-- data Foo = ...
--   deriving anyclass (FromJSON, ToJSON)
--   deriving (PQFormat, ToSQL, FromSQL) via JSONB Foo
-- @
newtype JSONB a = JSONB {unJSONB :: a}
  deriving (Eq, Functor, Ord, Show)

instance PQFormat (JSONB a) where
  pqFormat = BS.pack "%jsonb"

instance (FromJSON a, Typeable a) => FromSQL (JSONB a) where
  type PQBase (JSONB a) = PGbytea
  fromSQL = fmap JSONB . aesonFromSQL

instance ToJSON a => ToSQL (JSONB a) where
  type PQDest (JSONB a) = PGbytea
  toSQL = aesonToSQL . unJSONB

----------------------------------------

-- | A @json@ value as its unparsed UTF-8 text.
newtype RawJSON = RawJSON {unRawJSON :: BS.ByteString}
  deriving (Eq, Ord, Show)

instance PQFormat RawJSON where
  pqFormat = BS.pack "%json"

instance FromSQL RawJSON where
  type PQBase RawJSON = PGbytea
  fromSQL = fmap RawJSON . fromSQL

instance ToSQL RawJSON where
  type PQDest RawJSON = PGbytea
  toSQL = toSQL . unRawJSON

-- | Encode a value with its 'ToJSON' instance.
--
-- @since 1.15.0.0
encodeRawJSON :: ToJSON a => a -> RawJSON
encodeRawJSON = RawJSON . BSL.toStrict . encode

-- | Decode a value with its 'FromJSON' instance.
--
-- @since 1.15.0.0
decodeRawJSON :: FromJSON a => RawJSON -> Maybe a
decodeRawJSON = decodeStrict' . unRawJSON

-- | Decode a value with its 'FromJSON' instance and report the reason for a
-- failure.
--
-- @since 1.15.0.0
eitherDecodeRawJSON :: FromJSON a => RawJSON -> Either String a
eitherDecodeRawJSON = eitherDecodeStrict' . unRawJSON

----------------------------------------

-- | A @jsonb@ value as its unparsed UTF-8 text.
newtype RawJSONB = RawJSONB {unRawJSONB :: BS.ByteString}
  deriving (Eq, Ord, Show)

instance PQFormat RawJSONB where
  pqFormat = BS.pack "%jsonb"

instance FromSQL RawJSONB where
  type PQBase RawJSONB = PGbytea
  fromSQL = fmap RawJSONB . fromSQL

instance ToSQL RawJSONB where
  type PQDest RawJSONB = PGbytea
  toSQL = toSQL . unRawJSONB

-- | Encode a value with its 'ToJSON' instance.
--
-- /Note:/ The server normalizes a @jsonb@ value on input, e.g. it reorders the
-- keys of an object and drops insignificant whitespace. The result of
-- 'encodeRawJSONB' therefore differs in general from the text that the server
-- returns for the same value.
--
-- @since 1.15.0.0
encodeRawJSONB :: ToJSON a => a -> RawJSONB
encodeRawJSONB = RawJSONB . BSL.toStrict . encode

-- | Decode a value with its 'FromJSON' instance.
--
-- @since 1.15.0.0
decodeRawJSONB :: FromJSON a => RawJSONB -> Maybe a
decodeRawJSONB = decodeStrict' . unRawJSONB

-- | Decode a value with its 'FromJSON' instance and report the reason for a
-- failure.
--
-- @since 1.15.0.0
eitherDecodeRawJSONB :: FromJSON a => RawJSONB -> Either String a
eitherDecodeRawJSONB = eitherDecodeStrict' . unRawJSONB

----------------------------------------

aesonFromSQL :: forall a. (FromJSON a, Typeable a) => Maybe PGbytea -> IO a
aesonFromSQL mbase = do
  v <- fromSQL mbase
  case eitherDecodeStrict' v of
    Right a -> pure a
    Left err -> hpqTypesError $ "aesonFromSQL (" ++ show (typeRep $ Proxy @a) ++ "): " ++ err

aesonToSQL :: ToJSON a => a -> ParamAllocator -> (Ptr PGbytea -> IO r) -> IO r
aesonToSQL = toSQL . BSL.toStrict . encode