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 :: Text -> Text
replaceCommonEscapes = ( Text -> Text -> Text -> Text
T.replace (String -> Text
pack String
"\\n") (String -> Text
pack String
"\n") ) (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
( Text -> Text -> Text -> Text
T.replace (String -> Text
pack String
"\\\\") (String -> Text
pack String
"\\") ) (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
( Text -> Text -> Text -> Text
T.replace (String -> Text
pack String
"\\\"") (String -> Text
pack String
"\"") )
genLineErr' :: [Text] -> (Int, Int) -> Int -> Text
genLineErr' :: [Text] -> (Int, Int) -> Int -> Text
genLineErr' [Text]
allLines (Int, Int)
range Int
errorLineN = [Text] -> Text
T.unlines [ [Text] -> Text
T.concat [ if Int
i Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
errorLineN then String -> Text
pack String
">> " else String -> Text
pack String
" "
, String -> Text
pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ Int -> String
forall a. Show a => a -> String
show Int
i
, String -> Text
pack String
". "
, [Text]
allLines [Text] -> Int -> Text
forall a. [a] -> Int -> a
!! Int
i
] | Int
i <- (Int, Int) -> [Int]
forall a. Ix a => (a, a) -> [a]
Ix.range (Int, Int)
range]
genLineErr :: ByteString -> ByteString -> Text
genLineErr :: ByteString -> ByteString -> Text
genLineErr ByteString
full ByteString
part = [Text] -> (Int, Int) -> Int -> Text
genLineErr' [Text]
allLines (Int, Int)
interval Int
errorLineN
where
nLastLines :: Int
nLastLines = Int
1 Int -> Int -> Int
forall a. Num a => a -> a -> a
+ ([Text] -> Int
forall (t :: Type -> Type) a. Foldable t => t a -> Int
length ([Text] -> Int) -> [Text] -> Int
forall a b. (a -> b) -> a -> b
$ Text -> [Text]
T.lines (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> Text
replaceCommonEscapes (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ String -> Text
pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> String
forall a. Show a => a -> String
show ByteString
part)
errorLineN :: Int
errorLineN = [Text] -> Int
forall (t :: Type -> Type) a. Foldable t => t a -> Int
length [Text]
allLines Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
nLastLines Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
allLines :: [Text]
allLines = Text -> [Text]
T.lines (Text -> [Text]) -> Text -> [Text]
forall a b. (a -> b) -> a -> b
$ Text -> Text
replaceCommonEscapes (Text -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ String -> Text
pack (String -> Text) -> String -> Text
forall a b. (a -> b) -> a -> b
$ ByteString -> String
forall a. Show a => a -> String
show ByteString
full
interval :: (Int, Int)
interval = (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
errorLineN Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
5), Int -> Int -> Int
forall a. Ord a => a -> a -> a
min (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int -> Int) -> Int -> Int
forall a b. (a -> b) -> a -> b
$ [Text] -> Int
forall (t :: Type -> Type) a. Foldable t => t a -> Int
length [Text]
allLines Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1) (Int
errorLineN Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
5))
decodeOrErr
:: (HasCallStack, FromJSON a)
=> FilePath
-> ByteString
-> a
decodeOrErr :: String -> ByteString -> a
decodeOrErr String
path ByteString
contents =
case Parser Value -> ByteString -> Result Value
forall a. Parser a -> ByteString -> Result a
parse Parser Value
json ByteString
contents of
Done ByteString
leftover Value
v ->
case Value -> Result a
forall a. FromJSON a => Value -> Result a
fromJSON Value
v of
Success a
_ | (Word8 -> Bool) -> ByteString -> Bool
BS.any Word8 -> Bool
notWhitespace ByteString
leftover ->
String -> a
forall a. String -> a
clashError (String
"After parsing " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
path
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
", found unparsed trailing garbage:\n"
String -> String -> String
forall a. [a] -> [a] -> [a]
++ ByteString -> String
BSChar.unpack ByteString
leftover)
Success a
a ->
a
a
Error String
msg ->
String -> a
forall a. String -> a
clashError
( String
"Could not deduce valid scheme for json in "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
path String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". Error was: \n\n" String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
msg )
Fail ByteString
bytes [String]
cntxs String
msg ->
String -> a
forall a. String -> a
clashError
( String
"Could not read or parse json in " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> String
forall a. Show a => a -> String
show String
path String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
". "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ (if [String] -> Bool
forall (t :: Type -> Type) a. Foldable t => t a -> Bool
null [String]
cntxs then String
"" else String
"Context was:\n " String -> String -> String
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"\n " [String]
cntxs)
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n\nError reported by Attoparsec was:\n "
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
msg
String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
"\n\nApproximate location of error:\n\n"
String -> String -> String
forall a. [a] -> [a] -> [a]
++ (Text -> String
unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ ByteString -> ByteString -> Text
genLineErr ByteString
contents ByteString
bytes) )
where
loc :: SrcSpan
loc = FastString -> SrcSpan
mkGeneralSrcSpan (FastString -> SrcSpan) -> FastString -> SrcSpan
forall a b. (a -> b) -> a -> b
$ String -> FastString
mkFastString String
path
clashError :: String -> a
clashError String
msg = ClashException -> a
forall a e. Exception e => e -> a
throw (ClashException -> a) -> ClashException -> a
forall a b. (a -> b) -> a -> b
$ SrcSpan -> String -> Maybe String -> ClashException
ClashException SrcSpan
loc String
msg Maybe String
forall a. Maybe a
Nothing
notWhitespace :: Word8 -> Bool
notWhitespace Word8
c = Word8 -> ByteString -> Bool
BS.notElem Word8
c ByteString
whitespace
where whitespace :: ByteString
whitespace = String -> ByteString
BSChar.pack String
" \t\n\r"