aeson-match-qq 1.4.3 → 1.5.0
raw patch · 6 files changed
+115/−42 lines, 6 files
Files
- CHANGELOG.markdown +5/−0
- aeson-match-qq.cabal +1/−1
- src/Aeson/Match/QQ.hs +17/−16
- src/Aeson/Match/QQ/Internal/Match.hs +46/−18
- src/Aeson/Match/QQ/Internal/Value.hs +7/−0
- test/Aeson/Match/QQSpec.hs +39/−7
CHANGELOG.markdown view
@@ -1,3 +1,8 @@+1.5.0+=====++ * Streamlined API.+ 1.4.3 =====
aeson-match-qq.cabal view
@@ -5,7 +5,7 @@ -- see: https://github.com/sol/hpack name: aeson-match-qq-version: 1.4.3+version: 1.5.0 synopsis: Declarative JSON matchers. description: See README.markdown category: Web
src/Aeson/Match/QQ.hs view
@@ -1,5 +1,14 @@ module Aeson.Match.QQ- ( Value(..)+ ( match+ , qq++ , Error(..)+ , Mismatch(..)+ , MissingPathElem(..)+ , ExtraArrayValues(..)+ , ExtraObjectValues(..)++ , Value(..) , Array , Object , Box(..)@@ -8,14 +17,6 @@ , Nullable(..) , Path , PathElem(..)- , parse- , qq- , match- , mismatch- , mistype- , missingPathElem- , extraArrayValues- , extraObjectValues ) where import Data.String (IsString(..))@@ -24,14 +25,14 @@ import Language.Haskell.TH.Syntax (Lift(..)) import Aeson.Match.QQ.Internal.Match- ( Path+ ( match+ , Error(..)+ , Mismatch(..)+ , MissingPathElem(..)+ , ExtraArrayValues(..)+ , ExtraObjectValues(..)+ , Path , PathElem(..)- , match- , mismatch- , mistype- , missingPathElem- , extraArrayValues- , extraObjectValues ) import Aeson.Match.QQ.Internal.Parse (parse) import Aeson.Match.QQ.Internal.Value
src/Aeson/Match/QQ/Internal/Match.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE ApplicativeDo #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE LambdaCase #-}@@ -5,16 +6,31 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ViewPatterns #-}-module Aeson.Match.QQ.Internal.Match where+module Aeson.Match.QQ.Internal.Match+ ( match+ , Error(..)+ , Mismatch(..)+ , MissingPathElem(..)+ , ExtraArrayValues(..)+ , ExtraObjectValues(..)+ , Path+ , PathElem(..)+ ) where import Control.Applicative (liftA2) import Control.Monad (unless) import Data.Aeson ((.=)) import qualified Data.Aeson as Aeson+#if MIN_VERSION_aeson(2,0,0) import qualified Data.Aeson.KeyMap as Aeson (toHashMapText)+#endif import Data.Bool (bool) import qualified Data.CaseInsensitive as CI-import Data.Either.Validation (Validation(..), eitherToValidation)+import Data.Either.Validation+ ( Validation(..)+ , eitherToValidation+ , validationToEither+ ) import Data.Foldable (for_, toList) import Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HashMap@@ -37,12 +53,17 @@ ) +-- | Test if a matcher matches a 'Aeson.Value'. match :: Value Aeson.Value+ -- ^ A 'qq`-created 'Value' -> Aeson.Value- -> Validation (NonEmpty VE) (HashMap Text Aeson.Value)-match =- go []+ -- ^ A 'Value' from aeson+ -> Either (NonEmpty Error) (HashMap Text Aeson.Value)+ -- ^ Either a non-empty list of errors, or a mapping+ -- from _holes to their values.+match matcher0 given0 =+ validationToEither (go [] matcher0 given0) where go path matcher given = do let mismatched = mismatch (reverse path) matcher given@@ -103,7 +124,14 @@ (ArrayUO _, _) -> do mistyped pure mempty- (Object Box {knownValues, extendable}, Aeson.Object (Aeson.toHashMapText -> o)) ->+ ( Object Box {knownValues, extendable}+#if MIN_VERSION_aeson(2,0,0)+ , Aeson.Object (Aeson.toHashMapText -> o)+#else+ , Aeson.Object o+#endif++ ) -> let fold f = HashMap.foldrWithKey (\k v a -> liftA2 HashMap.union a (f k v)) (pure mempty) extraValues =@@ -135,11 +163,11 @@ (_, _) -> False matchArrayUO- :: Validation (NonEmpty VE) (HashMap Text Aeson.Value)+ :: Validation (NonEmpty Error) (HashMap Text Aeson.Value) -> Path -> Box (Vector (Value Aeson.Value)) -> Vector Aeson.Value- -> Validation (NonEmpty VE) (HashMap Text Aeson.Value)+ -> Validation (NonEmpty Error) (HashMap Text Aeson.Value) matchArrayUO mismatched path Box {knownValues, extendable} xs = do -- Collect possible indices in `xs` for each position in `knownValues`. let indices = map (collectMatchingIndices (toList xs)) (toList knownValues)@@ -169,10 +197,10 @@ where matchingIndex i x = case match knownValue x of- Success vs ->- Just (i, vs)- Failure _ ->+ Left _ -> Nothing+ Right vs ->+ Just (i, vs) allIndicesAssignments = map (map unI) . cleanUp . go Set.empty where go _ [] = [[]]@@ -192,23 +220,23 @@ I (a, _) `compare` I (b, _) = a `compare` b -mismatch :: Path -> Value Aeson.Value -> Aeson.Value -> Validation (NonEmpty VE) a+mismatch :: Path -> Value Aeson.Value -> Aeson.Value -> Validation (NonEmpty Error) a mismatch path matcher given = throwE (Mismatch MkMismatch {..}) -mistype :: Path -> Value Aeson.Value -> Aeson.Value -> Validation (NonEmpty VE) a+mistype :: Path -> Value Aeson.Value -> Aeson.Value -> Validation (NonEmpty Error) a mistype path matcher given = throwE (Mistype MkMismatch {..}) -missingPathElem :: Path -> PathElem -> Validation (NonEmpty VE) a+missingPathElem :: Path -> PathElem -> Validation (NonEmpty Error) a missingPathElem path missing = throwE (MissingPathElem MkMissingPathElem {..}) -extraArrayValues :: Path -> Vector Aeson.Value -> Validation (NonEmpty VE) a+extraArrayValues :: Path -> Vector Aeson.Value -> Validation (NonEmpty Error) a extraArrayValues path values = throwE (ExtraArrayValues MkExtraArrayValues {..}) -extraObjectValues :: Path -> HashMap Text Aeson.Value -> Validation (NonEmpty VE) a+extraObjectValues :: Path -> HashMap Text Aeson.Value -> Validation (NonEmpty Error) a extraObjectValues path values = throwE (ExtraObjectValues MkExtraObjectValues {..}) @@ -216,7 +244,7 @@ throwE = eitherToValidation . Left . pure -data VE+data Error = Mismatch Mismatch | Mistype Mismatch | MissingPathElem MissingPathElem@@ -224,7 +252,7 @@ | ExtraObjectValues ExtraObjectValues deriving (Show, Eq) -instance Aeson.ToJSON VE where+instance Aeson.ToJSON Error where toJSON = Aeson.object . \case Mismatch v ->
src/Aeson/Match/QQ/Internal/Value.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE DeriveLift #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-}@@ -20,7 +21,9 @@ import Data.Aeson ((.=)) import qualified Data.Aeson as Aeson+#if MIN_VERSION_aeson(2,0,0) import qualified Data.Aeson.KeyMap as Aeson (toHashMapText)+#endif import qualified Data.Aeson.Encoding.Internal as Aeson (encodingToLazyByteString) import Data.CaseInsensitive (CI) import qualified Data.CaseInsensitive as CI@@ -212,5 +215,9 @@ String n Aeson.Array xs -> Array Box {knownValues = fmap embed xs, extendable = False}+#if MIN_VERSION_aeson(2,0,0) Aeson.Object (Aeson.toHashMapText -> o) ->+#else+ Aeson.Object o ->+#endif Object Box {knownValues = fmap embed o, extendable = False}
test/Aeson/Match/QQSpec.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE GeneralisedNewtypeDeriving #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-}@@ -6,6 +7,7 @@ import qualified Data.Aeson as Aeson import Data.Aeson.QQ (aesonQQ)+import Data.List.NonEmpty (NonEmpty) import qualified Data.HashMap.Strict as HashMap import Test.Hspec @@ -117,7 +119,11 @@ it "paths" $ do match [qq| {foo: {bar: {baz: [1, 4]}}} |] [aesonQQ| {foo: {bar: {baz: [1, 7]}}} |] `shouldBe`- mismatch ["foo", "bar", "baz", Idx 1] [qq| 4 |] [aesonQQ| 7 |]+ throwE (Mismatch MkMismatch+ { path = ["foo", "bar", "baz", Idx 1]+ , matcher = [qq| 4 |]+ , given = [aesonQQ| 7 |]+ }) it "named holes" $ do match [qq| {foo: _hole} |] [aesonQQ| {foo: {bar: {baz: [1, 4]}}} |] `shouldBe`@@ -133,9 +139,15 @@ -- https://github.com/supki/aeson-match-qq/issues/7 it "#7" $ do match [qq| {foo: _} |] [aesonQQ| {} |] `shouldBe`- missingPathElem [] "foo"+ throwE (MissingPathElem MkMissingPathElem+ { path = []+ , missing = "foo"+ }) match [qq| [_] |] [aesonQQ| [] |] `shouldBe`- missingPathElem [] (Idx 0)+ throwE (MissingPathElem MkMissingPathElem+ { path = []+ , missing = Idx 0+ }) -- https://github.com/supki/aeson-match-qq/issues/10 it "#10" $ do@@ -160,16 +172,32 @@ it "#18" $ do -- string ~ string match [qq| "foo" |] [aesonQQ| "bar" |] `shouldBe`- mismatch [] (String "foo") (Aeson.String "bar")+ throwE (Mismatch MkMismatch+ { path = []+ , matcher = String "foo"+ , given = Aeson.String "bar"+ }) -- string !~ number match [qq| "foo" |] [aesonQQ| 4 |] `shouldBe`- mistype [] (String "foo") (Aeson.Number 4)+ throwE (Mistype MkMismatch+ { path = []+ , matcher = String "foo"+ , given = Aeson.Number 4+ }) -- string !~ null match [qq| "foo" |] [aesonQQ| null |] `shouldBe`- mistype [] (String "foo") Aeson.Null+ throwE (Mistype MkMismatch+ { path = []+ , matcher = String "foo"+ , given = Aeson.Null+ }) -- null !~ number match [qq| null |] [aesonQQ| 4 |] `shouldBe`- mistype [] Null (Aeson.Number 4)+ throwE (Mistype MkMismatch+ { path = []+ , matcher = Null+ , given = Aeson.Number 4+ }) newtype ToEncoding a = ToEncoding { unToEncoding :: a } deriving (Show, Eq, Num)@@ -187,3 +215,7 @@ shouldNotMatch :: Value Aeson.Value -> Aeson.Value -> Expectation shouldNotMatch a b = match a b `shouldNotBe` pure mempty++throwE :: e -> Either (NonEmpty e) a+throwE =+ Left . pure