module Data.Aeson.BetterErrors.Internal where
#if !MIN_VERSION_base(4,8,0)
import Control.Applicative (Applicative, pure, (<$>), (<*>))
import Data.Foldable (foldMap)
#endif
import Control.Arrow (left)
import Control.Monad.Identity
import Control.Monad.Reader
import Control.Monad.Trans.Except
import Control.Monad.Error.Class (MonadError(..))
import Data.Void
import Data.Monoid
import Data.DList (DList)
import qualified Data.DList as DList
import Data.Text (Text)
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8)
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as B
import qualified Data.Aeson as A
import qualified Data.Aeson.Types as A
import Data.Vector ((!?))
import qualified Data.Vector as V
import Data.Scientific (Scientific)
import qualified Data.Scientific as S
import qualified Data.HashMap.Strict as HashMap
import Data.Aeson.BetterErrors.Utils
newtype ParseT err m a
= ParseT (ReaderT ParseReader (ExceptT (ParseError err) m) a)
deriving (Functor, Applicative, Monad,
MonadReader ParseReader, MonadError (ParseError err))
type Parse err a = ParseT err Identity a
instance MonadTrans (ParseT err) where
lift f = ParseT (lift (lift f))
runParseT :: ParseT err m a -> A.Value -> m (Either (ParseError err) a)
runParseT (ParseT p) v = runExceptT (runReaderT p (ParseReader DList.empty v))
runParse :: Parse err a -> A.Value -> Either (ParseError err) a
runParse p v = runIdentity (runParseT p v)
mapParseT :: (ReaderT ParseReader (ExceptT (ParseError err) m) a -> ReaderT ParseReader (ExceptT (ParseError err') m') a') -> ParseT err m a -> ParseT err' m' a'
mapParseT f (ParseT p) = ParseT (f p)
mapError :: Functor m => (err -> err') -> ParseT err m a -> ParseT err' m a
mapError f = mapParseT (mapReaderT (withExceptT (fmap f)))
(.!) :: Functor m => ParseT err m a -> (err -> err') -> ParseT err' m a
(.!) = flip mapError
(<|>) :: Monad m => ParseT err m a -> ParseT err m a -> ParseT err m a
l <|> r = catchError l (const r)
infixl 3 <|>
type Parse' a = Parse Void a
runParserT :: Monad m =>
(s -> Either String A.Value) ->
ParseT err m a ->
s ->
m (Either (ParseError err) a)
runParserT decode p src =
case decode src of
Left err -> return $ Left (InvalidJSON err)
Right value -> runParseT p value
runParser ::
(s -> Either String A.Value) ->
Parse err a ->
s ->
Either (ParseError err) a
runParser decode p src =
runIdentity (runParserT decode p src)
parseM :: Monad m => ParseT err m a -> BL.ByteString -> m (Either (ParseError err) a)
parseM = runParserT A.eitherDecode
parse :: Parse err a -> BL.ByteString -> Either (ParseError err) a
parse = runParser A.eitherDecode
parseStrictM :: Monad m => ParseT err m a -> B.ByteString -> m (Either (ParseError err) a)
parseStrictM = runParserT A.eitherDecodeStrict
parseStrict :: Parse err a -> B.ByteString -> Either (ParseError err) a
parseStrict = runParser A.eitherDecodeStrict
parseValueM :: Monad m => ParseT err m a -> A.Value -> m (Either (ParseError err) a)
parseValueM = runParserT Right
parseValue :: Parse err a -> A.Value -> Either (ParseError err) a
parseValue = runParser Right
toAesonParser :: (err -> Text) -> Parse err a -> A.Value -> A.Parser a
toAesonParser showCustom p val =
case parseValue p val of
Right x -> return x
Left err -> fail (unlines (map T.unpack (displayError showCustom err)))
toAesonParser' :: Parse' a -> A.Value -> A.Parser a
toAesonParser' = toAesonParser absurd
fromAesonParser :: (Functor m, Monad m) => A.FromJSON a => ParseT e m a
fromAesonParser = liftParse $ \v ->
case A.fromJSON v of
A.Success x -> Right x
A.Error err -> Left (FromAeson err)
data ParseReader = ParseReader
{ rdrPath :: DList PathPiece
, rdrValue :: A.Value
}
appendPath :: PathPiece -> ParseReader -> ParseReader
appendPath p r = r { rdrPath = DList.snoc (rdrPath r) p }
setValue :: A.Value -> ParseReader -> ParseReader
setValue v r = r { rdrValue = v }
data PathPiece
= ObjectKey Text
| ArrayIndex Int
deriving (Show, Eq, Ord)
data ParseError err
= InvalidJSON String
| BadSchema [PathPiece] (ErrorSpecifics err)
deriving (Show, Eq, Functor)
type ParseError' = ParseError Void
data ErrorSpecifics err
= KeyMissing Text
| OutOfBounds Int
| WrongType JSONType A.Value
| ExpectedIntegral Double
| FromAeson String
| CustomError err
deriving (Show, Eq, Functor)
type ErrorSpecifics' = ErrorSpecifics Void
data JSONType
= TyObject
| TyArray
| TyString
| TyNumber
| TyBool
| TyNull
deriving (Show, Eq, Ord, Enum, Bounded)
displayJSONType :: JSONType -> Text
displayJSONType t = case t of
TyObject -> "object"
TyArray -> "array"
TyString -> "string"
TyNumber -> "number"
TyBool -> "boolean"
TyNull -> "null"
displayError :: (err -> Text) -> ParseError err -> [Text]
displayError _ (InvalidJSON str) =
[ "The input could not be parsed as JSON", "aeson said: " <> T.pack str ]
displayError f (BadSchema [] specs) =
displaySpecifics f specs
displayError f (BadSchema path specs) =
[ "At the path: " <> displayPath path ] <> displaySpecifics f specs
displayError' :: ParseError' -> [Text]
displayError' = displayError absurd
displayPath :: [PathPiece] -> Text
displayPath = foldMap showPiece
where
showPiece (ObjectKey t) = "[" <> tshow t <> "]"
showPiece (ArrayIndex i) = "[" <> tshow i <> "]"
displaySpecifics :: (err -> Text) -> ErrorSpecifics err -> [Text]
displaySpecifics _ (KeyMissing k) =
[ "The required key " <> tshow k <> " is missing" ]
displaySpecifics _ (OutOfBounds i) =
[ "The array index " <> tshow i <> " is out of bounds" ]
displaySpecifics _ (WrongType t val) =
[ "Type mismatch:"
, "Expected a value of type " <> displayJSONType t
, "Got: " <> decodeUtf8 (B.concat (BL.toChunks (A.encode val)))
]
displaySpecifics _ (ExpectedIntegral x) =
[ "Expected an integral value, got " <> tshow x ]
displaySpecifics _ (FromAeson str) =
[ "Arising from an Aeson FromJSON instance:"
, T.pack str
]
displaySpecifics f (CustomError err) =
[ f err ]
displaySpecifics' :: ErrorSpecifics' -> [Text]
displaySpecifics' = displaySpecifics absurd
jsonTypeOf :: A.Value -> JSONType
jsonTypeOf (A.Object _) = TyObject
jsonTypeOf (A.Array _) = TyArray
jsonTypeOf (A.String _) = TyString
jsonTypeOf (A.Number _) = TyNumber
jsonTypeOf (A.Bool _) = TyBool
jsonTypeOf A.Null = TyNull
liftParseT :: (Functor m, Monad m) => (A.Value -> ExceptT (ErrorSpecifics err) m a) -> ParseT err m a
liftParseT f = ParseT $ ReaderT $ \(ParseReader path value) ->
withExceptT (BadSchema (DList.toList path)) (f value)
liftParseM :: (Functor m, Monad m) => (A.Value -> m (Either (ErrorSpecifics err) a)) -> ParseT err m a
liftParseM f = liftParseT (ExceptT . f)
liftParse :: (Functor m, Monad m) => (A.Value -> Either (ErrorSpecifics err) a) -> ParseT err m a
liftParse f = liftParseM (return . f)
badSchema :: (Functor m, Monad m) => ErrorSpecifics err -> ParseT err m a
badSchema = liftParse . const . Left
as :: (Functor m, Monad m) => (A.Value -> Maybe a) -> JSONType -> ParseT err m a
as pat ty = liftParse $ \v ->
maybe (Left (WrongType ty v)) Right (pat v)
asValue :: (Functor m, Monad m) => ParseT err m A.Value
asValue = asks rdrValue
asText :: (Functor m, Monad m) => ParseT err m Text
asText = as patString TyString
asString :: (Functor m, Monad m) => ParseT err m String
asString = T.unpack <$> asText
asScientific :: (Functor m, Monad m) => ParseT err m Scientific
asScientific = as patNumber TyNumber
asIntegral :: (Functor m, Monad m, Integral a) => ParseT err m a
asIntegral =
asScientific
>>= liftParse . const . left ExpectedIntegral . S.floatingOrInteger
asRealFloat :: (Functor m, Monad m, RealFloat a) => ParseT err m a
asRealFloat =
either id fromInteger . S.floatingOrInteger <$> asScientific
asBool :: (Functor m, Monad m) => ParseT err m Bool
asBool = as patBool TyBool
asObject :: (Functor m, Monad m) => ParseT err m A.Object
asObject = as patObject TyObject
asArray :: (Functor m, Monad m) => ParseT err m A.Array
asArray = as patArray TyArray
asNull :: (Functor m, Monad m) => ParseT err m ()
asNull = as patNull TyNull
perhaps :: (Functor m, Monad m) => ParseT err m a -> ParseT err m (Maybe a)
perhaps p = do
v <- asks rdrValue
case v of
A.Null -> return Nothing
_ -> Just <$> p
key :: (Functor m, Monad m) => Text -> ParseT err m a -> ParseT err m a
key k p = key' (badSchema (KeyMissing k)) k p
keyOrDefault :: (Functor m, Monad m) => Text -> a -> ParseT err m a -> ParseT err m a
keyOrDefault k def p = key' (pure def) k p
keyMay :: (Functor m, Monad m) => Text -> ParseT err m a -> ParseT err m (Maybe a)
keyMay k p = keyOrDefault k Nothing (Just <$> p)
key' :: (Functor m, Monad m) => ParseT err m a -> Text -> ParseT err m a -> ParseT err m a
key' onMissing k p = do
v <- asks rdrValue
case v of
A.Object obj ->
case HashMap.lookup k obj of
Just v' ->
local (appendPath (ObjectKey k) . setValue v') p
Nothing ->
onMissing
_ ->
badSchema (WrongType TyObject v)
nth :: (Functor m, Monad m) => Int -> ParseT err m a -> ParseT err m a
nth n p = nth' (badSchema (OutOfBounds n)) n p
nthOrDefault :: (Functor m, Monad m) => Int -> a -> ParseT err m a -> ParseT err m a
nthOrDefault n def p =
nth' (pure def) n p
nthMay :: (Functor m, Monad m) => Int -> ParseT err m a -> ParseT err m (Maybe a)
nthMay n p = nthOrDefault n Nothing (Just <$> p)
nth' :: (Functor m, Monad m) => ParseT err m a -> Int -> ParseT err m a -> ParseT err m a
nth' onMissing n p = do
v <- asks rdrValue
case v of
A.Array vect ->
case vect !? n of
Just v' ->
local (appendPath (ArrayIndex n) . setValue v') p
Nothing ->
onMissing
_ ->
badSchema (WrongType TyArray v)
eachInArray :: (Functor m, Monad m) => ParseT err m a -> ParseT err m [a]
eachInArray p = do
xs <- zip [0..] . V.toList <$> asArray
forM xs $ \(i, x) ->
local (appendPath (ArrayIndex i) . setValue x) p
forEachInObject :: (Functor m, Monad m) => (Text -> ParseT err m a) -> ParseT err m [a]
forEachInObject p = do
xs <- HashMap.toList <$> asObject
forM xs $ \(k, x) ->
local (appendPath (ObjectKey k) . setValue x) (p k)
eachInObject :: (Functor m, Monad m) => ParseT err m a -> ParseT err m [(Text, a)]
eachInObject = eachInObjectWithKey Right
eachInObjectWithKey :: (Functor m, Monad m) => (Text -> Either err k) -> ParseT err m a -> ParseT err m [(k, a)]
eachInObjectWithKey parseKey parseVal = forEachInObject $ \k ->
(,) <$> liftEither (parseKey k) <*> parseVal
withValue :: (Functor m, Monad m) => (A.Value -> Either err a) -> ParseT err m a
withValue f = liftParse (left CustomError . f)
withValueM :: (Functor m, Monad m) => (A.Value -> m (Either err a)) -> ParseT err m a
withValueM f = liftParseM (fmap (left CustomError) . f)
liftEither :: (Functor m, Monad m) => Either err a -> ParseT err m a
liftEither = withValue . const
withM :: (Functor m, Monad m) => ParseT err m a -> (a -> m (Either err b)) -> ParseT err m b
withM g f = g >>= lift . f >>= liftEither
with :: (Functor m, Monad m) => ParseT err m a -> (a -> Either err b) -> ParseT err m b
with g f = withM g (return . f)
withTextM :: (Functor m, Monad m) => (Text -> m (Either err a)) -> ParseT err m a
withTextM = withM asText
withText :: (Functor m, Monad m) => (Text -> Either err a) -> ParseT err m a
withText = with asText
withStringM :: (Functor m, Monad m) => (String -> m (Either err a)) -> ParseT err m a
withStringM = withM asString
withString :: (Functor m, Monad m) => (String -> Either err a) -> ParseT err m a
withString = with asString
withScientificM :: (Functor m, Monad m) => (Scientific -> m (Either err a)) -> ParseT err m a
withScientificM = withM asScientific
withScientific :: (Functor m, Monad m) => (Scientific -> Either err a) -> ParseT err m a
withScientific = with asScientific
withIntegralM :: (Functor m, Monad m, Integral a) => (a -> m (Either err b)) -> ParseT err m b
withIntegralM = withM asIntegral
withIntegral :: (Functor m, Monad m, Integral a) => (a -> Either err b) -> ParseT err m b
withIntegral = with asIntegral
withRealFloatM :: (Functor m, Monad m, RealFloat a) => (a -> m (Either err b)) -> ParseT err m b
withRealFloatM = withM asRealFloat
withRealFloat :: (Functor m, Monad m, RealFloat a) => (a -> Either err b) -> ParseT err m b
withRealFloat = with asRealFloat
withBoolM :: (Functor m, Monad m) => (Bool -> m (Either err a)) -> ParseT err m a
withBoolM = withM asBool
withBool :: (Functor m, Monad m) => (Bool -> Either err a) -> ParseT err m a
withBool = with asBool
withObjectM :: (Functor m, Monad m) => (A.Object -> m (Either err a)) -> ParseT err m a
withObjectM = withM asObject
withObject :: (Functor m, Monad m) => (A.Object -> Either err a) -> ParseT err m a
withObject = with asObject
withArrayM :: (Functor m, Monad m) => (A.Array -> m (Either err a)) -> ParseT err m a
withArrayM = withM asArray
withArray :: (Functor m, Monad m) => (A.Array -> Either err a) -> ParseT err m a
withArray = with asArray
throwCustomError :: (Functor m, Monad m) => err -> ParseT err m a
throwCustomError = liftEither . Left
liftCustomT :: (Functor m, Monad m) => ExceptT err m a -> ParseT err m a
liftCustomT f = lift (runExceptT f) >>= liftEither