on master help sign in
Sort out the API backwards compatibility story
commit9cf5ae35fc0e7fb82bdeea72bf55e23f5201255d authorBryan O'Sullivan <bos@serpentine.com> dateThu 2 Jul 2015, 21:31 (11 years ago) parent5cce77a
9 files changed+138−45
Data/Aeson.hs+9−4 Data/Aeson/Internal.hs+25 Data/Aeson/Parser/Internal.hs+9−9 Data/Aeson/Types.hs+1−2 Data/Aeson/Types/Instances.hs+7 Data/Aeson/Types/Internal.hs+68−15 aeson.cabal+1 tests/Properties.hs+12−11 tests/Properties/ZonedTime.hs+6−4
Data/Aeson.hs+9−4
@@ -58,6 +58,7 @@ module Data.Aeson
5858 , GFromJSON(..)
5959 , GToJSON(..)
6060 , genericToJSON
61+ , genericToEncoding
6162 , genericParseJSON
6263
6364 -- * Inspecting @'Value's@
@@ -85,6 +86,8 @@ import Data.Aeson.Parser.Internal (decodeWith, decodeStrictWith,
8586 eitherDecodeWith, eitherDecodeStrictWith,
8687 jsonEOF, json, jsonEOF', json')
8788import Data.Aeson.Types
89+import Data.Aeson.Types.Internal (JSONPath, formatError)
90+import Data.Aeson.Types.Instances (ifromJSON)
8891import qualified Data.ByteString as B
8992import qualified Data.ByteString.Lazy as L
9093
@@ -149,22 +152,24 @@ eitherFormatError = either (Left . uncurry formatError) Right
149152
150153-- | Like 'decode' but returns an error message when decoding fails.
151154eitherDecode :: (FromJSON a) => L.ByteString -> Either String a
152−eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF fromJSON
155+eitherDecode = eitherFormatError . eitherDecodeWith jsonEOF ifromJSON
153156{-# INLINE eitherDecode #-}
154157
155158-- | Like 'decodeStrict' but returns an error message when decoding fails.
156159eitherDecodeStrict :: (FromJSON a) => B.ByteString -> Either String a
157−eitherDecodeStrict = eitherFormatError . eitherDecodeStrictWith jsonEOF fromJSON
160+eitherDecodeStrict =
161+ eitherFormatError . eitherDecodeStrictWith jsonEOF ifromJSON
158162{-# INLINE eitherDecodeStrict #-}
159163
160164-- | Like 'decode'' but returns an error message when decoding fails.
161165eitherDecode' :: (FromJSON a) => L.ByteString -> Either String a
162−eitherDecode' = eitherFormatError . eitherDecodeWith jsonEOF' fromJSON
166+eitherDecode' = eitherFormatError . eitherDecodeWith jsonEOF' ifromJSON
163167{-# INLINE eitherDecode' #-}
164168
165169-- | Like 'decodeStrict'' but returns an error message when decoding fails.
166170eitherDecodeStrict' :: (FromJSON a) => B.ByteString -> Either String a
167−eitherDecodeStrict' = eitherFormatError . eitherDecodeStrictWith jsonEOF' fromJSON
171+eitherDecodeStrict' =
172+ eitherFormatError . eitherDecodeStrictWith jsonEOF' ifromJSON
168173{-# INLINE eitherDecodeStrict' #-}
169174
170175-- $use
Data/Aeson/Internal.hsnew file+25
@@ -0,0 +1,25 @@
1+-- |
2+-- Module: Data.Aeson.Internal
3+-- Copyright: (c) 2015 Bryan O'Sullivan
4+-- License: Apache
5+-- Maintainer: Bryan O'Sullivan <bos@serpentine.com>
6+-- Stability: experimental
7+-- Portability: portable
8+--
9+-- Internal types and functions.
10+--
11+-- __Note__: all declarations in this module are unstable, and prone
12+-- to being changed at any time.
13+
14+module Data.Aeson.Internal
15+ (
16+ IResult(..)
17+ , JSONPathElement
18+ , JSONPath
19+ , formatError
20+ , ifromJSON
21+ , iparse
22+ ) where
23+
24+import Data.Aeson.Types.Instances (ifromJSON)
25+import Data.Aeson.Types.Internal
Data/Aeson/Parser/Internal.hs+9−9
@@ -30,7 +30,7 @@ module Data.Aeson.Parser.Internal
3030
3131import Control.Applicative ((*>), (<$>), (<*), liftA2, pure)
3232import Control.Monad.IO.Class (liftIO)
33−import Data.Aeson.Types (JSONPath, Result(..), Value(..))
33+import Data.Aeson.Types.Internal (IResult(..), JSONPath, Result(..), Value(..))
3434import Data.Attoparsec.ByteString.Char8 (Parser, char, endOfInput, scientific,
3535 skipSpace, string)
3636import Data.Bits ((.|.), shiftL)
@@ -287,27 +287,27 @@ decodeWith p to s =
287287decodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString
288288 -> Maybe a
289289decodeStrictWith p to s =
290− case either (Error []) to (A.parseOnly p s) of
290+ case either Error to (A.parseOnly p s) of
291291 Success a -> Just a
292292 _ -> Nothing
293293{-# INLINE decodeStrictWith #-}
294294
295−eitherDecodeWith :: Parser Value -> (Value -> Result a) -> L.ByteString
295+eitherDecodeWith :: Parser Value -> (Value -> IResult a) -> L.ByteString
296296 -> Either (JSONPath, String) a
297297eitherDecodeWith p to s =
298298 case L.parse p s of
299299 L.Done _ v -> case to v of
300− Success a -> Right a
301− Error path msg -> Left (path, msg)
300+ ISuccess a -> Right a
301+ IError path msg -> Left (path, msg)
302302 L.Fail _ _ msg -> Left ([], msg)
303303{-# INLINE eitherDecodeWith #-}
304304
305−eitherDecodeStrictWith :: Parser Value -> (Value -> Result a) -> B.ByteString
305+eitherDecodeStrictWith :: Parser Value -> (Value -> IResult a) -> B.ByteString
306306 -> Either (JSONPath, String) a
307307eitherDecodeStrictWith p to s =
308− case either (Error []) to (A.parseOnly p s) of
309− Success a -> Right a
310− Error path msg -> Left (path, msg)
308+ case either (IError []) to (A.parseOnly p s) of
309+ ISuccess a -> Right a
310+ IError path msg -> Left (path, msg)
311311{-# INLINE eitherDecodeStrictWith #-}
312312
313313-- $lazy
Data/Aeson/Types.hs+1−2
@@ -35,13 +35,12 @@ module Data.Aeson.Types
3535 , ToJSON(..)
3636 , KeyValue(..)
3737 , modifyFailure
38− , JSONPath
39− , formatError
4038
4139 -- ** Generic JSON classes
4240 , GFromJSON(..)
4341 , GToJSON(..)
4442 , genericToJSON
43+ , genericToEncoding
4544 , genericParseJSON
4645
4746 -- * Inspecting @'Value's@
Data/Aeson/Types/Instances.hs+7
@@ -29,6 +29,7 @@ module Data.Aeson.Types.Instances
2929 , GFromJSON(..)
3030 , GToJSON(..)
3131 , genericToJSON
32+ , genericToEncoding
3233 , genericParseJSON
3334
3435 -- * Types
@@ -44,6 +45,7 @@ module Data.Aeson.Types.Instances
4445
4546 -- * Functions
4647 , fromJSON
48+ , ifromJSON
4749 , (.:)
4850 , (.:?)
4951 , (.!=)
@@ -1457,6 +1459,11 @@ fromJSON :: (FromJSON a) => Value -> Result a
14571459fromJSON = parse parseJSON
14581460{-# INLINE fromJSON #-}
14591461
1462+-- | Convert a value from JSON, failing if the types do not match.
1463+ifromJSON :: (FromJSON a) => Value -> IResult a
1464+ifromJSON = iparse parseJSON
1465+{-# INLINE ifromJSON #-}
1466+
14601467-- | Retrieve the value associated with the given key of an 'Object'.
14611468-- The result is 'empty' if the key is not present or the value cannot
14621469-- be converted to the desired type.
Data/Aeson/Types/Internal.hs+68−15
@@ -26,8 +26,10 @@ module Data.Aeson.Types.Internal
2626 -- * Type conversion
2727 , Parser
2828 , Result(..)
29+ , IResult(..)
2930 , JSONPathElement(..)
3031 , JSONPath
32+ , iparse
3133 , parse
3234 , parseEither
3335 , parseMaybe
@@ -80,8 +82,13 @@ data JSONPathElement = Key Text
8082 deriving (Eq, Show, Typeable)
8183type JSONPath = [JSONPathElement]
8284
85+-- | The internal result of running a 'Parser'.
86+data IResult a = IError JSONPath String
87+ | ISuccess a
88+ deriving (Eq, Show, Typeable)
89+
8390-- | The result of running a 'Parser'.
84−data Result a = Error JSONPath String
91+data Result a = Error String
8592 | Success a
8693 deriving (Eq, Show, Typeable)
8794
@@ -89,28 +96,57 @@ instance NFData JSONPathElement where
8996 rnf (Key t) = rnf t
9097 rnf (Index i) = rnf i
9198
99+instance (NFData a) => NFData (IResult a) where
100+ rnf (ISuccess a) = rnf a
101+ rnf (IError path err) = rnf path `seq` rnf err
102+
92103instance (NFData a) => NFData (Result a) where
93104 rnf (Success a) = rnf a
94− rnf (Error path err) = rnf path `seq` rnf err
105+ rnf (Error err) = rnf err
106+
107+instance Functor IResult where
108+ fmap f (ISuccess a) = ISuccess (f a)
109+ fmap _ (IError path err) = IError path err
110+ {-# INLINE fmap #-}
95111
96112instance Functor Result where
97113 fmap f (Success a) = Success (f a)
98− fmap _ (Error path err) = Error path err
114+ fmap _ (Error err) = Error err
99115 {-# INLINE fmap #-}
100116
117+instance Monad IResult where
118+ return = ISuccess
119+ {-# INLINE return #-}
120+ ISuccess a >>= k = k a
121+ IError path err >>= _ = IError path err
122+ {-# INLINE (>>=) #-}
123+
101124instance Monad Result where
102125 return = Success
103126 {-# INLINE return #-}
104− Success a >>= k = k a
105− Error path err >>= _ = Error path err
127+ Success a >>= k = k a
128+ Error err >>= _ = Error err
106129 {-# INLINE (>>=) #-}
107130
131+instance Applicative IResult where
132+ pure = return
133+ {-# INLINE pure #-}
134+ (<*>) = ap
135+ {-# INLINE (<*>) #-}
136+
108137instance Applicative Result where
109138 pure = return
110139 {-# INLINE pure #-}
111140 (<*>) = ap
112141 {-# INLINE (<*>) #-}
113142
143+instance MonadPlus IResult where
144+ mzero = fail "mzero"
145+ {-# INLINE mzero #-}
146+ mplus a@(ISuccess _) _ = a
147+ mplus _ b = b
148+ {-# INLINE mplus #-}
149+
114150instance MonadPlus Result where
115151 mzero = fail "mzero"
116152 {-# INLINE mzero #-}
@@ -118,12 +154,24 @@ instance MonadPlus Result where
118154 mplus _ b = b
119155 {-# INLINE mplus #-}
120156
157+instance Alternative IResult where
158+ empty = mzero
159+ {-# INLINE empty #-}
160+ (<|>) = mplus
161+ {-# INLINE (<|>) #-}
162+
121163instance Alternative Result where
122164 empty = mzero
123165 {-# INLINE empty #-}
124166 (<|>) = mplus
125167 {-# INLINE (<|>) #-}
126168
169+instance Monoid (IResult a) where
170+ mempty = fail "mempty"
171+ {-# INLINE mempty #-}
172+ mappend = mplus
173+ {-# INLINE mappend #-}
174+
127175instance Monoid (Result a) where
128176 mempty = fail "mempty"
129177 {-# INLINE mempty #-}
@@ -288,9 +336,14 @@ emptyObject = Object H.empty
288336
289337-- | Run a 'Parser'.
290338parse :: (a -> Parser b) -> a -> Result b
291−parse m v = runParser (m v) [] Error Success
339+parse m v = runParser (m v) [] (const Error) Success
292340{-# INLINE parse #-}
293341
342+-- | Run a 'Parser'.
343+iparse :: (a -> Parser b) -> a -> IResult b
344+iparse m v = runParser (m v) [] IError ISuccess
345+{-# INLINE iparse #-}
346+
294347-- | Run a 'Parser' with a 'Maybe' result type.
295348parseMaybe :: (a -> Parser b) -> a -> Maybe b
296349parseMaybe m v = runParser (m v) [] (\_ _ -> Nothing) Just
@@ -298,18 +351,18 @@ parseMaybe m v = runParser (m v) [] (\_ _ -> Nothing) Just
298351
299352-- | Run a 'Parser' with an 'Either' result type.
300353parseEither :: (a -> Parser b) -> a -> Either String b
301−parseEither m v = runParser (m v) [] (\path msg -> Left $ formatError path msg) Right
354+parseEither m v = runParser (m v) [] onError Right
355+ where onError path msg = Left (formatError path msg)
302356{-# INLINE parseEither #-}
303357
304−-- | Annotate error message with JSON Path error location
358+-- | Annotate an error message with a
359+-- <http://goessner.net/articles/JsonPath/ JSONPath> error location.
305360formatError :: JSONPath -> String -> String
306−formatError path msg = "Error in " ++ (formatJSONPath path) ++ ": " ++ msg
307− where formatJSONPath :: JSONPath -> String
308− formatJSONPath p = format "$" p
309− where format :: String -> JSONPath -> String
310− format prefix [] = prefix
311− format prefix (Index idx:parts) = format (prefix ++ "[" ++ show idx ++ "]") parts
312− format prefix (Key key:parts) = format (prefix ++ "." ++ unpack key) parts
361+formatError path msg = "Error in " ++ (format "$" path) ++ ": " ++ msg
362+ where
363+ format pfx [] = pfx
364+ format pfx (Index idx:parts) = format (pfx ++ "[" ++ show idx ++ "]") parts
365+ format pfx (Key key:parts) = format (pfx ++ "." ++ unpack key) parts
313366
314367-- | A key\/value pair for an 'Object'.
315368type Pair = (Text, Value)
aeson.cabal+1
@@ -87,6 +87,7 @@ library
8787 exposed-modules:
8888 Data.Aeson
8989 Data.Aeson.Encode
90+ Data.Aeson.Internal
9091 Data.Aeson.Parser
9192 Data.Aeson.Types
9293 Data.Aeson.TH
tests/Properties.hs+12−11
@@ -3,6 +3,7 @@
33import Control.Monad (forM)
44import Data.Aeson (eitherDecode)
55import Data.Aeson.Encode
6+import Data.Aeson.Internal (IResult(..), formatError, ifromJSON, iparse)
67import Data.Aeson.Parser (value)
78import Data.Aeson.Types
89import Data.ByteString.Builder (toLazyByteString)
@@ -49,29 +50,29 @@ encodeInteger i = encode i === L.pack (show i)
4950toParseJSON :: (Arbitrary a, Eq a, Show a) =>
5051 (Value -> Parser a) -> (a -> Value) -> a -> Property
5152toParseJSON parsejson tojson x =
52− case parse parsejson . tojson $ x of
53− Error path msg -> failure "parse" (formatError path msg) x
54− Success x' -> x === x'
53+ case iparse parsejson . tojson $ x of
54+ IError path msg -> failure "parse" (formatError path msg) x
55+ ISuccess x' -> x === x'
5556
5657roundTrip :: (FromJSON a, ToJSON a, Show a) =>
5758 (a -> a -> Property) -> a -> a -> Property
5859roundTrip eq _ i =
59− case fmap fromJSON . L.parse value . encode . toJSON $ i of
60− L.Done _ (Success v) -> v `eq` i
61− L.Done _ (Error path err) -> failure "fromJSON" (formatError path err) i
62− L.Fail _ _ err -> failure "parse" err i
60+ case fmap ifromJSON . L.parse value . encode . toJSON $ i of
61+ L.Done _ (ISuccess v) -> v `eq` i
62+ L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
63+ L.Fail _ _ err -> failure "parse" err i
6364
6465roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
6566roundTripEq x y = roundTrip (===) x y
6667
6768toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
68−toFromJSON x = case fromJSON (toJSON x) of
69− Error path err -> failure "fromJSON" (formatError path err) x
70− Success x' -> x === x'
69+toFromJSON x = case ifromJSON (toJSON x) of
70+ IError path err -> failure "fromJSON" (formatError path err) x
71+ ISuccess x' -> x === x'
7172
7273modifyFailureProp :: String -> String -> Bool
7374modifyFailureProp orig added =
74− result == Error [] (added ++ orig)
75+ result == Error (added ++ orig)
7576 where
7677 parser = const $ modifyFailure (added ++) $ fail orig
7778 result :: Result ()
tests/Properties/ZonedTime.hs+6−4
@@ -2,12 +2,13 @@
22
33module Properties.ZonedTime (zonedTimeToJSON) where
44
5+import Data.Aeson.Internal (IResult(..), formatError, ifromJSON)
56import Data.Aeson.Types
67import Data.Time
78import Instances ()
89import Test.Framework (Test, testGroup)
910import Test.Framework.Providers.QuickCheck2 (testProperty)
10−import Test.QuickCheck ((===))
11+import Test.QuickCheck ((===), Property)
1112import Types
1213
1314#if !MIN_VERSION_time(1,5,0)
@@ -42,9 +43,10 @@ zonedTimeToJSON = testGroup "ZonedTime" [
4243 test :: String -> (ZonedTime -> ZonedTime) -> Test
4344 test format f = testProperty format go
4445 where
45− go t = case fromJSON (toJSON' format t) of
46− Error path msg -> failure "fromJSON" (formatError path msg) t
47− Success t' -> t' === f t
46+ go :: ZonedTime -> Property
47+ go t = case ifromJSON (toJSON' format t) of
48+ IError path msg -> failure "fromJSON" (formatError path msg) t
49+ ISuccess t' -> t' === f t
4850
4951clearTimeZone :: ZonedTime -> ZonedTime
5052clearTimeZone t = t { zonedTimeZone = TimeZone 0 False "" }