{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TypeApplications #-} -- | An 'Annotation' is attached to an 'Control.Exception.Annotated.AnnotatedException'. They're -- essentially a dynamically typed value with a convenient 'IsString' -- instance. -- -- When integrating into your own application, you will likely want to do more -- than just have the 'String' you get from 'show'ing the 'Annotation'. You can -- do this by creating a special wrapper type that carries a class constraint. -- This allows you to also pluck out the 'Annotation's from your library or -- executable independently and treat them differently from unknonwn -- annotations. -- -- As an example, here's one that requires a 'Data.Aeson.ToJSON' constraint on the -- underlying value. This means that you can convert any annotated value to -- JSON, and then use that JSON in bug reports or logging. -- -- @ -- data JsonAnnotation where -- JsonAnnotation :: (ToJSON a, Typeable a) => a -> JsonAnnotation -- -- instance Show JsonANnotation where -- show (JsonAnnotation a) = concat -- [ "(JsonAnnotation (" -- , show (toJSON a) -- , "))" -- ] -- -- jsonCheckpoint :: (Typeable a, ToJSON a, 'HasCallStack', MonadCatch m) => a -> m a -> m a -- jsonCheckpoint val = 'withFrozenCallStack' checkpoint (JsonAnnotation val) -- @ -- -- When handling the @['Annotation']@ carried on the -- 'Control.Exception.Annotated.AnnotatedException', you can use -- 'tryAnnotations' to pick out the JSON annotations. -- -- @ -- jsonAnnotations :: [Annotation] -> ([JsonAnnotation], [Annotation]) -- jsonAnnotations = tryAnnotations -- @ -- -- and handle them however you please. -- -- @since 0.1.0.0 module Data.Annotation ( module Data.Annotation , module Data.Proxy ) where import Data.Either import Data.Maybe import Data.Proxy import Data.Set (Set) import qualified Data.Set as Set import Data.String import qualified Data.Text as Text import Data.Typeable import GHC.Stack -- | The constraints that the value inside an 'Annotation' must have. -- -- We want 'Typeable' so we can do 'cast' and potentially get more useful -- information out of it. -- -- @since 0.1.0.0 type AnnC a = (Typeable a, Show a) -- | An 'Annotation' is a wrapper around a value that includes a 'Typeable' -- constraint so we can later unpack it. It is essentially a 'Data.Dynamic.Dynamic', but -- we also include 'Show' so that you can always fall back to simply 'show'ing -- the 'Annotation' if it is otherwise unrecognized. -- -- @since 0.1.0.0 data Annotation where Annotation :: AnnC a => a -> Annotation -- | -- -- @since 0.1.0.0 instance Show Annotation where showsPrec p (Annotation a) = showParen (p > 10) $ showString "Annotation @" . showsPrec 11 (typeOf a) . showString " " . showsPrec 11 a -- | -- -- @since 0.1.0.0 instance IsString Annotation where fromString = Annotation . Text.pack -- | Wrap a value in an 'Annotation'. -- -- @since 0.1.0.0 toAnnotation :: (AnnC a) => a -> Annotation toAnnotation = Annotation -- | Attempt to 'cast' the underlying value out of an 'Annotation'. -- -- @since 0.1.0.0 castAnnotation :: forall a. (Typeable a) => Annotation -> Maybe a castAnnotation (Annotation ann) = cast ann -- | Attempt to 'cast' the underlying value out of an 'Annotation'. -- Returns the original 'Annotation' if the cast isn't right. -- -- @since 0.1.0.0 tryAnnotation :: forall a. (Typeable a) => Annotation -> Either a Annotation tryAnnotation a@(Annotation val) = case cast val of Just x -> Left x Nothing -> Right a -- | Attempt to 'cast' list of 'Annotation' into the given type. Any -- 'Annotation' that is not in that form is left untouched. -- -- @since 0.1.0.0 tryAnnotations :: forall a. (Typeable a) => [Annotation] -> ([a], [Annotation]) tryAnnotations = partitionEithers . map tryAnnotation -- | Returns the 'Set' of types that are in the given annotations. -- -- @since 0.1.0.0 annotationTypes :: [Annotation] -> Set TypeRep annotationTypes = Set.fromList . map (\(Annotation a) -> typeOf a) -- | Map a function over the given 'Annotation'. If the types don't match -- up, then the whole thing returns 'Nothing'. -- -- @since 0.1.0.0 mapAnnotation :: ((AnnC a, AnnC b)) => (a -> b) -> Annotation -> Maybe Annotation mapAnnotation f (Annotation ann) = Annotation . f <$> cast ann -- | Map a function over the 'Annotation', leaving it unchanged if the -- types don't match. -- -- @since 0.1.0.0 mapMaybeAnnotation :: (AnnC a, AnnC b) => (a -> b) -> Annotation -> Annotation mapMaybeAnnotation f ann = fromMaybe ann (mapAnnotation f ann) -- | A wrapper type for putting a 'CallStack' into an 'Annotation'. We need -- this because 'CallStack' does not have an 'Eq' instance. -- -- Deprecated in 0.2.0.0 since you can just put a 'CallStack' directly in an -- 'Annotation' now that we have no need for an 'Eq' constraint on it. -- -- @since 0.1.0.0 newtype CallStackAnnotation = CallStackAnnotation { unCallStackAnnotation :: [(String, SrcLoc)] } deriving (Eq, Show) {-# DEPRECATED CallStackAnnotation "You can just use `CallStack` directly now." #-} -- | Grab an 'Annotation' corresponding to the 'CallStack' that is -- currently in scope. -- -- @since 0.1.0.0 callStackAnnotation :: HasCallStack => Annotation callStackAnnotation = Annotation callStack -- | Stuff a 'CallStack' into an 'Annotation' via the 'CallStackAnnotation' -- newtype wrapper. -- -- @since 0.1.0.0 callStackToAnnotation :: CallStack -> Annotation callStackToAnnotation = Annotation -- | Convert the legacy 'CallStackAnnotation' into a 'CallStack'. -- -- Deprecated in 0.2.0.0 since you can use 'CallStack' directly. -- -- @since 0.1.0.0 callStackFromAnnotation :: CallStackAnnotation -> CallStack callStackFromAnnotation ann = fromCallSiteList $ unCallStackAnnotation ann {-# DEPRECATED callStackFromAnnotation "You can use 'CallStack' directly in annotations as of 0.2.0.0." #-} -- | Extract the 'CallStack's from the @['Annotation']@. Any 'Annotation' -- not corresponding to a 'CallStack' will be in the second element of the -- tuple. -- -- @since 0.1.0.0 callStackInAnnotations :: [Annotation] -> ([CallStack], [Annotation]) callStackInAnnotations = tryAnnotations {-# DEPRECATED callStackInAnnotations "You can just use 'tryAnnotations' directly as of 0.2.0.0." #-}