Skip to content

Commit 3c0e3e2

Browse files
Merge pull request #44 from crcornwell/master
include key and index in error message from getField functions
2 parents 1e50939 + 695f11e commit 3c0e3e2

File tree

4 files changed

+101
-38
lines changed

4 files changed

+101
-38
lines changed

bower.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,6 @@
2929
"purescript-foreign-object": "^1.0.0"
3030
},
3131
"devDependencies": {
32-
"purescript-quickcheck": "^5.0.0"
32+
"purescript-test-unit": "^14.0.0"
3333
}
3434
}

src/Data/Argonaut/Decode/Class.purs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import Data.Map as M
1414
import Data.Maybe (maybe, Maybe(..))
1515
import Data.String (CodePoint, codePointAt)
1616
import Data.Traversable (traverse)
17+
import Data.TraversableWithIndex (traverseWithIndex)
1718
import Data.Tuple (Tuple(..))
1819
import Foreign.Object as FO
1920

@@ -75,8 +76,11 @@ instance decodeForeignObject :: DecodeJson a => DecodeJson (FO.Object a) where
7576

7677
instance decodeArray :: DecodeJson a => DecodeJson (Array a) where
7778
decodeJson
78-
= lmap ("Couldn't decode Array: " <> _)
79-
<<< (traverse decodeJson <=< decodeJArray)
79+
= lmap ("Couldn't decode Array (" <> _)
80+
<<< (traverseWithIndex f <=< decodeJArray)
81+
where
82+
msg i m = "Failed at index " <> show i <> "): " <> m
83+
f i = lmap (msg i) <<< decodeJson
8084

8185
instance decodeList :: DecodeJson a => DecodeJson (List a) where
8286
decodeJson

src/Data/Argonaut/Decode/Combinators.purs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1-
module Data.Argonaut.Decode.Combinators where
1+
module Data.Argonaut.Decode.Combinators
2+
( getField
3+
, getFieldOptional
4+
, defaultField
5+
, (.?)
6+
, (.??)
7+
, (.?=)
8+
) where
29

310
import Prelude
411

512
import Data.Argonaut.Core (Json)
613
import Data.Argonaut.Decode.Class (class DecodeJson, decodeJson)
14+
import Data.Bifunctor (lmap)
715
import Data.Either (Either(..))
816
import Data.Maybe (Maybe(..), fromMaybe, maybe)
917
import Foreign.Object as FO
@@ -12,7 +20,7 @@ getField :: forall a. DecodeJson a => FO.Object Json -> String -> Either String
1220
getField o s =
1321
maybe
1422
(Left $ "Expected field " <> show s)
15-
decodeJson
23+
(elaborateFailure s <<< decodeJson)
1624
(FO.lookup s o)
1725

1826
infix 7 getField as .?
@@ -24,11 +32,17 @@ getFieldOptional o s =
2432
decode
2533
(FO.lookup s o)
2634
where
27-
decode json = Just <$> decodeJson json
35+
decode json = Just <$> (elaborateFailure s <<< decodeJson) json
2836

2937
infix 7 getFieldOptional as .??
3038

3139
defaultField :: forall a. Either String (Maybe a) -> a -> Either String a
3240
defaultField parser default = fromMaybe default <$> parser
3341

3442
infix 6 defaultField as .?=
43+
44+
elaborateFailure :: a. String -> Either String a -> Either String a
45+
elaborateFailure s e =
46+
lmap msg e
47+
where
48+
msg m = "Failed to decode key '" <> s <> "': " <> m

test/Test/Main.purs

Lines changed: 77 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,37 +4,42 @@ import Prelude
44

55
import Control.Monad.Gen.Common (genMaybe)
66
import Data.Argonaut.Core (Json, isObject, stringify, toObject)
7-
import Data.Argonaut.Decode (decodeJson)
7+
import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.?))
88
import Data.Argonaut.Encode (encodeJson, (:=), (:=?), (~>), (~>?))
99
import Data.Argonaut.Gen (genJson)
10+
import Data.Argonaut.Parser (jsonParser)
1011
import Data.Bifunctor (rmap)
1112
import Data.Either (Either(..))
1213
import Data.Foldable (foldl)
1314
import Data.Maybe (Maybe(..), isJust, isNothing, maybe)
1415
import Data.String.Gen (genUnicodeString)
1516
import Data.Tuple (Tuple(..))
1617
import Effect (Effect)
17-
import Effect.Console (log)
1818
import Foreign.Object as FO
19-
import Test.QuickCheck (Result(..), quickCheck, (<?>), (===))
19+
import Test.QuickCheck (Result(..), (<?>), (===))
2020
import Test.QuickCheck.Gen (Gen, resize, suchThat)
21+
import Test.Unit (TestSuite, test, suite, failure)
22+
import Test.Unit.Assert as Assert
23+
import Test.Unit.Main (runTest)
24+
import Test.Unit.QuickCheck (quickCheck)
2125

