packages feed

grapesy-1.2.0: src/Network/GRPC/Util/Exception/FormatCtx.hs

{-# LANGUAGE CPP #-}

-- | Custom overrides for exception rendering
module Network.GRPC.Util.Exception.FormatCtx (
    FormatCtx -- opaque
    -- * Construction
  , emptyFormatCtx
  , insertFormatCtx
    -- * Use
  , formatException
#if MIN_VERSION_base(4,20,0)
  , formatExceptionAnnotation
#endif
  ) where

import Control.Exception
import Data.Typeable
import Type.Reflection qualified as Reflection

#if MIN_VERSION_base(4,20,0)
import Control.Exception.Annotation
#endif

import Network.GRPC.Util.Exception.Doc

{-------------------------------------------------------------------------------
  Definition

  TODO: It would be nicer to use dependent-map for 'FormatCtx' but it doesn't
  support GHC-9.14.
-------------------------------------------------------------------------------}

-- | Custom renderers
--
-- This can be used to dynamically dispatch rendering for exception or
-- exception annotations to user-specified renderers, overriding any instances
-- that may (or may not) be in scope.
newtype FormatCtx = FormatCtx [Renderer]

data Renderer where
    Renderer :: Reflection.TypeRep e -> (FormatCtx -> e -> Doc) -> Renderer

{-------------------------------------------------------------------------------
  Construction
-------------------------------------------------------------------------------}

emptyFormatCtx :: FormatCtx
emptyFormatCtx = FormatCtx []

insertFormatCtx :: Typeable e => (FormatCtx -> e -> Doc) -> FormatCtx -> FormatCtx
insertFormatCtx f (FormatCtx xs) = FormatCtx (Renderer Reflection.typeRep f : xs)

{-------------------------------------------------------------------------------
  Use
-------------------------------------------------------------------------------}

formatException :: Exception e => FormatCtx -> e -> Doc
formatException ctx e = case lookupFormatCtx ty ctx of
    Just f  -> f ctx e
    Nothing -> withHeader (show ty) $ fromLines $ displayException e
  where
    ty = Reflection.typeOf e

#if MIN_VERSION_base(4,20,0)
formatExceptionAnnotation :: ExceptionAnnotation ann => FormatCtx -> ann -> Doc
formatExceptionAnnotation ctx ann = case lookupFormatCtx ty ctx of
    Just f  -> f ctx ann
    Nothing -> withHeader (show ty) $ fromLines $ displayExceptionAnnotation ann
  where
    ty = Reflection.typeOf ann
#endif

{-------------------------------------------------------------------------------
  Internal auxiliary
-------------------------------------------------------------------------------}

lookupFormatCtx :: Reflection.TypeRep e -> FormatCtx -> Maybe (FormatCtx -> e -> Doc)
lookupFormatCtx ty (FormatCtx xs) = go xs where
    go [] = Nothing
    go (Renderer ty' f : xs') = case Reflection.eqTypeRep ty ty' of
        Just HRefl -> Just f
        Nothing    -> go xs'