throwable-exceptions 0.1.0.2 → 0.1.0.3
raw patch · 4 files changed
+126/−16 lines, 4 files
Files
- README.md +11/−0
- src/Control/Exception/Throwable.hs +58/−8
- test/Control/Exception/ThrowableTest.hs +54/−5
- throwable-exceptions.cabal +3/−3
README.md view
@@ -1,9 +1,16 @@ # :diamonds: throwable-exceptions :diamonds:+[](https://travis-ci.org/aiya000/hs-throwable-exceptions) [](https://hackage.haskell.org/package/throwable-exceptions) throwable-exceptions gives the exception's value constructors for your haskell project :dog: +## Document is available in here++- [throwable-exceptions - Hackage](https://hackage.haskell.org/package/throwable-exceptions)+- [the simulation of the use case](https://github.com/aiya000/hs-throwable-exceptions/blob/master/test/Control/Exception/ThrowableTest.hs)++ # :muscle: Why we should use this ? :muscle: The situation that we want to throw the specific exception is happened frequently, but many general exceptions are not given by base.@@ -35,3 +42,7 @@ But you have not to define a specific exception yourself if you use this :muscle:+++# :+1:+PR is welcome !
src/Control/Exception/Throwable.hs view
@@ -1,21 +1,71 @@ module Control.Exception.Throwable ( IOException' (..)+ , ioException' , IllegalArgumentException (..)+ , illegalArgumentException+ , GeneralException (..)+ , generalException+ , IndexOutOfBoundsException (..)+ , indexOutOfBoundsException ) where import Control.Exception.Safe (Exception)+import Data.Typeable (Typeable) -- | Simular to @Control.Exception.IOException@-data IOException' = IOException' { ioExceptionCause :: String }- | FileNotFoundException { fileNotFoundCause :: String }- deriving (Read, Show)+data IOException' a = IOException' { ioExceptionCause :: String, ioExceptionClue :: a }+ | FileNotFoundException { fileNotFoundCause :: String, fileNotFoundClue :: a } -instance Exception IOException'+instance Show a => Show (IOException' a) where+ show (IOException' cause _) = "IOException': " ++ show cause+ show (FileNotFoundException cause _) = "FileNotFoundException: " ++ show cause +instance (Typeable a, Show a) => Exception (IOException' a) --- Like java.lang.IllegalArgumentException-data IllegalArgumentException = IllegalArgumentException { illegalArgumentCause :: String }- deriving (Read, Show)+-- | A constructor for IOException' but doesn't take the clue+ioException' :: String -> IOException' ()+ioException' = flip IOException' () -instance Exception IllegalArgumentException++-- | Like java.lang.IllegalArgumentException+data IllegalArgumentException a = IllegalArgumentException { illegalArgumentCause :: String, illegalArgumentClue :: a }++instance Show a => Show (IllegalArgumentException a) where+ show (IllegalArgumentException cause _) = "IllegalArgumentException: " ++ show cause++instance (Typeable a, Show a) => Exception (IllegalArgumentException a)++-- | A constructor for IllegalArgumentException but doesn't take the clue+illegalArgumentException :: String -> IllegalArgumentException ()+illegalArgumentException = flip IllegalArgumentException ()+++-- | Like java.lang.IndexOutOfBoundsException+data IndexOutOfBoundsException a = IndexOutOfBoundsException { indexOutOfBoundsCause :: String, indexOutOfBoundsArgumentClue :: a }++instance Show a => Show (IndexOutOfBoundsException a) where+ show (IndexOutOfBoundsException cause _) = "IndexOutOfBoundsException: " ++ show cause++instance (Typeable a, Show a) => Exception (IndexOutOfBoundsException a)++-- | A constructor for IndexOutOfBoundsException but doesn't take the clue+indexOutOfBoundsException :: String -> IndexOutOfBoundsException ()+indexOutOfBoundsException = flip IndexOutOfBoundsException ()+++-- | An other of these exceptions+data GeneralException a = GeneralException+ { exceptionName :: String -- ^ This should be named by 'PascalCase' for show, for example: "MyOperation", "Database"+ , exceptionCause :: String -- ^ The message for the cause of the exception+ , exceptionClue :: a -- ^ The clue of the exception. This can be anything of Show instance+ }++instance Show a => Show (GeneralException a) where+ show (GeneralException name cause _) = name ++ "Exception: " ++ cause++instance (Typeable a, Show a) => Exception (GeneralException a)++-- | A constructor for GeneralException but doesn't take the clue+generalException :: String -> String -> GeneralException ()+generalException name cause = GeneralException name cause ()
test/Control/Exception/ThrowableTest.hs view
@@ -1,11 +1,60 @@ module Control.Exception.ThrowableTest where -import Control.Exception.Safe (MonadThrow, throwM)-import Control.Monad.Trans.Either (runEitherT)+import Control.Exception.Safe (Exception)+import Control.Exception.Safe (MonadThrow, throwM, fromException, SomeException)+import Data.Either (isLeft) import Test.Tasty (TestTree)-import Test.Tasty.HUnit (testCase, assertFailure, (@?=), Assertion)+import Test.Tasty.HUnit (testCase, Assertion, assertBool, (@?=)) import qualified Control.Exception.Throwable as ET -test_ioexception_prim :: [TestTree]-test_ioexception_prim = []+-- The message is never used in tasty-hunit+assertBool' :: Bool -> Assertion+assertBool' = assertBool "" ++canBeShown :: Exception e => e -> Assertion+canBeShown = assertBool' . not . null . show++canBeThrown :: Exception e => e -> Assertion+canBeThrown e = assertBool' . isLeft $ throwM e+++test_io_exception_prim :: [TestTree]+test_io_exception_prim =+ [ testCase "can be shown in most cases" $+ canBeShown $ ET.ioException' "honoka-chan!" -- IOException' can be shown, because IOException' is an Exception instance+ , testCase "can be thrown in all case" $+ canBeThrown $ ET.ioException' "kotori-chan ga!" -- IOException' can be thrown by throwM, because IOException' is an Exception instance !+ ]+++test_index_out_of_bounds_exception :: [TestTree]+test_index_out_of_bounds_exception =+ [ testCase "finds the perpetrator with the clue" $+ program -- You can find the perpetrator of some bug if you see the message ('.. looks like bad')+ ]+ where+ program :: IO ()+ program = do+ case someCalculation of + Right _ -> putStrLn "the calculation is succeed"+ Left e -> case fromException' e of+ Nothing -> putStrLn "!? I don't know !"+ Just (ET.IndexOutOfBoundsException _ clue) -> putStrLn $ show clue ++ " looks like bad"++ -- monomorphic function+ fromException' :: SomeException -> Maybe (ET.IndexOutOfBoundsException ([Int], Int))+ fromException' = fromException++ at :: MonadThrow m => [Int] -> Int -> m Int+ xs `at` i | length xs <= i = throwM $ ET.IndexOutOfBoundsException "mmm..?" (xs, i)+ | otherwise = return $ xs !! i++ someCalculation = [10..13] `at` 4+++test_general_exception :: [TestTree]+test_general_exception =+ [ testCase "looks like other exception when GeneralException is executed `show`" $+ show (ET.generalException "MyTest" "nico-chan") @?= "MyTestException: nico-chan"+ ]
throwable-exceptions.cabal view
@@ -1,5 +1,5 @@ name: throwable-exceptions-version: 0.1.0.2+version: 0.1.0.3 synopsis: throwable-exceptions gives the exception's value constructors description: throwable-exceptions gives the exception's value constructors homepage: https://github.com/aiya000/hs-throwable-exceptions#README.md@@ -16,7 +16,7 @@ library hs-source-dirs: src default-language: Haskell2010- ghc-options: -Wall -Wno-name-shadowing -Wno-unused-do-bind -Wunused-top-binds+ ghc-options: -Wall -Wno-name-shadowing -Wno-unused-do-bind -Wno-unused-top-binds exposed-modules: Control.Exception.Throwable build-depends: base >= 4.7 && < 5 , safe-exceptions@@ -27,7 +27,7 @@ hs-source-dirs: test main-is: Tasty.hs default-language: Haskell2010- ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wunused-top-binds+ ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall -Wno-unused-top-binds other-modules: Control.Exception.ThrowableTest build-depends: base , either