packages feed

waargonaut-0.6.0.0: test/Decoder.hs

{-# LANGUAGE LambdaCase        #-}
{-# 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 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
  ]

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")