{-# LANGUAGE TemplateHaskell #-}

-- | Postgres SQL Error
--
-- Functions and datatypes for interpreting Postgres errors.
module Hasura.Backends.Postgres.SQL.Error
  ( PGErrorType (..),
    _PGDataException,
    _PGIntegrityConstraintViolation,
    _PGSyntaxErrorOrAccessRuleViolation,
    _PGTransactionRollback,
    pgErrorType,
    PGErrorCode (..),
    _PGErrorGeneric,
    _PGErrorSpecific,
    PGDataException (..),
    PGIntegrityConstraintViolation (..),
    PGSyntaxErrorOrAccessRuleViolation (..),
    PGTransactionRollback (..),
  )
where

import Control.Lens.TH (makePrisms)
import Data.Text qualified as T
import Database.PG.Query.Connection qualified as Q
import Hasura.Prelude

-- | The top-level error code type. Errors in Postgres are divided into different /classes/, which
-- are further subdivided into individual error codes. Even if a particular status code is not known
-- to the application, it’s possible to determine its class and handle it appropriately.
data PGErrorType
  = PGDataException (Maybe (PGErrorCode PGDataException))
  | PGIntegrityConstraintViolation (Maybe (PGErrorCode PGIntegrityConstraintViolation))
  | PGSyntaxErrorOrAccessRuleViolation (Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation))
  | PGTransactionRollback (Maybe (PGErrorCode PGTransactionRollback))
  deriving (Int -> PGErrorType -> ShowS
[PGErrorType] -> ShowS
PGErrorType -> String
(Int -> PGErrorType -> ShowS)
-> (PGErrorType -> String)
-> ([PGErrorType] -> ShowS)
-> Show PGErrorType
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGErrorType] -> ShowS
$cshowList :: [PGErrorType] -> ShowS
show :: PGErrorType -> String
$cshow :: PGErrorType -> String
showsPrec :: Int -> PGErrorType -> ShowS
$cshowsPrec :: Int -> PGErrorType -> ShowS
Show, PGErrorType -> PGErrorType -> Bool
(PGErrorType -> PGErrorType -> Bool)
-> (PGErrorType -> PGErrorType -> Bool) -> Eq PGErrorType
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGErrorType -> PGErrorType -> Bool
$c/= :: PGErrorType -> PGErrorType -> Bool
== :: PGErrorType -> PGErrorType -> Bool
$c== :: PGErrorType -> PGErrorType -> Bool
Eq)

data PGErrorCode a
  = -- | represents errors that have the non-specific @000@ status code
    PGErrorGeneric
  | -- | represents errors with a known, more specific status code
    PGErrorSpecific a
  deriving (Int -> PGErrorCode a -> ShowS
[PGErrorCode a] -> ShowS
PGErrorCode a -> String
(Int -> PGErrorCode a -> ShowS)
-> (PGErrorCode a -> String)
-> ([PGErrorCode a] -> ShowS)
-> Show (PGErrorCode a)
forall a. Show a => Int -> PGErrorCode a -> ShowS
forall a. Show a => [PGErrorCode a] -> ShowS
forall a. Show a => PGErrorCode a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGErrorCode a] -> ShowS
$cshowList :: forall a. Show a => [PGErrorCode a] -> ShowS
show :: PGErrorCode a -> String
$cshow :: forall a. Show a => PGErrorCode a -> String
showsPrec :: Int -> PGErrorCode a -> ShowS
$cshowsPrec :: forall a. Show a => Int -> PGErrorCode a -> ShowS
Show, PGErrorCode a -> PGErrorCode a -> Bool
(PGErrorCode a -> PGErrorCode a -> Bool)
-> (PGErrorCode a -> PGErrorCode a -> Bool) -> Eq (PGErrorCode a)
forall a. Eq a => PGErrorCode a -> PGErrorCode a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGErrorCode a -> PGErrorCode a -> Bool
$c/= :: forall a. Eq a => PGErrorCode a -> PGErrorCode a -> Bool
== :: PGErrorCode a -> PGErrorCode a -> Bool
$c== :: forall a. Eq a => PGErrorCode a -> PGErrorCode a -> Bool
Eq, a -> PGErrorCode b -> PGErrorCode a
(a -> b) -> PGErrorCode a -> PGErrorCode b
(forall a b. (a -> b) -> PGErrorCode a -> PGErrorCode b)
-> (forall a b. a -> PGErrorCode b -> PGErrorCode a)
-> Functor PGErrorCode
forall a b. a -> PGErrorCode b -> PGErrorCode a
forall a b. (a -> b) -> PGErrorCode a -> PGErrorCode b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> PGErrorCode b -> PGErrorCode a
$c<$ :: forall a b. a -> PGErrorCode b -> PGErrorCode a
fmap :: (a -> b) -> PGErrorCode a -> PGErrorCode b
$cfmap :: forall a b. (a -> b) -> PGErrorCode a -> PGErrorCode b
Functor)

