packages feed

hpack-0.35.0: test/Data/Aeson/Config/FromValueSpec.hs

{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
module Data.Aeson.Config.FromValueSpec where

import           Helper

import           GHC.Generics
import qualified Data.Map.Lazy as Map
import           Data.Monoid (Last(..))

import           Data.Aeson.Config.FromValue

shouldDecodeTo :: (HasCallStack, Eq a, Show a, FromValue a) => Value -> Result a -> Expectation
shouldDecodeTo value expected = decodeValue value `shouldBe` expected

shouldDecodeTo_ :: (HasCallStack, Eq a, Show a, FromValue a) => Value -> a -> Expectation
shouldDecodeTo_ value expected = decodeValue value `shouldBe` Right (expected, [], [])

data Person = Person {
  personName :: String
, personAge :: Int
, personAddress :: Maybe Address
} deriving (Eq, Show, Generic, FromValue)

data Address = Address {
  addressRegion :: String
, addressZip :: String
} deriving (Eq, Show, Generic, FromValue)

data Job = Job {
  jobRole :: String
, jobSalary :: Int
} deriving (Eq, Show, Generic, FromValue)

data FlatMaybe = FlatMaybe {
  flatMaybeValue :: Maybe String
} deriving (Eq, Show, Generic, FromValue)

data AliasMaybe = AliasMaybe {
  aliasMaybeValue :: Alias 'False "some-alias" (Maybe String)
} deriving (Eq, Show, Generic, FromValue)

data NestedMaybe = NestedMaybe {
  nestedMaybeValue :: Maybe (Maybe String)
} deriving (Eq, Show, Generic, FromValue)

data AliasNestedMaybe = AliasNestedMaybe {
  aliasNestedMaybeValue :: Alias 'False "some-alias" (Maybe (Maybe String))
} deriving (Eq, Show, Generic, FromValue)

data FlatLast = FlatLast {
  flatLastValue :: Last String
} deriving (Eq, Show, Generic, FromValue)

data AliasLast = AliasLast {
  aliasLastValue :: Alias 'False "some-alias" (Last String)
} deriving (Eq, Show, Generic, FromValue)

spec :: Spec
spec = do
  describe "fromValue" $ do
    context "with a record" $ do
      let
        left :: String -> Result Person
        left = Left
      it "decodes a record" $ do
        [yaml|
        name: "Joe"
        age: 23
        |] `shouldDecodeTo_` Person "Joe" 23 Nothing

      it "captures unrecognized fields" $ do
        [yaml|
        name: "Joe"
        age: 23
        foo: bar
        |] `shouldDecodeTo` Right (Person "Joe" 23 Nothing, ["$.foo"], [])

      it "captures nested unrecognized fields" $ do
        [yaml|
        name: "Joe"
        age: 23
        address:
          region: somewhere
          zip: "123456"
          foo:
            bar: 23
        |] `shouldDecodeTo` Right (Person "Joe" 23 (Just (Address "somewhere" "123456")), ["$.address.foo"], [])

      it "ignores fields that start with an underscore" $ do
        [yaml|
        name: "Joe"
        age: 23
        address:
          region: somewhere
          zip: "123456"
          _foo:
            bar: 23
        |] `shouldDecodeTo_` Person "Joe" 23 (Just (Address "somewhere" "123456"))

      it "fails on missing field" $ do
        [yaml|
        name: "Joe"
        |] `shouldDecodeTo` left "Error while parsing $ - key \"age\" not present"

      it "fails on invalid field value" $ do
        [yaml|
        name: "Joe"
        age: "23"
        |] `shouldDecodeTo` left "Error while parsing $.age - parsing Int failed, expected Number, but encountered String"

      context "when parsing a field of type (Maybe a)" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` FlatMaybe (Just "some value")

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` FlatMaybe Nothing

        it "rejects null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo` (Left "Error while parsing $.value - expected String, but encountered Null" :: Result FlatMaybe)

      context "when parsing a field of type (Maybe (Maybe a))" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` NestedMaybe (Just $ Just "some value")

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` NestedMaybe Nothing

        it "accepts null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo_` NestedMaybe (Just Nothing)

      context "when parsing a field of type (Alias (Maybe a))" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` AliasMaybe (Alias $ Just "some value")

        it "allows the field to be accessed by its alias" $ do
          [yaml|
          some-alias: some alias value
          |] `shouldDecodeTo_` AliasMaybe (Alias $ Just "some alias value")

        it "gives the primary name precedence" $ do
          [yaml|
          value: some value
          some-alias: some alias value
          |] `shouldDecodeTo` Right (AliasMaybe (Alias $ Just "some value"), ["$.some-alias"], [])

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` AliasMaybe (Alias Nothing)

        it "rejects null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo` (Left "Error while parsing $.value - expected String, but encountered Null" :: Result AliasMaybe)

      context "when parsing a field of type (Alias (Maybe (Maybe a)))" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` AliasNestedMaybe (Alias . Just $ Just "some value")

        it "allows the field to be accessed by its alias" $ do
          [yaml|
          some-alias: some value
          |] `shouldDecodeTo_` AliasNestedMaybe (Alias . Just $ Just "some value")

        it "gives the primary name precedence" $ do
          [yaml|
          value: some value
          some-alias: some alias value
          |] `shouldDecodeTo` Right (AliasNestedMaybe (Alias . Just $ Just "some value"), ["$.some-alias"], [])

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` AliasNestedMaybe (Alias Nothing)

        it "accepts null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo_` AliasNestedMaybe (Alias $ Just Nothing)

      context "when parsing a field of type (Last a)" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` FlatLast (Last $ Just "some value")

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` FlatLast (Last Nothing)

        it "rejects null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo` (Left "Error while parsing $.value - expected String, but encountered Null" :: Result FlatLast)

      context "when parsing a field of type (Alias (Last a))" $ do
        it "accepts a value" $ do
          [yaml|
          value: some value
          |] `shouldDecodeTo_` AliasLast (Alias . Last $ Just "some value")

        it "allows the field to be accessed by its alias" $ do
          [yaml|
          some-alias: some value
          |] `shouldDecodeTo_` AliasLast (Alias . Last $ Just "some value")

        it "gives the primary name precedence" $ do
          [yaml|
          value: some value
          some-alias: some alias value
          |] `shouldDecodeTo` Right (AliasLast (Alias . Last $ Just "some value"), ["$.some-alias"], [])

        it "allows the field to be omitted" $ do
          [yaml|
          {}
          |] `shouldDecodeTo_` AliasLast (Alias $ Last Nothing)

        it "rejects null" $ do
          [yaml|
          value: null
          |] `shouldDecodeTo` (Left "Error while parsing $.value - expected String, but encountered Null" :: Result AliasLast)

    context "with (,)" $ do
      it "captures unrecognized fields" $ do
        [yaml|
        name: Joe
        age: 23
        role: engineer
        salary: 100000
        foo: bar
        |] `shouldDecodeTo` Right ((Person "Joe" 23 Nothing, Job "engineer" 100000), ["$.foo"], [])

    context "with []" $ do
      it "captures unrecognized fields" $ do
        let
          expected = [Person "Joe" 23 (Just (Address "somewhere" "123456")), Person "Marry" 25 Nothing]
        [yaml|
        - name: "Joe"
          age: 23
          address:
            region: somewhere
            zip: "123456"
            foo: 23
        - name: "Marry"
          age: 25
          bar: 42
        |] `shouldDecodeTo` Right (expected, ["$[1].bar", "$[0].address.foo"], [])

    context "with Map" $ do
      it "captures unrecognized fields" $ do
        [yaml|
        Joe:
          region: somewhere
          zip: '123456'
          foo: bar
        |] `shouldDecodeTo` Right (Map.fromList [("Joe", Address "somewhere" "123456")], ["$.Joe.foo"], [])