packages feed

avro-0.4.7.0: test/Avro/THUnionSpec.hs

{-# LANGUAGE DeriveGeneric       #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell     #-}
module Avro.THUnionSpec
where

import qualified Data.List.NonEmpty as NE


import           Control.Monad.Identity (Identity (..))
import qualified Data.Aeson             as Aeson
import           Data.Avro
import           Data.Avro.Deriving
import           Data.Avro.EitherN
import qualified Data.Avro.Schema       as Schema
import qualified Data.Avro.Types        as Avro
import qualified Data.ByteString        as BS
import qualified Data.ByteString.Lazy   as LBS
import qualified Data.Map               as Map
import qualified Data.Vector            as V

import System.Directory (doesFileExist)

import Test.Hspec

import Paths_avro

deriveAvro "test/data/unions.avsc"

spec :: Spec
spec = describe "Avro.THUnionSpec: Schema with unions." $ do
  let objA = Unions
        { unionsScalars     = Left "foo"
        , unionsNullable    = Nothing
        , unionsRecords     = Left $ Foo { fooStuff = "stuff" }
        , unionsSameFields  = Left $ Foo { fooStuff = "more stuff" }
        , unionsArrayAndMap = Left ["foo"]
        , unionsOne         = Identity 42
        , unionsThree       = E3_1 37
        , unionsFour        = E4_2 "foo"
        , unionsFive        = E5_4 $ Foo { fooStuff = "foo stuff" }
        , unionsSix        = E6_2 "foo"
        , unionsSeven        = E7_6 6.28
        , unionsEight        = E8_3 37
        , unionsNine        = E9_1 37
        , unionsTen        = E10_9 $ BS.pack [70, 79, 79, 66, 65, 82]
        }
      objB = Unions
        { unionsScalars     = Right 42
        , unionsNullable    = Just 37
        , unionsRecords     = Right $ Bar { barStuff  = "stuff"
                                          , barThings = Foo { fooStuff = "things" }
                                          }
        , unionsSameFields  = Right $ NotFoo { notFooStuff = "different from Foo" }
        , unionsArrayAndMap = Right $ Map.fromList [("a", 5)]
        , unionsOne         = Identity 42
        , unionsThree       = E3_3 37
        , unionsFour        = E4_4 $ Foo { fooStuff = "foo stuff" }
        , unionsFive        = E5_5 $ NotFoo { notFooStuff = "not foo stuff" }
        , unionsSix        = E6_6 6.28
        , unionsSeven        = E7_7 False
        , unionsEight        = E8_8 2.718
        , unionsNine        = E9_9 $ BS.pack [70, 79, 79, 66, 65, 82]
        , unionsTen        = E10_10 $ Bar { barStuff = "bar stuff",
                                            barThings = Foo { fooStuff = "things" }
                                          }
        }

      field name schema def = Schema.Field name [] Nothing (Just Schema.Ascending) schema def
      record name fields =
        Schema.Record name [] Nothing (Just Schema.Ascending) fields
      named = Schema.NamedType

      foo    = named "haskell.avro.example.Foo"
      notFoo = named "haskell.avro.example.NotFoo"
      bar = named "haskell.avro.example.Bar"
      expectedSchema = record "haskell.avro.example.Unions"
        [ field "scalars"     (Schema.mkUnion (NE.fromList [Schema.String', Schema.Long'])) scalarsDefault
        , field "nullable"    (Schema.mkUnion (NE.fromList [Schema.Null, Schema.Int']))    nullableDefault
        , field "records"     (Schema.mkUnion (NE.fromList [fooSchema, barSchema]))       Nothing
        , field "sameFields"  (Schema.mkUnion (NE.fromList [foo, notFooSchema]))          Nothing
        , field "arrayAndMap" (Schema.mkUnion (NE.fromList [array, map]))                 Nothing

        , field "one"   (Schema.mkUnion (NE.fromList [Schema.Int']))                                          Nothing
        , field "three" (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long']))              Nothing
        , field "four"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo]))         Nothing
        , field "five"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo])) Nothing
        , field "six"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo, Schema.Float])) Nothing
        , field "seven"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo, Schema.Float, Schema.Boolean])) Nothing
        , field "eight"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo, Schema.Float, Schema.Boolean, Schema.Double])) Nothing
        , field "nine"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo, Schema.Float, Schema.Boolean, Schema.Double, Schema.Bytes'])) Nothing
        , field "ten"  (Schema.mkUnion (NE.fromList [Schema.Int', Schema.String', Schema.Long', foo, notFoo, Schema.Float, Schema.Boolean, Schema.Double, Schema.Bytes', bar])) Nothing
        ]
      scalarsDefault  = Just $ Avro.Union (V.fromList [Schema.String', Schema.Long']) Schema.String' (Avro.String "foo")
      nullableDefault = Just $ Avro.Union (V.fromList [Schema.Null, Schema.Int'])    Schema.Null   Avro.Null

      fooSchema = record "haskell.avro.example.Foo" [field "stuff" Schema.String' Nothing]
      barSchema = record "haskell.avro.example.Bar"
        [ field "stuff"  Schema.String' Nothing
        , field "things" (named "haskell.avro.example.Foo") Nothing
        ]
      notFooSchema = record "haskell.avro.example.NotFoo" [field "stuff" Schema.String' Nothing]

      array = Schema.Array { Schema.item = Schema.String' }
      map   = Schema.Map { Schema.values = Schema.Long' }

  unionsSchemaFile <- runIO $ getFileName "test/data/unions.avsc" >>= LBS.readFile
  let Just unionsSchemaFromJSON = Aeson.decode unionsSchemaFile

  it "produces valid schemas" $ do
    schema'Unions        `shouldBe` expectedSchema
    unionsSchemaFromJSON `shouldBe` expectedSchema
  it "records with unions should roundtrip" $ do
    fromAvro (toAvro objA) `shouldBe` pure objA
    fromAvro (toAvro objB) `shouldBe` pure objB

getFileName :: FilePath -> IO FilePath
getFileName p = do
  path <- getDataFileName p
  isOk <- doesFileExist path
  pure $ if isOk then path else p