on master help sign in
Accumulative Parser errors
186ff70lyxia
Data/Aeson/Parser/Internal.hs+6−5
@@ -42,6 +42,7 @@ import Control.Applicative ((<|>))
42import Control.Monad (void, when)
42import Control.Monad (void, when)
43import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))
43import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))
44import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
44import Data.Attoparsec.ByteString.Char8 (Parser, char, decimal, endOfInput, isDigit_w8, signed, string)
45+import Data.List.NonEmpty (NonEmpty((:|)))
45import Data.Scientific (Scientific)
46import Data.Scientific (Scientific)
46import Data.Text (Text)
47import Data.Text (Text)
47import Data.Vector as Vector (Vector, empty, fromListN, reverse)
48import Data.Vector as Vector (Vector, empty, fromListN, reverse)
@@ -274,17 +275,17 @@ eitherDecodeWith :: Parser Value -> (Value -> IResult a) -> L.ByteString
274eitherDecodeWith p to s =
275eitherDecodeWith p to s =
275 case L.parse p s of
276 case L.parse p s of
276 L.Done _ v -> case to v of
277 L.Done _ v -> case to v of
277− ISuccess a -> Right a
278+ ISuccess a -> Right a
278− IError path msg -> Left (path, msg)
279+ IError (e :| _) -> Left e
279 L.Fail _ _ msg -> Left ([], msg)
280 L.Fail _ _ msg -> Left ([], msg)
280{-# INLINE eitherDecodeWith #-}
281{-# INLINE eitherDecodeWith #-}
281
282
282eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
283eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
283 -> Either (JSONPath, String) a
284 -> Either (JSONPath, String) a
284eitherDecodeStrictWith p to s =
285eitherDecodeStrictWith p to s =
285− case either (IError []) to (A.parseOnly p s) of
286+ case either (\e -> IError (([], e) :| [])) to (A.parseOnly p s) of
286− ISuccess a -> Right a
287+ ISuccess a -> Right a
287− IError path msg -> Left (path, msg)
288+ IError (e :| _) -> Left e
288{-# INLINE eitherDecodeStrictWith #-}
289{-# INLINE eitherDecodeStrictWith #-}
289
290
290-- $lazy
291-- $lazy
Data/Aeson/Types.hs+2
@@ -35,9 +35,11 @@ module Data.Aeson.Types
35 , parseMaybe
35 , parseMaybe
36 , ToJSON(..)
36 , ToJSON(..)
37 , KeyValue(..)
37 , KeyValue(..)
38+ , liftP2
38 , modifyFailure
39 , modifyFailure
39 , parserThrowError
40 , parserThrowError
40 , parserCatchError
41 , parserCatchError
42+ , parserCatchErrors
41
43
42 -- ** Keys for maps
44 -- ** Keys for maps
43 , ToJSONKey(..)
45 , ToJSONKey(..)
Data/Aeson/Types/Internal.hs+39−21
@@ -43,9 +43,11 @@ module Data.Aeson.Types.Internal
43 , parse
43 , parse
44 , parseEither
44 , parseEither
45 , parseMaybe
45 , parseMaybe
46+ , liftP2
46 , modifyFailure
47 , modifyFailure
47 , parserThrowError
48 , parserThrowError
48 , parserCatchError
49 , parserCatchError
50+ , parserCatchErrors
49 , formatError
51 , formatError
50 , (<?>)
52 , (<?>)
51 -- * Constructors and accessors
53 -- * Constructors and accessors
@@ -87,6 +89,7 @@ import Data.Foldable (foldl')
87import Data.HashMap.Strict (HashMap)
89import Data.HashMap.Strict (HashMap)
88import Data.Hashable (Hashable(..))
90import Data.Hashable (Hashable(..))
89import Data.List (intercalate)
91import Data.List (intercalate)
92+import Data.List.NonEmpty (NonEmpty((:|)))
90import Data.Scientific (Scientific)
93import Data.Scientific (Scientific)
91import Data.Semigroup (Semigroup((<>)))
94import Data.Semigroup (Semigroup((<>)))
92import Data.String (IsString(..))
95import Data.String (IsString(..))
@@ -98,6 +101,7 @@ import Data.Vector (Vector)
98import GHC.Generics (Generic)
101import GHC.Generics (Generic)
99import qualified Control.Monad.Fail as Fail
102import qualified Control.Monad.Fail as Fail
100import qualified Data.HashMap.Strict as H
103import qualified Data.HashMap.Strict as H
104+import qualified Data.List.NonEmpty as NonEmpty
101import qualified Data.Scientific as S
105import qualified Data.Scientific as S
102import qualified Data.Vector as V
106import qualified Data.Vector as V
103import qualified Language.Haskell.TH.Syntax as TH
107import qualified Language.Haskell.TH.Syntax as TH
@@ -118,7 +122,7 @@ data JSONPathElement = Key Text
118type JSONPath = [JSONPathElement]
122type JSONPath = [JSONPathElement]
119
123
120-- | The internal result of running a 'Parser'.
124-- | The internal result of running a 'Parser'.
121−data IResult a = IError JSONPath String
125+data IResult a = IError (NonEmpty (JSONPath, String))
122 | ISuccess a
126 | ISuccess a
123 deriving (Eq, Show, Typeable)
127 deriving (Eq, Show, Typeable)
124
128
@@ -133,15 +137,15 @@ instance NFData JSONPathElement where
133
137
134instance (NFData a) => NFData (IResult a) where
138instance (NFData a) => NFData (IResult a) where
135 rnf (ISuccess a) = rnf a
139 rnf (ISuccess a) = rnf a
136− rnf (IError path err) = rnf path `seq` rnf err
140+ rnf (IError err) = rnf err
137
141
138instance (NFData a) => NFData (Result a) where
142instance (NFData a) => NFData (Result a) where
139 rnf (Success a) = rnf a
143 rnf (Success a) = rnf a
140 rnf (Error err) = rnf err
144 rnf (Error err) = rnf err
141
145
142instance Functor IResult where
146instance Functor IResult where
143− fmap f (ISuccess a) = ISuccess (f a)
147+ fmap f (ISuccess a) = ISuccess (f a)
144− fmap _ (IError path err) = IError path err
148+ fmap _ (IError err) = IError err
145 {-# INLINE fmap #-}
149 {-# INLINE fmap #-}
146
150
147instance Functor Result where
151instance Functor Result where
@@ -153,15 +157,15 @@ instance Monad IResult where
153 return = pure
157 return = pure
154 {-# INLINE return #-}
158 {-# INLINE return #-}
155
159
156− ISuccess a >>= k = k a
160+ ISuccess a >>= k = k a
157− IError path err >>= _ = IError path err
161+ IError err >>= _ = IError err
158 {-# INLINE (>>=) #-}
162 {-# INLINE (>>=) #-}
159
163
160 fail = Fail.fail
164 fail = Fail.fail
161 {-# INLINE fail #-}
165 {-# INLINE fail #-}
162
166
163instance Fail.MonadFail IResult where
167instance Fail.MonadFail IResult where
164− fail err = IError [] err
168+ fail err = IError (([], err) :| [])
165 {-# INLINE fail #-}
169 {-# INLINE fail #-}
166
170
167instance Monad Result where
171instance Monad Result where
@@ -238,11 +242,11 @@ instance Monoid (Result a) where
238 {-# INLINE mappend #-}
242 {-# INLINE mappend #-}
239
243
240instance Foldable IResult where
244instance Foldable IResult where
241− foldMap _ (IError _ _) = mempty
245+ foldMap _ (IError _) = mempty
242 foldMap f (ISuccess y) = f y
246 foldMap f (ISuccess y) = f y
243 {-# INLINE foldMap #-}
247 {-# INLINE foldMap #-}
244
248
245− foldr _ z (IError _ _) = z
249+ foldr _ z (IError _) = z
246 foldr f z (ISuccess y) = f y z
250 foldr f z (ISuccess y) = f y z
247 {-# INLINE foldr #-}
251 {-# INLINE foldr #-}
248
252
@@ -256,8 +260,8 @@ instance Foldable Result where
256 {-# INLINE foldr #-}
260 {-# INLINE foldr #-}
257
261
258instance Traversable IResult where
262instance Traversable IResult where
259− traverse _ (IError path err) = pure (IError path err)
263+ traverse _ (IError err) = pure (IError err)
260− traverse f (ISuccess a) = ISuccess <$> f a
264+ traverse f (ISuccess a) = ISuccess <$> f a
261 {-# INLINE traverse #-}
265 {-# INLINE traverse #-}
262
266
263instance Traversable Result where
267instance Traversable Result where
@@ -266,7 +270,7 @@ instance Traversable Result where
266 {-# INLINE traverse #-}
270 {-# INLINE traverse #-}
267
271
268-- | Failure continuation.
272-- | Failure continuation.
269−type Failure f r = JSONPath -> String -> f r
273+type Failure f r = NonEmpty (JSONPath, String) -> f r
270-- | Success continuation.
274-- | Success continuation.
271type Success a f r = a -> f r
275type Success a f r = a -> f r
272
276
@@ -289,7 +293,7 @@ instance Monad Parser where
289 {-# INLINE fail #-}
293 {-# INLINE fail #-}
290
294
291instance Fail.MonadFail Parser where
295instance Fail.MonadFail Parser where
292− fail msg = Parser $ \path kf _ks -> kf (reverse path) msg
296+ fail msg = Parser $ \path kf _ks -> kf ((reverse path, msg) :| [])
293 {-# INLINE fail #-}
297 {-# INLINE fail #-}
294
298
295instance Functor Parser where
299instance Functor Parser where
@@ -309,10 +313,11 @@ instance Alternative Parser where
309 (<|>) = mplus
313 (<|>) = mplus
310 {-# INLINE (<|>) #-}
314 {-# INLINE (<|>) #-}
311
315
316+{- TODO accumulate errors -}
312instance MonadPlus Parser where
317instance MonadPlus Parser where
313 mzero = fail "mzero"
318 mzero = fail "mzero"
314 {-# INLINE mzero #-}
319 {-# INLINE mzero #-}
315− mplus a b = Parser $ \path kf ks -> let kf' _ _ = runParser b path kf ks
320+ mplus a b = Parser $ \path kf ks -> let kf' _ = runParser b path kf ks
316 in runParser a path kf' ks
321 in runParser a path kf' ks
317 {-# INLINE mplus #-}
322 {-# INLINE mplus #-}
318
323
@@ -333,6 +338,14 @@ apP d e = do
333 return (b a)
338 return (b a)
334{-# INLINE apP #-}
339{-# INLINE apP #-}
335
340
341+-- | A variant of 'liftA2' that lazily accumulates errors from both subparsers.
342+liftP2 :: (a -> b -> c) -> Parser a -> Parser b -> Parser c
343+liftP2 f pa pb = Parser $ \path kf ks ->
344+ runParser pa path
345+ (\(e :| es) -> kf (e :| es ++ runParser pb path NonEmpty.toList (const [])))
346+ (\a -> runParser pb path kf (\b -> ks (f a b)))
347+{-# INLINE liftP2 #-}
348+
336-- | A JSON \"object\" (key\/value map).
349-- | A JSON \"object\" (key\/value map).
337type Object = HashMap Text Value
350type Object = HashMap Text Value
338
351
@@ -423,7 +436,7 @@ emptyObject = Object H.empty
423
436
424-- | Run a 'Parser'.
437-- | Run a 'Parser'.
425parse :: (a -> Parser b) -> a -> Result b
438parse :: (a -> Parser b) -> a -> Result b
426−parse m v = runParser (m v) [] (const Error) Success
439+parse m v = runParser (m v) [] (Error . snd . NonEmpty.head) Success
427{-# INLINE parse #-}
440{-# INLINE parse #-}
428
441
429-- | Run a 'Parser'.
442-- | Run a 'Parser'.
@@ -433,14 +446,14 @@ iparse m v = runParser (m v) [] IError ISuccess
433
446
434-- | Run a 'Parser' with a 'Maybe' result type.
447-- | Run a 'Parser' with a 'Maybe' result type.
435parseMaybe :: (a -> Parser b) -> a -> Maybe b
448parseMaybe :: (a -> Parser b) -> a -> Maybe b
436−parseMaybe m v = runParser (m v) [] (\_ _ -> Nothing) Just
449+parseMaybe m v = runParser (m v) [] (const Nothing) Just
437{-# INLINE parseMaybe #-}
450{-# INLINE parseMaybe #-}
438
451
439-- | Run a 'Parser' with an 'Either' result type. If the parse fails,
452-- | Run a 'Parser' with an 'Either' result type. If the parse fails,
440-- the 'Left' payload will contain an error message.
453-- the 'Left' payload will contain an error message.
441parseEither :: (a -> Parser b) -> a -> Either String b
454parseEither :: (a -> Parser b) -> a -> Either String b
442parseEither m v = runParser (m v) [] onError Right
455parseEither m v = runParser (m v) [] onError Right
443− where onError path msg = Left (formatError path msg)
456+ where onError ((path, err) :| _) = Left (formatError path err)
444{-# INLINE parseEither #-}
457{-# INLINE parseEither #-}
445
458
446-- | Annotate an error message with a
459-- | Annotate an error message with a
@@ -510,21 +523,26 @@ p <?> pathElem = Parser $ \path kf ks -> runParser p (pathElem:path) kf ks
510-- Since 0.6.2.0
523-- Since 0.6.2.0
511modifyFailure :: (String -> String) -> Parser a -> Parser a
524modifyFailure :: (String -> String) -> Parser a -> Parser a
512modifyFailure f (Parser p) = Parser $ \path kf ks ->
525modifyFailure f (Parser p) = Parser $ \path kf ks ->
513− p path (\p' m -> kf p' (f m)) ks
526+ p path (\m -> kf ((fmap . fmap) f m)) ks
514
527
515-- | Throw a parser error with an additional path.
528-- | Throw a parser error with an additional path.
516--
529--
517-- @since 1.2.1.0
530-- @since 1.2.1.0
518parserThrowError :: JSONPath -> String -> Parser a
531parserThrowError :: JSONPath -> String -> Parser a
519parserThrowError path' msg = Parser $ \path kf _ks ->
532parserThrowError path' msg = Parser $ \path kf _ks ->
520− kf (reverse path ++ path') msg
533+ kf ((reverse path ++ path', msg) :| [])
521
534
522-- | A handler function to handle previous errors and return to normal execution.
535-- | A handler function to handle previous errors and return to normal execution.
523--
536--
524-- @since 1.2.1.0
537-- @since 1.2.1.0
525parserCatchError :: Parser a -> (JSONPath -> String -> Parser a) -> Parser a
538parserCatchError :: Parser a -> (JSONPath -> String -> Parser a) -> Parser a
539+parserCatchError p handler = parserCatchErrors p (\((e, msg) :| _) -> handler e msg)
540+
541+-- | A handler function to handle multiple previous errors and return to normal
542+-- execution.
543+parserCatchErrors :: Parser a -> (NonEmpty (JSONPath, String) -> Parser a) -> Parser a
526−parserCatchError (Parser p) handler = Parser $ \path kf ks ->
544+parserCatchErrors (Parser p) handler = Parser $ \path kf ks ->
527− p path (\e msg -> runParser (handler e msg) path kf ks) ks
545+ p path (\es -> runParser (handler es) path kf ks) ks
528
546
529--------------------------------------------------------------------------------
547--------------------------------------------------------------------------------
530-- Generic and TH encoding configuration
548-- Generic and TH encoding configuration
tests/Properties.hs+7−7
@@ -21,7 +21,7 @@ import Data.Functor.Compose (Compose (..))
21import Data.HashMap.Strict (HashMap)
21import Data.HashMap.Strict (HashMap)
22import Data.Hashable (Hashable)
22import Data.Hashable (Hashable)
23import Data.Int (Int8)
23import Data.Int (Int8)
24−import Data.List.NonEmpty (NonEmpty)
24+import Data.List.NonEmpty (NonEmpty((:|)))
25import Data.Map (Map)
25import Data.Map (Map)
26import Data.Proxy (Proxy)
26import Data.Proxy (Proxy)
27import Data.Ratio (Ratio)
27import Data.Ratio (Ratio)
@@ -59,8 +59,8 @@ toParseJSON :: (Eq a, Show a) =>
59 (Value -> Parser a) -> (a -> Value) -> a -> Property
59 (Value -> Parser a) -> (a -> Value) -> a -> Property
60toParseJSON parsejson tojson x =
60toParseJSON parsejson tojson x =
61 case iparse parsejson . tojson $ x of
61 case iparse parsejson . tojson $ x of
62− IError path msg -> failure "parse" (formatError path msg) x
62+ IError ((path, msg) :| _) -> failure "parse" (formatError path msg) x
63− ISuccess x' -> x === x'
63+ ISuccess x' -> x === x'
64
64
65toParseJSON1
65toParseJSON1
66 :: (Eq (f Int), Show (f Int))
66 :: (Eq (f Int), Show (f Int))
@@ -78,15 +78,15 @@ roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
78roundTripEnc eq _ i =
78roundTripEnc eq _ i =
79 case fmap ifromJSON . L.parse value . encode $ i of
79 case fmap ifromJSON . L.parse value . encode $ i of
80 L.Done _ (ISuccess v) -> v `eq` i
80 L.Done _ (ISuccess v) -> v `eq` i
81− L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
81+ L.Done _ (IError ((path, err) :| _)) -> failure "fromJSON" (formatError path err) i
82 L.Fail _ _ err -> failure "parse" err i
82 L.Fail _ _ err -> failure "parse" err i
83
83
84roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
84roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
85 (a -> a -> Property) -> a -> a -> Property
85 (a -> a -> Property) -> a -> a -> Property
86roundTripNoEnc eq _ i =
86roundTripNoEnc eq _ i =
87 case ifromJSON . toJSON $ i of
87 case ifromJSON . toJSON $ i of
88− (ISuccess v) -> v `eq` i
88+ ISuccess v -> v `eq` i
89− (IError path err) -> failure "fromJSON" (formatError path err) i
89+ IError ((path, err) :| _) -> failure "fromJSON" (formatError path err) i
90
90
91roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
91roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
92roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
92roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
@@ -104,7 +104,7 @@ x ==~ y =
104
104
105toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
105toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
106toFromJSON x = case ifromJSON (toJSON x) of
106toFromJSON x = case ifromJSON (toJSON x) of
107− IError path err -> failure "fromJSON" (formatError path err) x
107+ IError ((path, err) :| _) -> failure "fromJSON" (formatError path err) x
108 ISuccess x' -> x === x'
108 ISuccess x' -> x === x'
109
109
110modifyFailureProp :: String -> String -> Bool
110modifyFailureProp :: String -> String -> Bool