packages feed

waargonaut 0.4.1.0 → 0.4.2.0

raw patch · 6 files changed

+185/−14 lines, 6 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Waargonaut.Decode: atKeyOptional :: Monad f => Text -> Decoder f b -> Decoder f (Maybe b)
+ Waargonaut.Decode: fromKeyOptional :: Monad f => Text -> Decoder f b -> JCurs -> DecodeResult f (Maybe b)
+ Waargonaut.Decode.Internal: compressHistory :: CursorHistory' i -> CursorHistory' i

Files

changelog.md view
@@ -1,5 +1,11 @@ # Revision history for waargonaut +## 0.4.2.0  -- 2018-11-29++* Improved pretty printing of CursorHistory by condensing multiple numeric movements and removing the single movements following searching for keys.+* Add `fromKeyOptional` and `atKeyOptional` that make it easier to handle optional keys on objects.+* Add `prismDOrFail'` function to allow the user to construct an error from the value that was decoded.+ ## 0.4.1.0  -- 2018-11-20  * Add `oneOf` decoder and tests
src/Waargonaut/Decode.hs view
@@ -53,6 +53,10 @@   , atKey   , focus +    -- * Attempting decoding+  , fromKeyOptional+  , atKeyOptional+     -- * Provided Decoders   , leftwardCons   , rightwardSnoc@@ -85,10 +89,11 @@  import           Control.Lens                              (Cons, Lens', Prism',                                                             Snoc, cons, lens,-                                                            modifying, preview,-                                                            snoc, traverseOf,-                                                            view, ( # ), (.~),-                                                            (^.), _Wrapped)+                                                            matching, modifying,+                                                            preview, snoc,+                                                            traverseOf, view,+                                                            ( # ), (.~), (^.),+                                                            _Wrapped)  import           Prelude                                   (Bool, Bounded, Char,                                                             Eq, Int, Integral,@@ -98,10 +103,12 @@  import           Control.Applicative                       (Applicative (..)) import           Control.Category                          ((.))-import           Control.Monad                             (Monad (..), (>=>))+import           Control.Monad                             (Monad (..), (=<<),+                                                            (>=>)) import           Control.Monad.Morph                       (embed, generalize) -import           Control.Monad.Except                      (lift, liftEither,+import           Control.Monad.Except                      (catchError, lift,+                                                            liftEither,                                                             throwError) import           Control.Monad.Reader                      (ReaderT (..), ask,                                                             local, runReaderT)@@ -111,6 +118,7 @@ import           Control.Monad.Error.Hoist                 ((<!?>), (<?>))  import           Data.Either                               (Either (..))+import qualified Data.Either                               as Either (either) import           Data.Foldable                             (Foldable, foldl,                                                             foldr) import           Data.Function                             (const, flip, ($),@@ -481,6 +489,39 @@ atKey k d =   withCursor (down >=> fromKey k d) +-- | A version of 'fromKey' that returns its result in 'Maybe'. If the key is+-- not present in the object, 'Nothing' is returned. If the key is present,+-- decoding will be performed as with 'fromKey'.+--+-- For example, if a key could be absent and could be null if present,+-- it could be decoded as follows:+--+-- @+-- join <$> fromKeyOptional "key" (maybeOrNull text)+-- @+fromKeyOptional+  :: Monad f+  => Text+  -> Decoder f b+  -> JCurs+  -> DecodeResult f (Maybe b)+fromKeyOptional k d c =+  focus' =<< catchError (pure <$> moveToKey k c) (\de -> case de of+    KeyNotFound _ -> pure Nothing+    _             -> throwError de)+  where+    focus' = maybe (pure Nothing) (fmap Just . focus d)++-- | A version of 'atKey' that returns its result in 'Maybe'. If the key is+-- not present in the object, 'Nothing' is returned. If the key is present,+-- decoding will be performed as with 'atKey'.+atKeyOptional+  :: Monad f+  => Text+  -> Decoder f b+  -> Decoder f (Maybe b)+atKeyOptional k d = withCursor (down >=> fromKeyOptional k d)+ -- | Used internally in the construction of the basic 'Decoder's. Takes a 'Text' -- description of the thing you expect to find at the current cursor, and a -- function to convert the 'Json' structure found there into something else.@@ -702,8 +743,17 @@   -> Prism' a b   -> Decoder f a   -> Decoder f b-prismDOrFail e p d = withCursor $-  focus d >=> \a -> preview p a <?> e+prismDOrFail e = prismDOrFail' (const e)++-- | Like 'prismDOrFail'', but lets you use the @a@ to construct the error.+prismDOrFail'+  :: Monad f+  => (a -> DecodeError)+  -> Prism' a b+  -> Decoder f a+  -> Decoder f b+prismDOrFail' e p d = withCursor $+  focus d >=> Either.either (throwError . e) pure . matching p  -- | Decode an 'Int'. int :: Monad f => Decoder f Int
src/Waargonaut/Decode/Internal.hs view
@@ -10,6 +10,8 @@ module Waargonaut.Decode.Internal   ( CursorHistory' (..)   , ppCursorHistory+  , compressHistory+   , DecodeResultT (..)   , Decoder' (..) @@ -47,7 +49,6 @@ import           Control.Lens                  (Rewrapped, Wrapped (..), (%=),                                                 _1, _Wrapped) import qualified Control.Lens                  as L- import           Control.Monad                 ((>=>)) import           Control.Monad.Except          (ExceptT (..), MonadError (..),                                                 liftEither, runExceptT)@@ -58,8 +59,10 @@ import           Control.Monad.Morph           (MFunctor (..), MMonad (..))  import           Data.Bifunctor                (first)+import qualified Data.Foldable                 as F import           Data.Functor                  (($>))-import           Data.Sequence                 (Seq)+import           Data.Semigroup                ((<>))+import           Data.Sequence                 (Seq, fromList)  import           Data.Text                     (Text) @@ -73,6 +76,8 @@ import           Data.Scientific               (Scientific) import qualified Data.Scientific               as Sci +import           Natural                       (Natural, _Natural)+ import           Waargonaut.Types              (AsJType (..), JString,                                                 jNumberToScientific,                                                 jsonAssocKey, jsonAssocVal,@@ -96,6 +101,53 @@   }   deriving (Show, Eq) +switchbackMoves+  :: (Natural -> a)+  -> (Natural -> a)+  -> Natural+  -> Natural+  -> b+  -> b+  -> [(a, b)]+  -> [(a, b)]+switchbackMoves a b n m i i' sq =+  let+    n' = _Natural L.# n :: Int+    m' = _Natural L.# m+  in+    if n' > m'+    then (a $ (n' - m') L.^. _Natural, i)  : sq+    else (b $ (m' - n') L.^. _Natural, i') : sq++rmKeyJumps :: [(ZipperMove, i)] -> [(ZipperMove, i)]+rmKeyJumps (d@(DAt _, _) : (R _, _) : sq) = d:sq+rmKeyJumps s                              = s++combineLRMoves :: [(ZipperMove, i)] -> [(ZipperMove, i)]+combineLRMoves ((R n, _) : (R m, i)  : sq) = (R (n <> m), i) : sq+combineLRMoves ((L n, _) : (L m, i)  : sq) = (L (n <> m), i) : sq+combineLRMoves ((L n, i) : (R m, i') : sq) = switchbackMoves L R n m i i' sq+combineLRMoves ((R n, i) : (L m, i') : sq) = switchbackMoves R L n m i i' sq+combineLRMoves s                           = s++-- | This function will condense incidental movements, reducing the amount of+-- noise in the error output.+--+-- The rules that are currently applied are:+--+-- * [R n, R m]   = [R (n + m)]+-- * [L n, R m]   = [L (n + m)]+-- * [R n, L m]   = [R (n - m)] where n > m+-- * [R n, L m]   = [L (m - n)] where n < m+-- * [L n, R m]   = [L (n - m)] where n > m+-- * [L n, R m]   = [R (m - n)] where n < m+-- * [DAt k, R n] = [DAt k]+--+-- This function is automatically applied when using the 'ppCursorHistory'+-- function to render the cursor movements.+compressHistory :: CursorHistory' i -> CursorHistory' i+compressHistory = L.over _Wrapped (fromList . L.transform (combineLRMoves . rmKeyJumps) . F.toList)+ -- | -- Pretty print the given 'CursorHistory'' to a more useful format compared to a 'Seq' of 'i'. ppCursorHistory@@ -105,6 +157,7 @@   foldr (<+>) mempty   . fmap (ppZipperMove . fst)   . unCursorHistory'+  . compressHistory  instance CursorHistory' i ~ t => Rewrapped (CursorHistory' i) t 
test/Decoder.hs view
@@ -5,8 +5,8 @@   ( decoderTests   ) where -import           Prelude                    (Char, Eq, Int, Show, String, print,-                                             (==))+import           Prelude                    (Char, Eq, Int, Show (show), String,+                                             print, (==))  import           Control.Applicative        (liftA3, (<$>)) import           Control.Category           ((.))@@ -21,6 +21,8 @@ import qualified Data.Either                as Either import           Data.Function              (const, ($)) import           Data.Functor.Alt           ((<!>))+import           Data.Maybe                 (Maybe (Just, Nothing))+import           Data.Semigroup             (Semigroup ((<>))) import qualified Data.Sequence              as Seq import           Data.Tagged                (untag) import           Data.Text                  (Text)@@ -51,6 +53,7 @@   , testCase "Using Alt (Error) - Records BranchFail" decodeAltError   , testCase "List Decoder" listDecoder   , testCase "NonEmpty List Decoder" nonEmptyDecoder+  , testCase "Absent Key Decoder" absentKeyDecoder   ]  nonEmptyDecoder :: Assertion@@ -187,3 +190,17 @@                   )                  (\_ -> assertFailure "Alt Error Test should fail")++absentKeyDecoder :: Assertion+absentKeyDecoder = do+  a <- D.runDecode (D.atKeyOptional "key" D.text) parseBS (D.mkCursor "{\"key\":\"present\"}")+  b <- D.runDecode (D.atKeyOptional "missing" D.text) parseBS (D.mkCursor "{\"key\":\"present\"}")+  c <- D.runDecode (D.atKeyOptional "key" D.int) parseBS (D.mkCursor "{\"key\":\"present\"}")++  a @?= Either.Right (Just "present")+  b @?= Either.Right Nothing+  case c of+    Either.Right _ ->+      assertFailure "atKeyOptional succeeded when it shouldn't have"+    Either.Left (e, _) ->+      assertEqual ("atKeyOptional failed incorrectly: " <> show e) e (D.ConversionFailure "integral")
test/Main.hs view
@@ -1,8 +1,10 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections     #-} module Main (main) where  import           Control.Lens                (( # ), (^.), (^?), _2) import qualified Control.Lens                as L+import           Control.Monad               (when)  import           Data.Either                 (isLeft) @@ -18,6 +20,8 @@ import           Data.Text                   (Text) import qualified Data.Text.Encoding          as Text +import qualified Data.Sequence               as S+ import           Hedgehog import qualified Hedgehog.Gen                as Gen import qualified Hedgehog.Range              as Range@@ -26,6 +30,8 @@ import           Test.Tasty.Hedgehog import           Test.Tasty.HUnit +import           Natural                     (_Natural)+ import           Data.Digit                  (HeXDigit)  import           Waargonaut                  (Json)@@ -40,6 +46,8 @@  import qualified Waargonaut.Decode           as D import           Waargonaut.Decode.Error     (DecodeError)+import           Waargonaut.Decode.Internal  (CursorHistory' (..),+                                              ZipperMove (..), compressHistory) import qualified Waargonaut.Encode           as E  import           Waargonaut.Generic          (mkDecoder, mkEncoder)@@ -75,6 +83,42 @@ decode =   Common.parseText +prop_history_condense :: Property+prop_history_condense = property $ do+  n <- forAll $ Gen.int (Range.linear 1 10)+  m <- forAll $ Gen.int (Range.linear 1 10)++  let+    ixa = 1 :: Int+    ixb = 2+    mkCH = CursorHistory' . S.fromList+    mcA cn cm n' m' = mkCH [(cn (n' ^. _Natural), ixa), (cm (m' ^. _Natural), ixb)]+    mcB c x i = mkCH [(c (x ^. _Natural), i)]++  -- * [R n, R m]   = [R (n + m)]+  compressHistory (mcA R R n m) === mcB R (n + m) ixb++  -- * [L n, R m]   = [L (n + m)]+  compressHistory (mcA L L n m) === mcB L (n + m) ixb++  let+    rlch = compressHistory (mcA R L n m)+    lrch = compressHistory (mcA L R n m)+  when (n > m) $ do+    -- * [R n, L m]   = [R (n - m)] where n > m+    rlch === mcB R (n - m) ixa+    -- * [L n, R m]   = [L (n - m)] where n > m+    lrch === mcB L (n - m) ixa++  when (n < m) $ do+    -- * [R n, L m]   = [L (m - n)] where n < m+    rlch === mcB L (m - n) ixb+    -- * [L n, R m]   = [R (m - n)] where n < m+    lrch === mcB R (m - n) ixb++  -- * [DAt k, R n] = [DAt k]+  compressHistory (mkCH [(DAt "KeyName", ixa), (R (n ^. _Natural), ixb)]) === mkCH [(DAt "KeyName", ixa)]+ prop_uncons_consCommaSep :: Property prop_uncons_consCommaSep = property $ do   cs <- forAll $ CS.genCommaSeparated WS.genWS Gen.bool@@ -177,7 +221,7 @@       -- $ D.atKey "beep" (D.maybeOrNull D.bool)  tripping_properties :: TestTree-tripping_properties = testGroup "Round Trip"+tripping_properties = testGroup "Properties"   [ testProperty "CommaSeparated: cons . uncons = id"                  prop_uncons_consCommaSep   , testProperty "CommaSeparated (disregard WS): cons . uncons = id"   prop_uncons_consCommaSepVal   , testProperty "Char -> JChar Digit -> Maybe Char = Just id"         prop_jchar@@ -188,6 +232,7 @@   , testProperty "Maybe Bool (generic)"                                prop_tripping_maybe_bool_generic   , testProperty "Image record (generic)"                              prop_tripping_image_record_generic   , testProperty "Newtype with Options (generic)"                      prop_tripping_newtype_fudge_generic+  , testProperty "Condensing History"                                  prop_history_condense   ]  parser_properties :: TestTree
waargonaut.cabal view
@@ -10,7 +10,7 @@ -- PVP summary:      +-+------- breaking API changes --                   | | +----- non-breaking API additions --                   | | | +--- code changes with no API change-version:             0.4.1.0+version:             0.4.2.0  -- A short (one-line) description of the package. synopsis:            JSON wrangling