data PGDataException
  = PGInvalidDatetimeFormat
  | PGInvalidParameterValue
  | PGInvalidEscapeSequence
  | PGInvalidTextRepresentation
  deriving (Int -> PGDataException -> ShowS
[PGDataException] -> ShowS
PGDataException -> String
(Int -> PGDataException -> ShowS)
-> (PGDataException -> String)
-> ([PGDataException] -> ShowS)
-> Show PGDataException
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGDataException] -> ShowS
$cshowList :: [PGDataException] -> ShowS
show :: PGDataException -> String
$cshow :: PGDataException -> String
showsPrec :: Int -> PGDataException -> ShowS
$cshowsPrec :: Int -> PGDataException -> ShowS
Show, PGDataException -> PGDataException -> Bool
(PGDataException -> PGDataException -> Bool)
-> (PGDataException -> PGDataException -> Bool)
-> Eq PGDataException
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGDataException -> PGDataException -> Bool
$c/= :: PGDataException -> PGDataException -> Bool
== :: PGDataException -> PGDataException -> Bool
$c== :: PGDataException -> PGDataException -> Bool
Eq)

data PGIntegrityConstraintViolation
  = PGRestrictViolation
  | PGNotNullViolation
  | PGForeignKeyViolation
  | PGUniqueViolation
  | PGCheckViolation
  | PGExclusionViolation
  deriving (Int -> PGIntegrityConstraintViolation -> ShowS
[PGIntegrityConstraintViolation] -> ShowS
PGIntegrityConstraintViolation -> String
(Int -> PGIntegrityConstraintViolation -> ShowS)
-> (PGIntegrityConstraintViolation -> String)
-> ([PGIntegrityConstraintViolation] -> ShowS)
-> Show PGIntegrityConstraintViolation
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGIntegrityConstraintViolation] -> ShowS
$cshowList :: [PGIntegrityConstraintViolation] -> ShowS
show :: PGIntegrityConstraintViolation -> String
$cshow :: PGIntegrityConstraintViolation -> String
showsPrec :: Int -> PGIntegrityConstraintViolation -> ShowS
$cshowsPrec :: Int -> PGIntegrityConstraintViolation -> ShowS
Show, PGIntegrityConstraintViolation
-> PGIntegrityConstraintViolation -> Bool
(PGIntegrityConstraintViolation
 -> PGIntegrityConstraintViolation -> Bool)
-> (PGIntegrityConstraintViolation
    -> PGIntegrityConstraintViolation -> Bool)
-> Eq PGIntegrityConstraintViolation
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGIntegrityConstraintViolation
-> PGIntegrityConstraintViolation -> Bool
$c/= :: PGIntegrityConstraintViolation
-> PGIntegrityConstraintViolation -> Bool
== :: PGIntegrityConstraintViolation
-> PGIntegrityConstraintViolation -> Bool
$c== :: PGIntegrityConstraintViolation
-> PGIntegrityConstraintViolation -> Bool
Eq)

