bugsnag-1.2.0.1: src/Network/Bugsnag/Exception/Parse.hs
-- |
--
-- Parse error messages for @'HasCallStack'@ information.
module Network.Bugsnag.Exception.Parse
( MessageWithStackFrames (..)
, parseErrorCall
, parseExceptionWithContext
, parseStringException
) where
import Prelude
import qualified Control.Exception as Exception (ErrorCall, SomeException)
import Control.Monad (void)
import Data.Bifunctor (first)
import Data.Bugsnag
import Data.Text (Text, pack)
import qualified Network.Bugsnag.Exception.Context as Exception
import Text.Parsec
import Text.Parsec.String
data MessageWithStackFrames = MessageWithStackFrames
{ mwsfMessage :: Text
, mwsfStackFrames :: [StackFrame]
}
-- | Parse an @'ErrorCall'@ for @'HasCallStack'@ information
parseErrorCall :: Exception.ErrorCall -> Either String MessageWithStackFrames
parseErrorCall = parse' errorCallParser . show
parseExceptionWithContext
:: Exception.ExceptionWithContext Exception.SomeException
-> Either String MessageWithStackFrames
parseExceptionWithContext =
parse' backtraceParser . Exception.displayExceptionWithContext
-- | Parse a @'StringException'@ for @'HasCallStack'@ information
--
-- We accept this as @'SomeException'@ so that this library doesn't depend on
-- any one concrete library that has @'throwString'@ (there are two right now,
-- sigh.)
parseStringException
:: Exception.SomeException -> Either String MessageWithStackFrames
parseStringException = parse' stringExceptionParser . show
-- brittany-disable-next-binding
errorCallParser :: Parser MessageWithStackFrames
errorCallParser =
MessageWithStackFrames
<$> messageParser
<*> manyTill hasCallStackStackFrameParser eof
where
messageParser :: Parser Text
messageParser = do
msg <- pack <$> manyTill anyChar eol
msg <$ (string "CallStack (from HasCallStack):" *> eol)
backtraceParser :: Parser MessageWithStackFrames
backtraceParser =
MessageWithStackFrames
<$> messageParser
<*> manyTill hasCallStackStackFrameParser eof
where
messageParser :: Parser Text
messageParser = do
msg <- pack <$> manyTill anyChar eol
msg <$ (string "HasCallStack backtrace:" *> eol)
hasCallStackStackFrameParser :: Parser StackFrame
hasCallStackStackFrameParser = do
func <- stackFrameFunctionTill $ string ", called at "
(path, ln, cl) <- stackFrameLocationTill $ eol <|> eof
pure
defaultStackFrame
{ stackFrame_file = pack path
, stackFrame_lineNumber = ln
, stackFrame_columnNumber = Just cl
, stackFrame_method = func
, stackFrame_inProject = Just True
, stackFrame_code = Nothing
}
-- brittany-disable-next-binding
stringExceptionParser :: Parser MessageWithStackFrames
stringExceptionParser =
MessageWithStackFrames
<$> messageParser
<*> manyTill stackFrameParser eof
where
messageParser :: Parser Text
messageParser = do
manyTill anyChar (try $ string "throwString called with:") *> eol *> eol
pack <$> manyTill anyChar (try $ eol *> string "Called from:" *> eol)
stackFrameParser :: Parser StackFrame
stackFrameParser = do
func <- stackFrameFunctionTill $ string " ("
(path, ln, cl) <- stackFrameLocationTill $ char ')' *> eol <|> eof
pure
defaultStackFrame
{ stackFrame_file = pack path
, stackFrame_lineNumber = ln
, stackFrame_columnNumber = Just cl
, stackFrame_method = func
, stackFrame_inProject = Just True
, stackFrame_code = Nothing
}
stackFrameFunctionTill :: Parser a -> Parser Text
stackFrameFunctionTill p = spaces *> (pack <$> manyTill anyChar p)
stackFrameLocationTill :: Parser a -> Parser (FilePath, Int, Int)
stackFrameLocationTill p = do
result <-
(,,)
<$> manyTill anyChar (char ':')
<*> (read <$> manyTill digit (char ':'))
<*> (read <$> manyTill digit (char ' '))
-- Ignore the "in package:module" part. TODO: we could use this to set
-- bsfInProject if we had some more knowledge about project packages.
void $ string "in "
void $ manyTill anyChar $ char ':'
void $ manyTill anyChar p
pure result
parse'
:: Parser MessageWithStackFrames
-> String
-> Either String MessageWithStackFrames
parse' p = first show . parse (p <* eof) "<error>"
eol :: Parser ()
eol = void endOfLine