{-# LANGUAGE OverloadedStrings #-} module Main ( main, ) where import Data.Avro.Schema import Data.Text as T hiding (tail) import Data.Vector (fromList) import Language.Avro.Parser import Language.Avro.Types import Test.Hspec import Text.Megaparsec (parse) enumTest :: T.Text enumTest = T.unlines [ "@aliases([\"org.foo.KindOf\"])", "enum Kind {", "FOO,", "BAR, // the bar enum value", "BAZ", "}" ] simpleProtocol :: [T.Text] simpleProtocol = [ "@namespace(\"example.seed.server.protocol.avro\")", "protocol PeopleService {", "import idl \"People.avdl\";", "example.seed.server.protocol.avro.PeopleResponse getPerson(example.seed.server.protocol.avro.PeopleRequest request);", "}" ] simpleRecord :: T.Text simpleRecord = T.unlines [ "@aliases([\"org.foo.Person\"])", "record Person {", "string name;", "int age;", "}" ] complexRecord :: T.Text complexRecord = T.unlines [ "record TestRecord {", "@order(\"ignore\")", "string name;", "@order(\"descending\")", "Kind kind;", "MD5 hash;", "union { MD5, null} @aliases([\"hash\"]) nullableHash;", "array arrayOfLongs;", "}" ] main :: IO () main = hspec $ do describe "Parse annotations" $ do it "should parse namespaces" $ do parse parseNamespace "" "@namespace(\"mynamespace\")" `shouldBe` (Right $ Namespace ["mynamespace"]) parse parseNamespace "" "@namespace(\"org.apache.avro.test\")" `shouldBe` (Right $ Namespace ["org", "apache", "avro", "test"]) it "should parse ordering" $ do parse parseOrder "" "@order(\"ascending\")" `shouldBe` Right Ascending parse parseOrder "" "@order(\"descending\")" `shouldBe` Right Descending parse parseOrder "" "@order(\"ignore\")" `shouldBe` Right Ignore it "should parse aliases" $ do parse parseAliases "" "@aliases([\"org.foo.KindOf\"])" `shouldBe` Right [TN "KindOf" ["org", "foo"]] parse parseAliases "" "@aliases([\"org.old.OldRecord\", \"org.ancient.AncientRecord\"])" `shouldBe` Right [TN "OldRecord" ["org", "old"], TN "AncientRecord" ["org", "ancient"]] it "should parse other annotations" $ do parse parseAnnotation "" "@java-class(\"java.util.ArrayList\")" `shouldBe` (Right $ Annotation "java-class" "java.util.ArrayList") parse parseAnnotation "" "@java-key-class(\"java.io.File\")" `shouldBe` (Right $ Annotation "java-key-class" "java.io.File") describe "Parse imports" $ do it "should parse idl" $ parse parseImport "" "import idl \"foo.avdl\";" `shouldBe` (Right $ IdlImport "foo.avdl") it "should parse protocol" $ parse parseImport "" "import protocol \"foo.avpr\";" `shouldBe` (Right $ ProtocolImport "foo.avpr") it "should parse schema" $ parse parseImport "" "import schema \"foo.avsc\";" `shouldBe` (Right $ SchemaImport "foo.avsc") describe "Parse Data.Avro.Schema" $ do it "should parse null" $ parse parseSchema "" "null" `shouldBe` Right Null it "should parse boolean" $ parse parseSchema "" "boolean" `shouldBe` Right Boolean it "should parse int" $ parse parseSchema "" "int" `shouldBe` Right Int it "should parse long" $ parse parseSchema "" "long" `shouldBe` Right Long it "should parse float" $ parse parseSchema "" "float" `shouldBe` Right Float it "should parse double" $ parse parseSchema "" "double" `shouldBe` Right Double it "should parse bytes" $ parse parseSchema "" "bytes" `shouldBe` Right Bytes it "should parse string" $ parse parseSchema "" "string" `shouldBe` Right String it "should parse array" $ do parse parseSchema "" "array" `shouldBe` (Right $ Array Int) parse parseSchema "" "array>" `shouldBe` (Right $ Array $ Array String) it "should parse map" $ do parse parseSchema "" "map" `shouldBe` (Right $ Map Int) parse parseSchema "" "map>" `shouldBe` (Right $ Map $ Map String) it "should parse unions" $ parse parseSchema "" "union { string, int, null }" `shouldBe` (Right $ Union $ fromList [String, Int, Null]) it "should parse fixeds" $ do parse parseSchema "" "fixed MD5(16)" `shouldBe` (Right $ Fixed (TN "MD5" []) [] 16) parse parseSchema "" "@aliases([\"org.foo.MD5\"])\nfixed MD5(16)" `shouldBe` (Right $ Fixed (TN "MD5" []) ["org.foo.MD5"] 16) it "should parse enums" $ do parse parseSchema "" enumTest `shouldBe` (Right $ Enum (TN "Kind" []) [TN "KindOf" ["org", "foo"]] Nothing (fromList ["FOO", "BAR", "BAZ"])) parse parseSchema "" "enum Suit { SPADES, DIAMONDS, CLUBS, HEARTS }" `shouldBe` (Right $ Enum (TN "Suit" []) [] Nothing (fromList ["SPADES", "DIAMONDS", "CLUBS", "HEARTS"])) it "should parse named types" $ parse parseSchema "" "example.seed.server.protocol.avro.PeopleResponse" `shouldBe` ( Right $ NamedType $ TN { baseName = "PeopleResponse", namespace = ["example", "seed", "server", "protocol", "avro"] } ) it "should parse simple records" $ parse parseSchema "" simpleRecord `shouldBe` ( Right $ Record (TN "Person" []) [TN "Person" ["org", "foo"]] Nothing -- docs are ignored for now... Nothing -- order is ignored for now... [ Field "name" [] Nothing Nothing String Nothing, Field "age" [] Nothing Nothing Int Nothing ] ) it "should parse complex records" $ parse parseSchema "" complexRecord `shouldBe` ( Right $ Record (TN "TestRecord" []) [] Nothing -- docs are ignored for now... Nothing -- order is ignored for now... [ Field "name" [] Nothing (Just Ignore) String Nothing, Field "kind" [] Nothing (Just Descending) (NamedType "Kind") Nothing, Field "hash" [] Nothing Nothing (NamedType "MD5") Nothing, Field "nullableHash" ["hash"] Nothing Nothing (Union $ fromList [NamedType "MD5", Null]) Nothing, Field "arrayOfLongs" [] Nothing Nothing (Array Long) Nothing ] ) describe "Parse protocols" $ do let getPerson = Method "getPerson" [Argument (NamedType "example.seed.server.protocol.avro.PeopleRequest") "request"] (NamedType "example.seed.server.protocol.avro.PeopleResponse") Null False it "should parse with imports" $ parse parseProtocol "" (T.unlines . tail $ simpleProtocol) `shouldBe` ( Right $ Protocol Nothing "PeopleService" [IdlImport "People.avdl"] [] [getPerson] ) it "should parse with namespace" $ parse parseProtocol "" (T.unlines simpleProtocol) `shouldBe` ( Right $ Protocol (Just (Namespace ["example", "seed", "server", "protocol", "avro"])) "PeopleService" [IdlImport "People.avdl"] [] [getPerson] ) describe "Parse services" $ do it "should parse simple messages" $ parse parseMethod "" "string hello(string greeting);" `shouldBe` (Right $ Method "hello" [Argument String "greeting"] String Null False) it "should parse more simple messages" $ parse parseMethod "" "bytes echoBytes(bytes data);" `shouldBe` (Right $ Method "echoBytes" [Argument Bytes "data"] Bytes Null False) it "should parse custom type messages" $ let custom = NamedType "TestRecord" in parse parseMethod "" "TestRecord echo(TestRecord `record`);" `shouldBe` (Right $ Method "echo" [Argument custom "record"] custom Null False) it "should parse multiple argument messages" $ parse parseMethod "" "int add(int arg1, int arg2);" `shouldBe` (Right $ Method "add" [Argument Int "arg1", Argument Int "arg2"] Int Null False) it "should parse escaped and throwing messages" $ parse parseMethod "" "void `error`() throws TestError;" `shouldBe` (Right $ Method "error" [] Null (NamedType $ TN "TestError" []) False) it "should parse oneway messages" $ parse parseMethod "" "void ping() oneway;" `shouldBe` (Right $ Method "ping" [] Null Null True)