Skip to content

Commit abdb8a3

Browse files
committed
Accumulate errors for Seq and Vector parsers
1 parent 0f13c59 commit abdb8a3

File tree

5 files changed

+76
-7
lines changed

5 files changed

+76
-7
lines changed

Data/Aeson/Types/FromJSON.hs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ parseIndexedJSONPair keyParser valParser idx value = p value <?> Index idx
164164
p = withArray "(k,v)" $ \ab ->
165165
let n = V.length ab
166166
in if n == 2
167-
then (,) <$> parseJSONElemAtIndex keyParser 0 ab
168-
<*> parseJSONElemAtIndex valParser 1 ab
167+
then (,) <$> parseJSONElemAtIndex keyParser 0 ab
168+
<*>+ parseJSONElemAtIndex valParser 1 ab
169169
else fail $ "cannot unpack array of length " ++
170170
show n ++ " into a pair"
171171
{-# INLINE parseIndexedJSONPair #-}
@@ -606,7 +606,7 @@ parseJSON2 = liftParseJSON2 parseJSON parseJSONList parseJSON parseJSONList
606606

607607
-- | Helper function to use with 'liftParseJSON'. See 'Data.Aeson.ToJSON.listEncoding'.
608608
listParser :: (Value -> Parser a) -> Value -> Parser [a]
609-
listParser f (Array xs) = fmap V.toList (V.mapM f xs)
609+
listParser f (Array xs) = fmap V.toList (accumulateTraverseVector f xs)
610610
listParser _ v = typeMismatch "[a]" v
611611
{-# INLINE listParser #-}
612612

@@ -1529,7 +1529,7 @@ instance (FromJSON1 f, FromJSON1 g, FromJSON a) => FromJSON (Sum f g a) where
15291529
instance FromJSON1 Seq.Seq where
15301530
liftParseJSON p _ = withArray "Seq a" $
15311531
fmap Seq.fromList .
1532-
Tr.sequence . zipWith (parseIndexedJSON p) [0..] . V.toList
1532+
accumulateSequenceList . zipWith (parseIndexedJSON p) [0..] . V.toList
15331533
{-# INLINE liftParseJSON #-}
15341534

15351535
instance (FromJSON a) => FromJSON (Seq.Seq a) where
@@ -1607,7 +1607,7 @@ instance FromJSONKey UUID.UUID where
16071607

16081608
instance FromJSON1 Vector where
16091609
liftParseJSON p _ = withArray "Vector a" $
1610-
V.mapM (uncurry $ parseIndexedJSON p) . V.indexed
1610+
accumulateTraverseVector (uncurry $ parseIndexedJSON p) . V.indexed
16111611
{-# INLINE liftParseJSON #-}
16121612

16131613
instance (FromJSON a) => FromJSON (Vector a) where

Data/Aeson/Types/Internal.hs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,12 @@
2525

2626
module Data.Aeson.Types.Internal
2727
(
28+
accumulateSequenceList
29+
, accumulateTraverseList
30+
, accumulateTraverseVector
31+
2832
-- * Core JSON types
29-
Value(..)
33+
, Value(..)
3034
, Array
3135
, emptyArray, isEmptyArray
3236
, Pair
@@ -349,6 +353,20 @@ liftP2 f pa pb = Parser $ \path kf ks ->
349353
(\a -> runParser pb path kf (\b -> ks (f a b)))
350354
{-# INLINE liftP2 #-}
351355

356+
accumulateSequenceList :: [Parser a] -> Parser [a]
357+
accumulateSequenceList = accumulateTraverseList id
358+
359+
accumulateTraverseList :: (a -> Parser b) -> [a] -> Parser [b]
360+
accumulateTraverseList f s = case s of
361+
[] -> pure mempty
362+
h : t -> (:) <$> (f h) <*>+ (accumulateTraverseList f t)
363+
364+
accumulateTraverseVector :: (a -> Parser b) -> Vector a -> Parser (Vector b)
365+
accumulateTraverseVector f v =
366+
if V.null v
367+
then pure mempty
368+
else V.cons <$> (f $ V.head v) <*>+ (accumulateTraverseVector f $ V.tail v)
369+
352370
infixl 4 <*>+
353371

354372
-- | A variant of ('<*>') that lazily accumulates errors from both subparsers.

aeson.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ test-suite tests
196196
SerializationFormatSpec
197197
Types
198198
UnitTests
199+
UnitTests.AccErrors
199200
UnitTests.NullaryConstructors
200201

201202
build-depends:

tests/Tests.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,15 @@ import Test.Framework (defaultMain)
77
import qualified DataFamilies.Properties as DF
88
import qualified Properties
99
import qualified UnitTests
10+
import qualified UnitTests.AccErrors as AccErrors
1011

1112
main :: IO ()
1213
main = do
1314
ioTests <- UnitTests.ioTests
14-
defaultMain (DF.tests : Properties.tests : UnitTests.tests : ioTests)
15+
defaultMain
16+
( AccErrors.tests
17+
: DF.tests
18+
: Properties.tests
19+
: UnitTests.tests
20+
: ioTests
21+
)

tests/UnitTests/AccErrors.hs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{-# LANGUAGE NoMonomorphismRestriction #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
module UnitTests.AccErrors (tests) where
4+
5+
import Prelude ()
6+
import Prelude.Compat hiding (seq)
7+
8+
import Data.Aeson
9+
import Data.Aeson.Parser.Internal
10+
import Data.Aeson.Types ()
11+
import Data.Aeson.Internal
12+
import Data.List.NonEmpty (NonEmpty)
13+
import Data.Semigroup
14+
import Data.Vector (Vector)
15+
import Test.Framework
16+
import Test.Framework.Providers.HUnit
17+
import Test.HUnit hiding (Test)
18+
import qualified Data.ByteString.Lazy as L
19+
import qualified Data.List.NonEmpty as NL
20+
import qualified Data.Sequence as Seq
21+
22+
tests :: Test
23+
tests = testGroup "Error accumulation" [
24+
testCase "seq" seq
25+
, testCase "vector" vector
26+
]
27+
28+
decoder :: FromJSON a
29+
=> L.ByteString
30+
-> Either (NonEmpty (JSONPath, String)) a
31+
decoder = verboseDecodeWith jsonEOF ifromJSON
32+
33+
seq :: Assertion
34+
seq = do
35+
let res = decoder "[true, null]" :: Either (NonEmpty (JSONPath, String)) (Seq.Seq Int)
36+
let message i s = ([Index i], "expected Int, encountered " <> s)
37+
res @=? Left (NL.fromList [message 0 "Boolean", message 1 "Null"])
38+
39+
vector :: Assertion
40+
vector = do
41+
let res = decoder "[true, null]" :: Either (NonEmpty (JSONPath, String)) (Vector Int)
42+
let message i s = ([Index i], "expected Int, encountered " <> s)
43+
res @=? Left (NL.fromList [message 0 "Boolean", message 1 "Null"])

0 commit comments

Comments
 (0)