packages feed

croaring-ffi-1.0: src/Data/Bitmap/Roaring/FFI/NullHandle.hs

{-# LANGUAGE NoFieldSelectors #-}

-- | The exception the @with@ helpers throw when their constructor returns a NULL handle.
module Data.Bitmap.Roaring.FFI.NullHandle
  ( NullHandle (..)
  , bracket
  ) where

import Control.Exception (Exception (..), throwIO)
import Control.Exception qualified as Exception
import Data.Coerce (Coercible, coerce)
import Foreign.Ptr (Ptr, nullPtr)

{- | A @with@ helper's constructor returned NULL.

The helper cannot tell why.
Depending on the constructor, NULL means failed allocation, rejected arguments or malformed bytes.
-}
newtype NullHandle = NullHandle
  { function :: String
  -- ^ The helper that received the NULL handle, such as @Data.Bitmap.Roaring.FFI.Bitmap.with@.
  }
  deriving (Eq, Show)

instance Exception NullHandle where
  displayException (NullHandle function) = function <> ": the constructor returned NULL"

{- | Runs the action on the constructed handle and releases the handle when the action ends.
If the constructor returns NULL, throws t'NullHandle' with the given name instead of running the action.
-}
bracket :: forall handle a. (Coercible handle (Ptr handle)) => String -> (handle -> IO ()) -> IO handle -> (handle -> IO a) -> IO a
bracket function release construct = Exception.bracket (construct >>= checked) release
  where
    checked handle
      | coerce handle == (nullPtr :: Ptr handle) = throwIO $ NullHandle function
      | otherwise = pure handle
{-# INLINE bracket #-}