packages feed

aeson-combinators-0.0.1.0: test/JSONDecodeSpec.hs

{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE FlexibleContexts    #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}

module JSONDecodeSpec where

import           Control.Applicative
import qualified Data.Aeson.Combinators.Decode as JD
import           Data.Aeson.Types              (FromJSON (..))
import           Data.ByteString.Lazy
import           Data.ByteString.Lazy.UTF8     (fromString)
import           Data.Monoid                   ((<>))
import           Data.Text
import           GHC.Generics
import           Test.Hspec

data Object = Object
    { name :: Text
    , nick :: Text
    }
    deriving (Show, Eq, Generic)

data FooBar = Foo | Bar
    deriving (Show, Eq)

instance FromJSON Object

decoder :: JD.Decoder Object
decoder =
  Object
    <$> JD.key "name" JD.text
    <*> JD.key "nick" JD.text

json :: ByteString
json = "{\"name\":\"Jany Doe\",\"nick\": \"jany\"}"

jsonNested :: ByteString
jsonNested = "{\"data\":" <> json <> "}"

objectSpec :: Spec
objectSpec = do
  describe "field" $ do
    let res = Just $ Object "Jany Doe" "jany"

    it "should decode object" $ do
      JD.decode decoder json `shouldBe` res

    it "should decode nested object" $ do
      JD.decode (JD.key "data" decoder) jsonNested
        `shouldBe` res

    it "should be possible to use default decoder" $ do
      JD.decode JD.auto json `shouldBe` res

  describe "at" $ do
    let
      nest' :: Int -> ByteString -> ByteString
      nest' n inner =
          if n <= 0 then
            inner
          else
            nest' (n - 1) ("{\"level-" <> fromString (show n) <> "\":" <> inner <> "}")
    let nest n = nest' n json
    let res = Just $ Object "Jany Doe" "jany"

    it "should decode object directly for empty list" $ do
      JD.decode (JD.at [] decoder) json
       `shouldBe` res

    it "should decode 2 level json" $ do
      let jdata = nest 2

      JD.decode (JD.at ["level-1", "level-2"] decoder) jdata
       `shouldBe` res

    it "should decode 10 level json" $ do
      let jdata = nest 10
      let path = ["level-1", "level-2", "level-3", "level-4", "level-5"
             , "level-6", "level-7", "level-8", "level-9", "level-10"
             ]

      JD.decode (JD.at path decoder) jdata `shouldBe` res

arraySpec :: Spec
arraySpec = do
  describe "index" $ do
    it "decode singleton by index" $ do
      JD.decode (JD.index 0 decoder) "[{\"name\": \"Sanders\", \"nick\": \"bern\"}]"
        `shouldBe` (Just $ Object "Sanders" "bern")

    it "should decode nth index" $ do
      JD.decode (JD.index 1 decoder) "[false, {\"name\": \"Sanders\", \"nick\": \"bern\"}, true]"
        `shouldBe` (Just $ Object "Sanders" "bern")

monadSpec :: Spec
monadSpec =
  describe "monadic decoding" $ do
    let fromText v =
          case v of
            "foo" -> pure Foo
            "bar" -> pure Bar
            _     -> fail "unknown"

    it "should work as a dummy value" $ do
      JD.decode (JD.string >>= pure) "\"foo\""
        `shouldBe` (Just "foo")

    it "should turn string to sum" $ do
      JD.decode (JD.string >>= fromText) "\"foo\""
        `shouldBe` (Just Foo)

    it "should fail with right error" $ do
      JD.eitherDecode (JD.string >>= fromText) "\"foobar\""
        `shouldBe` (Left "Error in $: unknown")

alternativeSpec :: Spec
alternativeSpec =
  describe "alternative (<|>)" $ do
    let (dec :: JD.Decoder (Either Bool Object)) =
          Left <$> JD.bool <|> Right <$> decoder

    it "should decode first alternative" $ do
      JD.decode dec "false" `shouldBe` (Just $ Left False)

    it "should decode second alternative" $ do
      JD.decode dec "{\"name\": \"Joe\",\"nick\": \"jd\"}"
        `shouldBe` (Just $ Right $ Object "Joe" "jd")

jsonNullSpec :: Spec
jsonNullSpec =
  describe "jsonNull" $ do
    let barDec txt =
          case txt of
            "bar" -> pure Bar
            _     -> fail $ "Unknown value" <> show txt

    let (dec :: JD.Decoder FooBar) =
          JD.jsonNull Foo <|> (barDec =<< JD.text)

    it "should decode Foo from null" $ do
      JD.decode dec "null" `shouldBe` (Just Foo)

    it "should decode Bar from string" $ do
      JD.decode dec "\"bar\"" `shouldBe` (Just Bar)

spec :: Spec
spec = do
  objectSpec
  arraySpec
  monadSpec
  alternativeSpec
  jsonNullSpec