{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} module Decoder ( decoderTests ) where import Prelude (Char, Eq, Int, Show (show), String, print, (==)) import Control.Applicative (liftA3, pure, (<$>)) import Control.Category ((.)) import Control.Lens (preview) import Control.Monad (Monad, (>=>), (>>=)) import Test.Tasty (TestTree, testGroup) import Test.Tasty.HUnit (Assertion, assertBool, assertEqual, assertFailure, testCase, (@?=)) import Hedgehog (Property, evalIO, property, withTests, (/==), (===)) import Test.Tasty.Hedgehog (testProperty) import Data.Bool (Bool (..)) import qualified Data.ByteString as BS import qualified Data.Either as Either import Data.Function (const, ($)) import Data.Functor (fmap) import Data.Maybe (Maybe (Just, Nothing)) import Data.Semigroup (Semigroup ((<>))) import qualified Data.Sequence as Seq import Data.Tagged (untag) import Data.Text (Text) import qualified Data.Text as T import qualified Data.Text.IO as TIO import qualified Natural as N import Waargonaut.Lens (_String) import Waargonaut.Generic (mkDecoder) import Waargonaut.Decode.Internal (ZipperMove (BranchFail), ppCursorHistory, unCursorHistory') import qualified Waargonaut.Decode as D import qualified Waargonaut.Decode.Error as D import qualified Waargonaut.Attoparsec as WA import Types.Common (imageDecodeSuccinct, testImageDataType) decoderTests :: TestTree decoderTests = testGroup "Decoding" [ testCase "Image Record" decodeImageObjJson , testCase "[Int]" decodeIntListJson , testCase "(Char,String,[Int])" decodeTripleJson , testCase "Fail with Bad Key" decodeTestBadObjKey , testCase "Fail with Missing Key" decodeTestMissingObjKey , testCase "Enum" decodeTestEnum , testCase "Enum and throwError" decodeTestEnumError , testCase "Using Alt" decodeAlt , testCase "Using Alt (Error) - Records BranchFail" decodeAltError , testCase "List Decoder" listDecoder , testCase "NonEmpty List Decoder" nonEmptyDecoder , testCase "Object Decoder" objectAsKeyValuesDecoder , testCase "Absent Key Decoder" absentKeyDecoder , testCase "Either decoding order - Right first" decodeEitherRightFirst , testProperty "Unicode codepoint handling regression" unicodeHandlingRegression , testCase "Pass decoded key to the object value decoder" testKeysPassedToValueDecoder ] data ContainsItsKey = ContainsItsKey { _containsItsKeyKey :: Text , _containsItsKeyOtherVal :: Text } deriving (Eq,Show) testKeysPassedToValueDecoder :: Assertion testKeysPassedToValueDecoder = TIO.readFile "test/json-data/keys-in-obj.json" >>= WA.decodeAttoparsecText dec >>= (@?= Either.Right expected) where expected = [ ContainsItsKey "ID0" "Some data" , ContainsItsKey "ID1" "Some different data" ] dec = D.atKey "Collection" (D.passKeysToValues [] D.text takesKeyDecoder) takesKeyDecoder k = ContainsItsKey k <$> D.atKey "SomeCommonKeys" D.text unicodeHandlingRegression :: Property unicodeHandlingRegression = withTests 1 . property $ do let -- Prepare a decoder function dec = WA.pureDecodeAttoparsecByteString -- Decoder for JSON -> Text decText = dec D.text -- Decoder for JSON -> Json decJson = dec D.json -- Manual created JSON "String" input manualInput = "\"\\u2705\"" -- what the above JSON string should parse into. strGood = ("\x2705" :: String) -- what issue #58 claims is produced by parsing to 'Text' strBad = ("\x05" :: String) -- This is the expected 'Text' value expectedText = Either.Right ("\x2705" :: Text) -- Read the JSON string in from a file to try to avoid differences from -- creating the text input via haskell values. fileInput <- evalIO $ BS.readFile "test/json-data/unicode_2705.json" let -- Decode the manual and file inputs to 'Text' fileInputDecoded = decText fileInput manualInputDecoded = decText manualInput -- Decode the manual and file inputs to their 'Json' representations fileInputJson = decJson fileInput manualInputJson = decJson manualInput -- Do the 'Text' decoded values match our expectations fileInputDecoded === expectedText manualInputDecoded === expectedText -- If we decode to 'Json' and then use the prism, do we still get the required -- string. There should be no reason this is different to decoding to text. -- Key words being "should be". (fmap (preview _String) fileInputJson) === fmap pure expectedText (fmap (preview _String) manualInputJson) === fmap pure expectedText -- For comparison, take the expected good/bad 'String' values and pack them to 'Text'. Either.Right (T.pack strGood) === fileInputDecoded Either.Right (T.pack strGood) === manualInputDecoded Either.Right (T.pack strBad) /== fileInputDecoded Either.Right (T.pack strBad) /== manualInputDecoded nonEmptyDecoder :: Assertion nonEmptyDecoder = do let dec = WA.pureDecodeAttoparsecByteString (D.nonempty D.int) ok = "[1]" notOkay = "[]" badTypeObj = "{}" badTypeText = "\"test\"" badTypeNum = "3" badElem = "[1, \"fred\"]" assertBool "NonEmpty Decoder - fail! non-empty list decoder BROKEN. Start panicking" (Either.isRight (dec ok)) assertBool "NonEmpty Decoder - empty list shouldn't succeed" (Either.isLeft (dec notOkay)) assertBool "NonEmpty Decoder - invalid element decoder accepted" (Either.isLeft (dec badElem)) assertBool "NonEmpty Decoder - invalid type accepted - object" (Either.isLeft (dec badTypeObj)) assertBool "NonEmpty Decoder - invalid type accepted - text" (Either.isLeft (dec badTypeText)) assertBool "NonEmpty Decoder - invalid type accepted - num" (Either.isLeft (dec badTypeNum)) listDecoder :: Assertion listDecoder = do let dec = WA.pureDecodeAttoparsecByteString (D.list D.int) ok = "[1,2,3]" okE = "[]" badTypeObj = "{}" badTypeText = "\"test\"" badTypeNum = "3" badElem = "[\"fred\", \"susan\"]" assertBool "List Decoder - fail! List Decoder BROKEN. Start panicking." (Either.isRight (dec ok)) assertBool "List Decoder - empty list fail" (Either.isRight (dec okE)) assertBool "List Decoder - invalid type accepted - object" (Either.isLeft (dec badTypeObj)) assertBool "List Decoder - invalid type accepted - text" (Either.isLeft (dec badTypeText)) assertBool "List Decoder - invalid type accepted - num" (Either.isLeft (dec badTypeNum)) assertBool "List Decoder - invalid element decoder accepted" (Either.isLeft (dec badElem)) objectAsKeyValuesDecoder :: Assertion objectAsKeyValuesDecoder = do let dec = WA.pureDecodeAttoparsecByteString (D.objectAsKeyValues D.text D.int) ok = "{\"1\":1,\"2\":2,\"3\":3}" okE = "{}" badTypeArray = "[]" badTypeText = "\"test\"" badTypeNum = "3" badKey = "{1:1}" badValue = "{\"1\":\"fred\", \"2\":\"susan\"}" assertBool "Object Decoder - fail! Object Decoder BROKEN. Start panicking." (Either.isRight (dec ok)) assertBool "Object Decoder - empty list fail" (Either.isRight (dec okE)) assertBool "Object Decoder - invalid type accepted - object" (Either.isLeft (dec badTypeArray)) assertBool "Object Decoder - invalid type accepted - text" (Either.isLeft (dec badTypeText)) assertBool "Object Decoder - invalid type accepted - num" (Either.isLeft (dec badTypeNum)) assertBool "Object Decoder - invalid key decoder accepted" (Either.isLeft (dec badKey)) assertBool "Object Decoder - invalid value decoder accepted" (Either.isLeft (dec badValue)) decodeTestMissingObjKey :: Assertion decodeTestMissingObjKey = do let j = "{\"foo\":33}" d = D.withCursor $ D.down >=> D.fromKey "bar" D.int let r = WA.pureDecodeAttoparsecByteString d j Either.either (\(e, _) -> assertBool "Incorrect Error - Expected KeyDecodeFailed" (e == D.KeyNotFound "bar")) (\_ -> assertFailure "Expected Error!") r decodeTestBadObjKey :: Assertion decodeTestBadObjKey = do let j = "{33:33}" d = D.withCursor $ D.down >=> D.fromKey "foo" D.int let r = WA.pureDecodeAttoparsecByteString d j Either.either (\(e, _) -> assertBool "Incorrect Error - Expected KeyDecodeFailed" (e == D.KeyDecodeFailed) ) (\_ -> assertFailure "Expected Error!") r decodeImageObjJson :: Assertion decodeImageObjJson = WA.pureDecodeAttoparsecByteString imageDecodeSuccinct <$> BS.readFile "test/json-data/image_obj.json" >>= Either.either failWithHistory (assertEqual "Image Decode Failed" testImageDataType) where failWithHistory (err, hist) = do print err print (ppCursorHistory hist) assertFailure "Decode Failed" decodeIntListJson :: Assertion decodeIntListJson = assertBool "[Int] Decode Success" . Either.isRight $ WA.pureDecodeAttoparsecByteString listDecode "[23,44]" where listDecode :: Monad f => D.Decoder f [Int] listDecode = untag mkDecoder decodeTripleJson :: Assertion decodeTripleJson = assertBool "(Char,String,[Int]) Decode Success" . Either.isRight $ WA.pureDecodeAttoparsecByteString decoder "[\"a\",\"fred\",1,2,3,4]" where decoder :: Monad f => D.Decoder f (Char,String,[Int]) decoder = D.withCursor $ D.down >=> \fstElem -> liftA3 (,,) (D.focus D.unboundedChar fstElem) (D.moveRight1 fstElem >>= D.focus D.string) (D.moveRightN (N.successor' (N.successor' N.zero')) fstElem >>= D.rightwardSnoc [] D.int) data MyEnum = A | B | C deriving (Eq, Show) decodeMyEnum :: Monad f => D.Decoder f MyEnum decodeMyEnum = D.oneOf D.text "MyEnum" [ ("a", A) , ("b", B) , ("c", C) ] decodeTestEnum :: Assertion decodeTestEnum = do chk "\"a\"" A chk "\"b\"" B chk "\"c\"" C where chk i o = WA.pureDecodeAttoparsecByteString decodeMyEnum i @?= (Either.Right o) decodeTestEnumError :: Assertion decodeTestEnumError = let i = WA.pureDecodeAttoparsecByteString decodeMyEnum "\"WUT\"" in Either.either (\(e, _) -> assertBool "Incorrect Error!" (e == D.ConversionFailure "MyEnum")) (const (assertFailure "Should not succeed")) i decodeEitherAlt :: Monad f => D.Decoder f (Either.Either Text Int) decodeEitherAlt = D.either D.text D.int decodeEitherRightFirst :: Assertion decodeEitherRightFirst = do let d = WA.pureDecodeAttoparsecByteString (D.either D.scientific D.int) d "44e333" @?= Either.Right (Either.Left 44e333) d "33" @?= Either.Right (Either.Right 33) decodeAlt :: Assertion decodeAlt = do let t = WA.pureDecodeAttoparsecByteString decodeEitherAlt "\"FRED\"" i = WA.pureDecodeAttoparsecByteString decodeEitherAlt "33" t @?= Either.Right (Either.Left "FRED") i @?= Either.Right (Either.Right 33) decodeAltError :: Assertion decodeAltError = let i = WA.pureDecodeAttoparsecByteString decodeEitherAlt "{\"foo\":33}" in Either.either (\(_,h) -> assertBool "BranchFail error not found in history" $ case Seq.viewr (unCursorHistory' h) of _ Seq.:> (BranchFail _, _) -> True _ -> False ) (\_ -> assertFailure "Alt Error Test should fail") i absentKeyDecoder :: Assertion absentKeyDecoder = do let a = WA.pureDecodeAttoparsecByteString (D.atKeyOptional "key" D.text) "{\"key\":\"present\"}" b = WA.pureDecodeAttoparsecByteString (D.atKeyOptional "missing" D.text) "{\"key\":\"present\"}" c = WA.pureDecodeAttoparsecByteString (D.atKeyOptional "key" D.int) "{\"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")