data PGSyntaxErrorOrAccessRuleViolation
  = PGUndefinedObject
  | PGInvalidColumnReference
  deriving (Int -> PGSyntaxErrorOrAccessRuleViolation -> ShowS
[PGSyntaxErrorOrAccessRuleViolation] -> ShowS
PGSyntaxErrorOrAccessRuleViolation -> String
(Int -> PGSyntaxErrorOrAccessRuleViolation -> ShowS)
-> (PGSyntaxErrorOrAccessRuleViolation -> String)
-> ([PGSyntaxErrorOrAccessRuleViolation] -> ShowS)
-> Show PGSyntaxErrorOrAccessRuleViolation
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGSyntaxErrorOrAccessRuleViolation] -> ShowS
$cshowList :: [PGSyntaxErrorOrAccessRuleViolation] -> ShowS
show :: PGSyntaxErrorOrAccessRuleViolation -> String
$cshow :: PGSyntaxErrorOrAccessRuleViolation -> String
showsPrec :: Int -> PGSyntaxErrorOrAccessRuleViolation -> ShowS
$cshowsPrec :: Int -> PGSyntaxErrorOrAccessRuleViolation -> ShowS
Show, PGSyntaxErrorOrAccessRuleViolation
-> PGSyntaxErrorOrAccessRuleViolation -> Bool
(PGSyntaxErrorOrAccessRuleViolation
 -> PGSyntaxErrorOrAccessRuleViolation -> Bool)
-> (PGSyntaxErrorOrAccessRuleViolation
    -> PGSyntaxErrorOrAccessRuleViolation -> Bool)
-> Eq PGSyntaxErrorOrAccessRuleViolation
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGSyntaxErrorOrAccessRuleViolation
-> PGSyntaxErrorOrAccessRuleViolation -> Bool
$c/= :: PGSyntaxErrorOrAccessRuleViolation
-> PGSyntaxErrorOrAccessRuleViolation -> Bool
== :: PGSyntaxErrorOrAccessRuleViolation
-> PGSyntaxErrorOrAccessRuleViolation -> Bool
$c== :: PGSyntaxErrorOrAccessRuleViolation
-> PGSyntaxErrorOrAccessRuleViolation -> Bool
Eq)

data PGTransactionRollback
  = PGSerializationFailure
  deriving (Int -> PGTransactionRollback -> ShowS
[PGTransactionRollback] -> ShowS
PGTransactionRollback -> String
(Int -> PGTransactionRollback -> ShowS)
-> (PGTransactionRollback -> String)
-> ([PGTransactionRollback] -> ShowS)
-> Show PGTransactionRollback
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [PGTransactionRollback] -> ShowS
$cshowList :: [PGTransactionRollback] -> ShowS
show :: PGTransactionRollback -> String
$cshow :: PGTransactionRollback -> String
showsPrec :: Int -> PGTransactionRollback -> ShowS
$cshowsPrec :: Int -> PGTransactionRollback -> ShowS
Show, PGTransactionRollback -> PGTransactionRollback -> Bool
(PGTransactionRollback -> PGTransactionRollback -> Bool)
-> (PGTransactionRollback -> PGTransactionRollback -> Bool)
-> Eq PGTransactionRollback
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: PGTransactionRollback -> PGTransactionRollback -> Bool
$c/= :: PGTransactionRollback -> PGTransactionRollback -> Bool
== :: PGTransactionRollback -> PGTransactionRollback -> Bool
$c== :: PGTransactionRollback -> PGTransactionRollback -> Bool
Eq)

$(makePrisms ''PGErrorType)
$(makePrisms ''PGErrorCode)

pgErrorType :: Q.PGStmtErrDetail -> Maybe PGErrorType
pgErrorType :: PGStmtErrDetail -> Maybe PGErrorType
pgErrorType PGStmtErrDetail
errorDetails = do
  Text -> Maybe PGErrorType
parseTypes (Text -> Maybe PGErrorType) -> Maybe Text -> Maybe PGErrorType
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< PGStmtErrDetail -> Maybe Text
Q.edStatusCode PGStmtErrDetail
errorDetails
  where
    parseTypes :: Text -> Maybe PGErrorType
parseTypes Text
fullCodeText =
      [Maybe PGErrorType] -> Maybe PGErrorType
