module Data.Aeson.Extra where
import Control.Exception (throw)
import qualified Data.Ix as Ix
import qualified Data.Text as T
import Data.Text (Text,pack,unpack)
import Data.List (intercalate)
import Data.Aeson (FromJSON, Result (..), fromJSON, json)
import Data.Attoparsec.Lazy (Result (..), parse)
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as BS
import qualified Data.ByteString.Lazy.Char8 as BSChar
import System.FilePath ()
import Clash.Util (ClashException(..))
import SrcLoc (mkGeneralSrcSpan)
import FastString (mkFastString)
import GHC.Stack (HasCallStack)
replaceCommonEscapes :: Text -> Text
replaceCommonEscapes = ( T.replace (pack "\\n") (pack "\n") ) .
( T.replace (pack "\\\\") (pack "\\") ) .
( T.replace (pack "\\\"") (pack "\"") )
genLineErr' :: [Text] -> (Int, Int) -> Int -> Text
genLineErr' allLines range errorLineN = T.unlines [ T.concat [ if i == errorLineN then pack ">> " else pack " "
, pack $ show i
, pack ". "
, allLines !! i
] | i <- Ix.range range]
genLineErr :: ByteString -> ByteString -> Text
genLineErr full part = genLineErr' allLines interval errorLineN
where
nLastLines = 1 + (length $ T.lines $ replaceCommonEscapes $ pack $ show part)
errorLineN = length allLines - nLastLines + 1
allLines = T.lines $ replaceCommonEscapes $ pack $ show full
interval = (max 0 (errorLineN - 5), min (max 0 $ length allLines - 1) (errorLineN + 5))
decodeOrErr
:: (HasCallStack, FromJSON a)
=> FilePath
-> ByteString
-> a
decodeOrErr path contents =
case parse json contents of
Done leftover v ->
case fromJSON v of
Success _ | BS.any notWhitespace leftover ->
clashError ("After parsing " ++ show path
++ ", found unparsed trailing garbage:\n"
++ BSChar.unpack leftover)
Success a ->
a
Error msg ->
clashError
( "Could not deduce valid scheme for json in "
++ show path ++ ". Error was: \n\n" ++ msg )
Fail bytes cntxs msg ->
clashError
( "Could not read or parse json in " ++ show path ++ ". "
++ (if null cntxs then "" else "Context was:\n " ++ intercalate "\n " cntxs)
++ "\n\nError reported by Attoparsec was:\n "
++ msg
++ "\n\nApproximate location of error:\n\n"
++ (unpack $ genLineErr contents bytes) )
where
loc = mkGeneralSrcSpan $ mkFastString path
clashError msg = throw $ ClashException loc msg Nothing
notWhitespace c = BS.notElem c whitespace
where whitespace = BSChar.pack " \t\n\r"