aeson-jsonpath 0.4.0.0 → 0.4.1.0
raw patch · 11 files changed
+251/−29 lines, 11 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Aeson.JSONPath: dumpQuery :: Query -> Text
+ Data.Aeson.JSONPath.Query: dumpQuery :: Query -> Text
Files
- CHANGELOG.md +4/−0
- README.md +1/−1
- aeson-jsonpath.cabal +13/−17
- jsonpath-compliance-test-suite/cts.json +9/−5
- src/Data/Aeson/JSONPath.hs +4/−1
- src/Data/Aeson/JSONPath/Query.hs +2/−0
- src/Data/Aeson/JSONPath/Query/DumpQuery.hs +122/−0
- src/Data/Aeson/JSONPath/Types/Filter.hs +1/−0
- test/compliance/ComplianceSpec.hs +1/−5
- test/spec/DumpQuerySpec.hs +92/−0
- test/spec/Main.hs +2/−0
CHANGELOG.md view
@@ -2,6 +2,10 @@ All notable changes to this package are documented in this file. This project adheres to [Haskell PVP](https://pvp.haskell.org/) versioning. +## 0.4.1.0++- #85, Implement `dumpQuery` to dump JSONPath query+ ## 0.4.0.0 - #82, Implement `search()` function from function extensions
README.md view
@@ -1,6 +1,6 @@ # aeson-jsonpath -[](https://github.com/taimoorzaeem/aeson-jsonpath/actions/workflows/build.yml) [](https://hackage.haskell.org/package/aeson-jsonpath) [](https://www.patreon.com/taimoorzaeem) [](https://github.com/taimoorzaeem/aeson-jsonpath/actions/workflows/compliance.yml)+[](https://github.com/taimoorzaeem/aeson-jsonpath/actions/workflows/build.yml) [](https://hackage.haskell.org/package/aeson-jsonpath) [](https://github.com/taimoorzaeem/aeson-jsonpath/actions/workflows/compliance.yml) Run [RFC 9535](https://www.rfc-editor.org/rfc/rfc9535) compliant JSONPath queries on [Data.Aeson](https://hackage.haskell.org/package/aeson).
aeson-jsonpath.cabal view
@@ -1,5 +1,6 @@+cabal-version: 3.0 name: aeson-jsonpath-version: 0.4.0.0+version: 0.4.1.0 synopsis: Parse and run JSONPath queries on Aeson documents description: RFC 9535 compliant JSONPath parsing and querying package. JSONPath is similar to XPath for querying@@ -15,7 +16,6 @@ extra-source-files: CHANGELOG.md extra-doc-files: README.md data-files: jsonpath-compliance-test-suite/cts.json-cabal-version: 1.18 tested-with: GHC == 9.4.8@@ -29,11 +29,16 @@ type: git location: https://github.com/taimoorzaeem/aeson-jsonpath -library+common common-fields default-language: Haskell2010 default-extensions: OverloadedStrings NoImplicitPrelude QuasiQuotes++ ghc-options: -Wall -Wunused-packages++library+ import: common-fields hs-source-dirs: src exposed-modules: Data.Aeson.JSONPath Data.Aeson.JSONPath.Parser@@ -48,6 +53,7 @@ Data.Aeson.JSONPath.Query.Segment Data.Aeson.JSONPath.Query.Selector Data.Aeson.JSONPath.Query.Filter+ Data.Aeson.JSONPath.Query.DumpQuery Data.Aeson.JSONPath.Types.Query Data.Aeson.JSONPath.Types.Segment Data.Aeson.JSONPath.Types.Selector@@ -61,19 +67,15 @@ , text >= 1.2.2 && < 2.2 , vector >= 0.11 && < 0.14 - ghc-options: -Wall -Wunused-packages- test-suite spec+ import: common-fields type: exitcode-stdio-1.0- default-language: Haskell2010- default-extensions: OverloadedStrings- NoImplicitPrelude- QuasiQuotes hs-source-dirs: test/spec main-is: Main.hs other-modules: ParserSpec QuerySpec LocatedSpec+ DumpQuerySpec build-depends: aeson >= 2.0.3 && < 2.5 , aeson-jsonpath , base >= 4.9 && < 4.23@@ -81,18 +83,14 @@ , parsec >= 3.1.11 && < 3.2 , vector >= 0.11 && < 0.14 - ghc-options: -Wall -Wunused-packages- test-suite compliance+ import: common-fields type: exitcode-stdio-1.0- default-language: Haskell2010- default-extensions: OverloadedStrings- NoImplicitPrelude- QuasiQuotes hs-source-dirs: test/compliance main-is: Main.hs other-modules: ComplianceSpec Paths_aeson_jsonpath+ autogen-modules: Paths_aeson_jsonpath build-depends: aeson >= 2.0.3 && < 2.5 , aeson-jsonpath , base >= 4.9 && < 4.23@@ -100,5 +98,3 @@ , parsec >= 3.1.11 && < 3.2 , text >= 1.2.2 && < 2.2 , vector >= 0.11 && < 0.14-- ghc-options: -Wall -Wunused-packages
jsonpath-compliance-test-suite/cts.json view
@@ -8020,7 +8020,8 @@ ], "tags": [ "function",- "length"+ "length",+ "value" ] }, {@@ -8047,7 +8048,8 @@ ], "tags": [ "function",- "length"+ "length",+ "value" ] }, {@@ -8383,7 +8385,8 @@ ], "tags": [ "function",- "match"+ "match",+ "value" ] }, {@@ -8903,7 +8906,8 @@ ], "tags": [ "function",- "search"+ "search",+ "value" ] }, {@@ -9857,8 +9861,8 @@ "$[1]" ], "tags": [+ "count", "function",- "search", "whitespace" ] },
src/Data/Aeson/JSONPath.hs view
@@ -20,6 +20,9 @@ , queryLocated , queryLocatedQQ + -- * Dump Query+ , dumpQuery+ -- * QuasiQuoter , jsonPath )@@ -33,7 +36,7 @@ import Language.Haskell.TH.Syntax (lift) import Data.Aeson.JSONPath.Parser (pQuery)-import Data.Aeson.JSONPath.Query (qQuery, qQueryLocated)+import Data.Aeson.JSONPath.Query (qQuery, qQueryLocated, dumpQuery) import Data.Aeson.JSONPath.Types
src/Data/Aeson/JSONPath/Query.hs view
@@ -12,7 +12,9 @@ module Data.Aeson.JSONPath.Query ( module Data.Aeson.JSONPath.Query.Query+ , module Data.Aeson.JSONPath.Query.DumpQuery ) where import Data.Aeson.JSONPath.Query.Query+import Data.Aeson.JSONPath.Query.DumpQuery
+ src/Data/Aeson/JSONPath/Query/DumpQuery.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+module Data.Aeson.JSONPath.Query.DumpQuery+ ( dumpQuery )+ where++import Data.Text (Text)++import qualified Data.Text as T++import Data.Aeson.JSONPath.Types++import Prelude++-- | Dump JSONPath Query+dumpQuery :: Query -> Text+dumpQuery Query{..} = case queryType of+ Root -> "$" <> T.concat (map dumpQuerySegment querySegments)+ Current -> "@" <> T.concat (map dumpQuerySegment querySegments)++-- |+dumpQuerySegment :: QuerySegment Query -> Text+dumpQuerySegment QuerySegment{..} = case segmentType of+ Child -> dumpSegment True segment+ Descendant -> ".." <> dumpSegment False segment++-- |+dumpSegment :: Bool -> Segment Query -> Text+dumpSegment isChild = \case+ Bracketed selectors -> "[" <> T.intercalate "," (map dumpSelector selectors) <> "]"+ Dotted txt -> "." <> txt+ WildcardSegment -> if isChild then ".*" else "*"++-- |+dumpSelector :: Selector Query -> Text+dumpSelector = \case+ Name txt -> "'" <> txt <> "'"+ Index i -> T.pack (show i)+ ArraySlice (start, end, step) ->+ case (start, end) of+ (Just start', Just end') -> T.pack (show start' <> ":" <> show end' <> ":" <> show step)+ (Just start', Nothing) -> T.pack (show start' <> "::" <> show step)+ (Nothing, Just end') -> T.pack (":" <> show end' <> ":" <> show step)+ (Nothing, Nothing) -> T.pack ("::"<> show step)+ Filter logicalOrExpr -> "?" <> dumpLogicalOrExpr logicalOrExpr+ WildcardSelector -> "*"++-- |+dumpLogicalOrExpr :: LogicalOrExpr Query -> Text+dumpLogicalOrExpr (LogicalOr andExprs) = T.intercalate " || " (map dumpLogicalAndExpr andExprs)++-- |+dumpLogicalAndExpr :: LogicalAndExpr Query -> Text+dumpLogicalAndExpr (LogicalAnd basicExprs) = T.intercalate " || " (map dumpBasicExpr basicExprs)++-- |+dumpBasicExpr :: BasicExpr Query -> Text+dumpBasicExpr = \case+ Paren orExpr -> "(" <> dumpLogicalOrExpr orExpr <> ")"+ NotParen orExpr -> "!(" <> dumpLogicalOrExpr orExpr <> ")"+ Test testExpr -> dumpTestExpr testExpr+ NotTest testExpr -> "!" <> dumpTestExpr testExpr+ Comparison expr -> dumpComparisonExpr expr++-- |+dumpTestExpr :: TestExpr Query -> Text+dumpTestExpr = \case+ FilterQuery q -> dumpQuery q+ TestFunc funcExpr -> dumpFunctionExpr funcExpr++-- |+dumpComparisonExpr :: ComparisonExpr -> Text+dumpComparisonExpr (Comp compLeft op compRight) = dumpComparable compLeft <> " " <> dumpComparisonOp op <> " " <> dumpComparable compRight++-- |+dumpComparisonOp :: ComparisonOp -> Text+dumpComparisonOp = \case+ Less -> "<"+ LessOrEqual -> "<="+ Greater -> ">"+ GreaterOrEqual -> ">="+ Equal -> "=="+ NotEqual -> "!="++-- |+dumpComparable :: Comparable -> Text+dumpComparable = \case+ CompLit lit -> dumpLiteral lit+ CompSQ sq -> dumpSingularQ sq++-- |+dumpLiteral :: Literal -> Text+dumpLiteral = \case+ LitString txt -> "'" <> txt <> "'"+ LitNum s -> T.pack $ show s+ LitBool b -> T.toLower $ T.pack $ show b+ LitNull -> "null"++-- |+dumpSingularQ :: SingularQuery -> Text+dumpSingularQ SingularQuery{..} = case singularQueryType of+ RootSQ -> "$" <> T.concat (map dumpSingularQSegment singularQuerySegments)+ CurrentSQ -> "@" <> T.concat (map dumpSingularQSegment singularQuerySegments)++-- |+dumpSingularQSegment :: SingularQuerySegment -> Text+dumpSingularQSegment = \case+ NameSQSeg txt -> "." <> txt+ IndexSQSeg i -> "[" <> T.pack (show i) <> "]"++-- |+dumpFunctionExpr :: FunctionExpr Query -> Text+dumpFunctionExpr FunctionSearch{..} =+ "search(" <> dumpFunctionArg functionArg1 <> ", " <> dumpFunctionArg functionArg2 <> ")"++-- |+dumpFunctionArg :: FunctionArg Query -> Text+dumpFunctionArg = \case+ ArgLit lit -> dumpLiteral lit+ ArgQuery q -> dumpQuery q+ ArgLogicExpr e -> dumpLogicalOrExpr e+ ArgFuncExpr f -> dumpFunctionExpr f
src/Data/Aeson/JSONPath/Types/Filter.hs view
@@ -101,6 +101,7 @@ -- | data FunctionExpr a =+ -- TODO: Remove functionName, not needed because Type Constructor already tells us that FunctionSearch { functionName :: FunctionName , functionArg1 :: FunctionArg a
test/compliance/ComplianceSpec.hs view
@@ -63,21 +63,17 @@ mapM_ runTestCase tests -- Skip 5 tests because these tests are not according to Haskell TDFA regular expression.--- Skip 2 tests that include other functions like "value()" or "count()" which are not yet--- implemented. TODO: These can be removed, if tagged accordingly searchFunctionTestsToBeSkipped :: [String] searchFunctionTestsToBeSkipped = [ "functions, search, filter, search function, unicode char class, uppercase" , "functions, search, filter, search function, unicode char class negated, uppercase" , "functions, search, dot matcher on \\u2028" , "functions, search, dot matcher on \\u2029"- , "functions, search, arg is a function expression"+ , "functions, search, arg is a function expression" -- skipped bcz it has "value()" function, which is not implemented yet , "functions, search, escaped right square bracket"- , "whitespace, functions, space between arg and parenthesis" ] runTestCase :: TestCase -> SpecWith ()--- skip function extension tests runTestCase tc@TestCase{tags=Just xs, ..} = if ("function" `elem` xs && not ("search" `elem` xs)) || (name `elem` searchFunctionTestsToBeSkipped) then xit name pending
+ test/spec/DumpQuerySpec.hs view
@@ -0,0 +1,92 @@+module DumpQuerySpec+ ( spec )+ where++import Test.Hspec++import Data.Aeson.JSONPath (jsonPath, dumpQuery)++import Prelude++spec :: Spec+spec = do+ describe "test query dump" $ do+ it "should dump the function query" $+ dumpQuery [jsonPath|$.store.books[?search(@.author, 'Diamond|Herman')]|]+ `shouldBe` "$.store.books[?search(@.author, 'Diamond|Herman')]"++ it "should dump filter test query" $+ dumpQuery [jsonPath|$.store.books[?@.not_here]|]+ `shouldBe` "$.store.books[?@.not_here]"++ it "should dump filter comparison query" $+ dumpQuery [jsonPath|$.store.books[?@.price < 20]|]+ `shouldBe` "$.store.books[?@.price < 20.0]"++ it "should dump root" $+ dumpQuery [jsonPath|$|]+ `shouldBe` "$"++ it "should dump dotted segment" $+ dumpQuery [jsonPath|$.store|]+ `shouldBe` "$.store"++ it "should dump bracketed segment" $+ dumpQuery [jsonPath|$['store']|]+ `shouldBe` "$['store']"++ it "should dump dotted segments" $+ dumpQuery [jsonPath|$.store.books|]+ `shouldBe` "$.store.books"++ it "should dump index selector" $+ dumpQuery [jsonPath|$.store.books[0]|]+ `shouldBe` "$.store.books[0]"++ it "should dump -ve index" $+ dumpQuery [jsonPath|$.store.books[-4]|]+ `shouldBe` "$.store.books[-4]"++ it "should dump multiple singular query segment" $+ dumpQuery [jsonPath|$.store.books[0,2]|]+ `shouldBe` "$.store.books[0,2]"++ it "should dump slice" $+ dumpQuery [jsonPath|$.store.books[1:3]|]+ `shouldBe` "$.store.books[1:3:1]"++ it "should dump multiple bracketed" $+ dumpQuery [jsonPath|$.store.books[1:3,0,1]|]+ `shouldBe` "$.store.books[1:3:1,0,1]"++ it "should dump slice with no end" $+ dumpQuery [jsonPath|$[5:]|]+ `shouldBe` "$[5::1]"++ it "should dump slice with start, end and step" $+ dumpQuery [jsonPath|$[1:5:2]|]+ `shouldBe` "$[1:5:2]"++ it "should dump slice with step -2" $+ dumpQuery [jsonPath|$[5:1:-2]|]+ `shouldBe` "$[5:1:-2]"++ it "should dump slice with step -1" $+ dumpQuery [jsonPath|$[::-1]|]+ `shouldBe` "$[::-1]"++ it "should dump child wildcard" $+ dumpQuery [jsonPath|$.*|]+ `shouldBe` "$.*"++ it "should dump segment wildcard" $+ dumpQuery [jsonPath|$[*]|]+ `shouldBe` "$[*]"++ it "should dump descendant wildcard" $+ dumpQuery [jsonPath|$..*|]+ `shouldBe` "$..*"++ it "should dump descendant bracketed" $+ dumpQuery [jsonPath|$..[*]|]+ `shouldBe` "$..[*]"
test/spec/Main.hs view
@@ -7,6 +7,7 @@ import qualified ParserSpec import qualified QuerySpec import qualified LocatedSpec+import qualified DumpQuerySpec import Test.Hspec.Runner import Prelude@@ -17,6 +18,7 @@ ParserSpec.spec QuerySpec.spec LocatedSpec.spec+ DumpQuerySpec.spec main :: IO ()