forall (f :: * -> *) a. Alternative f => [f a] -> f a
choice
        [ Text
-> (Maybe (PGErrorCode PGDataException) -> PGErrorType)
-> [Maybe (PGErrorCode PGDataException)]
-> Maybe PGErrorType
forall a b. Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
withClass
            Text
"22"
            Maybe (PGErrorCode PGDataException) -> PGErrorType
PGDataException
            [ Text -> PGDataException -> Maybe (PGErrorCode PGDataException)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"007" PGDataException
PGInvalidDatetimeFormat,
              Text -> PGDataException -> Maybe (PGErrorCode PGDataException)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"023" PGDataException
PGInvalidParameterValue,
              Text -> PGDataException -> Maybe (PGErrorCode PGDataException)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"025" PGDataException
PGInvalidEscapeSequence,
              Text -> PGDataException -> Maybe (PGErrorCode PGDataException)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"P02" PGDataException
PGInvalidTextRepresentation
            ],
          Text
-> (Maybe (PGErrorCode PGIntegrityConstraintViolation)
    -> PGErrorType)
-> [Maybe (PGErrorCode PGIntegrityConstraintViolation)]
-> Maybe PGErrorType
forall a b. Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
withClass
            Text
"23"
            Maybe (PGErrorCode PGIntegrityConstraintViolation) -> PGErrorType
PGIntegrityConstraintViolation
            [ Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"001" PGIntegrityConstraintViolation
PGRestrictViolation,
              Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"502" PGIntegrityConstraintViolation
PGNotNullViolation,
              Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"503" PGIntegrityConstraintViolation
PGForeignKeyViolation,
              Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"505" PGIntegrityConstraintViolation
PGUniqueViolation,
              Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"514" PGIntegrityConstraintViolation
PGCheckViolation,
              Text
-> PGIntegrityConstraintViolation
-> Maybe (PGErrorCode PGIntegrityConstraintViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"P01" PGIntegrityConstraintViolation
PGExclusionViolation
            ],
          Text
-> (Maybe (PGErrorCode PGTransactionRollback) -> PGErrorType)
-> [Maybe (PGErrorCode PGTransactionRollback)]
-> Maybe PGErrorType
forall a b. Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
withClass
            Text
"40"
            Maybe (PGErrorCode PGTransactionRollback) -> PGErrorType
PGTransactionRollback
            [ Text
-> PGTransactionRollback
-> Maybe (PGErrorCode PGTransactionRollback)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"001" PGTransactionRollback
PGSerializationFailure
            ],
          Text
-> (Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation)
    -> PGErrorType)
-> [Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation)]
-> Maybe PGErrorType
forall a b. Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
withClass
            Text
"42"
            Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation)
-> PGErrorType
PGSyntaxErrorOrAccessRuleViolation
            [ Text
-> PGSyntaxErrorOrAccessRuleViolation
-> Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"704" PGSyntaxErrorOrAccessRuleViolation
PGUndefinedObject,
              Text
-> PGSyntaxErrorOrAccessRuleViolation
-> Maybe (PGErrorCode PGSyntaxErrorOrAccessRuleViolation)
forall a. Text -> a -> Maybe (PGErrorCode a)
code Text
"P10" PGSyntaxErrorOrAccessRuleViolation
PGInvalidColumnReference
            ]
        ]
      where
        (Text
classText, Text
codeText) = Int -> Text -> (Text, Text)
T.splitAt Int
2 Text
fullCodeText

        withClass :: Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
        withClass :: Text -> (Maybe a -> b) -> [Maybe a] -> Maybe b
withClass Text
expectedClassText Maybe a -> b
mkClass [Maybe a]
codes =
          Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Text
classText Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
expectedClassText) Maybe () -> b -> Maybe b
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> Maybe a -> b
mkClass ([Maybe a] -> Maybe a
forall (f :: * -> *) a. Alternative f => [f a] -> f a
choice [Maybe a]
codes)

        code :: Text -> a -> Maybe (PGErrorCode a)
        code :: Text -> a -> Maybe (PGErrorCode a)
code Text
expectedCodeText a
codeValue =
          Bool -> Maybe ()
forall (f :: * -> *). Alternative f => Bool -> f ()
guard (Text
codeText Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
== Text
expectedCodeText) Maybe () -> PGErrorCode a -> Maybe (PGErrorCode a)
forall (f :: * -> *) a b. Functor f => f a -> b -> f b
$> a -> PGErrorCode a
forall a. a -> PGErrorCode a
PGErrorSpecific a
codeValue