2226
main :: Effect Unit
23-
main = do
24-
eitherCheck
25-
encodeDecodeCheck
26-
combinatorsCheck
27+
main = runTest do
28+
suite "Either Check" eitherCheck
29+
suite "Encode/Decode Checks" encodeDecodeCheck
30+
suite "Combinators Checks" combinatorsCheck
31+
suite "Error Message Checks" errorMsgCheck
2732

2833
genTestJson :: Gen Json
2934
genTestJson = resize 5 genJson
3035

31-
encodeDecodeCheck :: Effect Unit
36+
encodeDecodeCheck :: TestSuite
3237
encodeDecodeCheck = do
33-
log "Testing that any JSON can be encoded and then decoded"
34-
quickCheck prop_encode_then_decode
38+
test "Testing that any JSON can be encoded and then decoded" do
39+
quickCheck prop_encode_then_decode
3540

36-
log "Testing that any JSON can be decoded and then encoded"
37-
quickCheck prop_decode_then_encode
41+
test "Testing that any JSON can be decoded and then encoded" do
42+
quickCheck prop_decode_then_encode
3843

3944
where
4045

@@ -54,18 +59,18 @@ encodeDecodeCheck = do
5459
genObj :: Gen Json
5560
genObj = suchThat (resize 5 genJson) isObject
5661

57-
combinatorsCheck :: Effect Unit
62+
combinatorsCheck :: TestSuite
5863
combinatorsCheck = do
59-
log "Check assoc builder `:=`"
60-
quickCheck prop_assoc_builder_str
61-
log "Check assocOptional builder `:=?`"
62-
quickCheck prop_assoc_optional_builder_str
63-
log "Check JAssoc append `~>`"
64-
quickCheck prop_assoc_append
65-
log "Check JAssoc appendOptional `~>?`"
66-
quickCheck prop_assoc_append_optional
67-
log "Check get field `obj .? 'foo'`"
68-
quickCheck prop_get_jobject_field
64+
test "Check assoc builder `:=`" do
65+
quickCheck prop_assoc_builder_str
66+
test "Check assocOptional builder `:=?`" do
67+
quickCheck prop_assoc_optional_builder_str
68+
test "Check JAssoc append `~>`" do
69+
quickCheck prop_assoc_append
70+
test "Check JAssoc appendOptional `~>?`" do
71+
quickCheck prop_assoc_append_optional
72+
test "Check get field `obj .? 'foo'`" do
73+
quickCheck prop_get_jobject_field
6974

7075
where
7176

@@ -116,13 +121,53 @@ combinatorsCheck = do
116121
let keys = FO.keys object
117122
in foldl (\ok key -> ok && isJust (FO.lookup key object)) true keys
118123

119-
eitherCheck :: Effect Unit
124+
eitherCheck :: TestSuite
120125
eitherCheck = do
121-
log "Test EncodeJson/DecodeJson Either instance"
122-
quickCheck \(x :: Either String String) ->
123-
case decodeJson (encodeJson x) of
124-
Right decoded ->
125-
decoded == x
126-
<?> ("x = " <> show x <> ", decoded = " <> show decoded)
127-
Left err ->
128-
false <?> err
126+
test "Test EncodeJson/DecodeJson Either test" do
127+
quickCheck \(x :: Either String String) ->
128+
case decodeJson (encodeJson x) of
129+
Right decoded ->
130+
decoded == x
131+
<?> ("x = " <> show x <> ", decoded = " <> show decoded)
132+
Left err ->
133+
false <?> err
134+
135+
errorMsgCheck :: TestSuite
136+
errorMsgCheck = do
137+
test "Test that decoding array fails with the proper message" do
138+
case notBar of
139+
Left err -> Assert.equal barErr err
140+
_ -> failure "Should have failed to decode"
141+
test "Test that decoding record fails with the proper message" do
142+
case notBaz of
143+
Left err -> Assert.equal bazErr err
144+
_ -> failure "Should have failed to decode"
145+
146+
where
147+
148+
barErr :: String
149+
barErr = "Failed to decode key 'bar': "
150+
<> "Couldn't decode Array (Failed at index 1): "
151+
<> "Value is not a Number"
152+
153+
bazErr :: String
154+
bazErr = "Failed to decode key 'baz': "
155+
<> "Value is not a Boolean"
156+
157+
notBar :: Either String Foo
158+
notBar = decodeJson =<< jsonParser "{ \"bar\": [1, true, 3], \"baz\": false }"
159+
160+
notBaz :: Either String Foo
161+
notBaz = decodeJson =<< jsonParser "{ \"bar\": [1, 2, 3], \"baz\": 42 }"
162+
163+
newtype Foo = Foo
164+
{ bar :: Array Int
165+
, baz :: Boolean
166+
}
167+
168+
instance decodeJsonFoo :: DecodeJson Foo where
169+
decodeJson json = do
170+
x <- decodeJson json
171+
bar <- x .? "bar"
172+
baz <- x .? "baz"
173+
pure $ Foo { bar, baz }

0 commit comments

Comments
 (0)