dataframe-core-1.0.0.0: src/DataFrame/Errors.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}
module DataFrame.Errors where
import qualified Data.Map.Lazy as ML
import qualified Data.Text as T
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import Control.Exception
import qualified Data.List as L
import Data.Typeable (Typeable)
import DataFrame.Display.Terminal.Colours
import Type.Reflection (TypeRep)
data TypeErrorContext a b = MkTypeErrorContext
{ userType :: Either String (TypeRep a)
, expectedType :: Either String (TypeRep b)
, errorColumnName :: Maybe String
, callingFunctionName :: Maybe String
}
data DataFrameException where
TypeMismatchException ::
forall a b.
(Typeable a, Typeable b) =>
TypeErrorContext a b ->
DataFrameException
AggregatedAndNonAggregatedException :: T.Text -> T.Text -> DataFrameException
ColumnsNotFoundException :: [T.Text] -> T.Text -> [T.Text] -> DataFrameException
EmptyDataSetException :: T.Text -> DataFrameException
InternalException :: T.Text -> DataFrameException
NonColumnReferenceException :: T.Text -> DataFrameException
UnaggregatedException :: T.Text -> DataFrameException
WrongQuantileNumberException :: Int -> DataFrameException
WrongQuantileIndexException :: VU.Vector Int -> Int -> DataFrameException
deriving (Exception)
instance Show DataFrameException where
show :: DataFrameException -> String
show (TypeMismatchException context) =
let
errorString =
typeMismatchError
(either id show (userType context))
(either id show (expectedType context))
in
addCallPointInfo
(errorColumnName context)
(callingFunctionName context)
errorString
show (ColumnsNotFoundException columnNames callPoint availableColumns) = columnsNotFound columnNames callPoint availableColumns
show (EmptyDataSetException callPoint) = emptyDataSetError callPoint
show (WrongQuantileNumberException q) = wrongQuantileNumberError q
show (WrongQuantileIndexException qs q) = wrongQuantileIndexError qs q
show (InternalException msg) = "Internal error: " ++ T.unpack msg
show (NonColumnReferenceException msg) = "Expression must be a column reference in: " ++ T.unpack msg
show (UnaggregatedException expr) = "Expression is not fully aggregated: " ++ T.unpack expr
show (AggregatedAndNonAggregatedException expr1 expr2) =
"Cannot combine aggregated and non-aggregated expressions: \n"
++ T.unpack expr1
++ "\n"
++ T.unpack expr2
columnNotFound :: T.Text -> T.Text -> [T.Text] -> String
columnNotFound missingColumn = columnsNotFound [missingColumn]
columnsNotFound :: [T.Text] -> T.Text -> [T.Text] -> String
columnsNotFound missingColumns callPoint availableColumns =
red "\n\n[ERROR] "
++ missingColumnsLabel missingColumns
++ ": "
++ T.unpack (T.intercalate ", " missingColumns)
++ " for operation "
++ T.unpack callPoint
++ formatSuggestions missingColumns availableColumns
++ "\n\n"
where
missingColumnsLabel [_] = "Column not found"
missingColumnsLabel _ = "Columns not found"
formatSuggestions [missingColumn] columns =
case guessColumnName missingColumn columns of
"" -> ""
guessed ->
"\n\tDid you mean "
++ T.unpack guessed
++ "?"
formatSuggestions names columns =
case traverse (`suggestColumnName` columns) names of
Just guessedColumns
| not (null guessedColumns) ->
"\n\tDid you mean "
++ formatColumnSuggestions guessedColumns
++ "?"
_ -> ""
suggestColumnName missingColumn columns = case guessColumnName missingColumn columns of
"" -> Nothing
guessed -> Just guessed
formatColumnSuggestions guessedColumns =
"["
++ L.intercalate ", " (map (show . T.unpack) guessedColumns)
++ "]"
typeMismatchError :: String -> String -> String
typeMismatchError givenType expType =
red $
red "\n\n[Error]: Type Mismatch"
++ "\n\tWhile running your code I tried to "
++ "get a column of type: "
++ red (show givenType)
++ " but the column in the dataframe was actually of type: "
++ green (show expType)
emptyDataSetError :: T.Text -> String
emptyDataSetError callPoint =
red "\n\n[ERROR] "
++ T.unpack callPoint
++ " cannot be called on empty data sets"
wrongQuantileNumberError :: Int -> String
wrongQuantileNumberError q =
red "\n\n[ERROR] "
++ "Quantile number q should satisfy "
++ "q >= 2, but here q is "
++ show q
wrongQuantileIndexError :: VU.Vector Int -> Int -> String
wrongQuantileIndexError qs q =
red "\n\n[ERROR] "
++ "For quantile number q, "
++ "each quantile index i "
++ "should satisfy 0 <= i <= q, "
++ "but here q is "
++ show q
++ " and indexes are "
++ show qs
addCallPointInfo :: Maybe String -> Maybe String -> String -> String
addCallPointInfo (Just name) (Just cp) err =
err
++ ( "\n\tThis happened when calling function "
++ brightGreen cp
++ " on "
++ brightGreen name
)
addCallPointInfo Nothing (Just cp) err =
err
++ ( "\n\tThis happened when calling function "
++ brightGreen cp
)
addCallPointInfo (Just name) Nothing err =
err
++ ( "\n\tOn "
++ name
++ "\n\n"
)
addCallPointInfo Nothing Nothing err = err
guessColumnName :: T.Text -> [T.Text] -> T.Text
guessColumnName userInput columns = case map (\k -> (editDistance userInput k, k)) columns of
[] -> ""
res -> (snd . minimum) res
editDistance :: T.Text -> T.Text -> Int
editDistance xs ys = table ML.! (m, n)
where
(m, n) = (T.length xs, T.length ys)
xv = V.fromList (T.unpack xs)
yv = V.fromList (T.unpack ys)
table :: ML.Map (Int, Int) Int
table = ML.fromList [((i, j), dist i j) | i <- [0 .. m], j <- [0 .. n]]
dist 0 j = j
dist i 0 = i
dist i j =
minimum
[ table ML.! (i - 1, j) + 1
, table ML.! (i, j - 1) + 1
, (if xv V.! (i - 1) == yv V.! (j - 1) then 0 else 1)
+ table ML.! (i - 1, j - 1)
]