mu-avro 0.3.0.0 → 0.4.0.0
raw patch · 5 files changed
+132/−149 lines, 5 filesdep +transformersdep ~aesondep ~avrodep ~bytestringPVP ok
version bump matches the API change (PVP)
Dependencies added: transformers
Dependency ranges changed: aeson, avro, bytestring, containers, deepseq, language-avro, mu-avro, mu-rpc, mu-schema, sop-core, tagged, template-haskell, text, time, unordered-containers, uuid, vector
API changes (from Hackage documentation)
- Mu.Adapter.Avro: instance forall tn fn (r :: Mu.Schema.Definition.Schema tn fn) (d :: Mu.Schema.Definition.TypeDef tn fn) (ds :: Mu.Schema.Definition.Schema tn fn). (Mu.Adapter.Avro.HasAvroSchema' (Mu.Schema.Interpretation.Term r d), Mu.Adapter.Avro.HasAvroSchemas r ds) => Mu.Adapter.Avro.HasAvroSchemas r (d : ds)
- Mu.Adapter.Avro: instance forall tn fn (r :: Mu.Schema.Definition.Schema tn fn). Mu.Adapter.Avro.HasAvroSchemas r '[]
- Mu.Adapter.Avro: instance forall tn fn (sch :: Mu.Schema.Definition.Schema tn fn) (sty :: tn) t. (Mu.Schema.Class.ToSchema sch sty t, Data.Avro.Encoding.ToAvro.ToAvro (Mu.Schema.Interpretation.Term sch (sch Mu.Schema.Definition.:/: sty)), GHC.TypeNats.KnownNat (Mu.Adapter.Avro.IxOf sch sty)) => Data.Avro.Encoding.ToAvro.ToAvro (Mu.Schema.Class.WithSchema sch sty t)
- Mu.Adapter.Avro: instance forall tn fn (sch :: Mu.Schema.Definition.Schema tn fn) (sty :: tn) t. Mu.Adapter.Avro.HasAvroSchemas sch sch => Data.Avro.HasAvroSchema.HasAvroSchema (Mu.Schema.Class.WithSchema sch sty t)
+ Mu.Adapter.Avro: instance forall tn fn (sch :: Mu.Schema.Definition.Schema tn fn) (sty :: tn) t. (Mu.Schema.Class.ToSchema sch sty t, Data.Avro.Encoding.ToAvro.ToAvro (Mu.Schema.Interpretation.Term sch (sch Mu.Schema.Definition.:/: sty))) => Data.Avro.Encoding.ToAvro.ToAvro (Mu.Schema.Class.WithSchema sch sty t)
+ Mu.Adapter.Avro: instance forall tn fn (sch :: Mu.Schema.Definition.Schema tn fn) (sty :: tn) t. Mu.Adapter.Avro.HasAvroSchema' (Mu.Schema.Interpretation.Term sch (sch Mu.Schema.Definition.:/: sty)) => Data.Avro.HasAvroSchema.HasAvroSchema (Mu.Schema.Class.WithSchema sch sty t)
+ Mu.Quasi.Avro.Example: type ExampleProtocol = '[ 'DRecord "DuplicatedPersonError" '[ 'FieldDef "message" ('TPrimitive Text)], 'DRecord "NotFoundError" '[ 'FieldDef "message" ('TPrimitive Text)], 'DRecord "PeopleRequest" '[ 'FieldDef "name" ('TPrimitive Text)], 'DRecord "PeopleResponse" '[ 'FieldDef "result" ('TUnion '[ 'TSchematic "Person", 'TSchematic "NotFoundError", 'TSchematic "DuplicatedPersonError"])], 'DRecord "Person" '[ 'FieldDef "name" ('TPrimitive Text), 'FieldDef "age" ('TPrimitive Int32)]]
+ Mu.Quasi.Avro.Example: type ExampleService = 'Package ('Just "example.seed.server.protocol.avro") '[ 'Service "Service" '[ 'Method "getPerson" '[ 'ArgSingle ('Nothing :: Maybe Symbol) ('SchemaRef ExampleProtocol "PeopleRequest")] ('RetSingle ('SchemaRef ExampleProtocol "PeopleResponse"))]]
- Mu.Quasi.Avro.Example: type ExampleFromFile = ('[ 'DEnum "gender" '[ 'ChoiceDef "male", 'ChoiceDef "female", 'ChoiceDef "nb"], 'DRecord "address" '[ 'FieldDef "postcode" ('TPrimitive Text), 'FieldDef "country" ('TPrimitive Text)], 'DRecord "person" '[ 'FieldDef "firstName" ('TPrimitive Text), 'FieldDef "lastName" ('TPrimitive Text), 'FieldDef "age" ('TOption ('TPrimitive Int64)), 'FieldDef "gender" ('TOption ('TSchematic "gender")), 'FieldDef "address" ('TSchematic "address"), 'FieldDef "lucky_numbers" ('TList ('TPrimitive Int64))]])
+ Mu.Quasi.Avro.Example: type ExampleFromFile = ('[ 'DRecord "person" '[ 'FieldDef "firstName" ('TPrimitive Text), 'FieldDef "lastName" ('TPrimitive Text), 'FieldDef "age" ('TOption ('TPrimitive Int64)), 'FieldDef "gender" ('TOption ('TSchematic "gender")), 'FieldDef "address" ('TSchematic "address"), 'FieldDef "lucky_numbers" ('TList ('TPrimitive Int64))], 'DEnum "gender" '[ 'ChoiceDef "male", 'ChoiceDef "female", 'ChoiceDef "nb"], 'DRecord "address" '[ 'FieldDef "postcode" ('TPrimitive Text), 'FieldDef "country" ('TPrimitive Text)]])
Files
- mu-avro.cabal +23/−22
- src/Mu/Adapter/Avro.hs +66/−95
- src/Mu/Quasi/Avro.hs +7/−5
- src/Mu/Quasi/Avro/Example.hs +13/−4
- test/avro/example.avsc +23/−23
mu-avro.cabal view
@@ -1,5 +1,5 @@ name: mu-avro-version: 0.3.0.0+version: 0.4.0.0 synopsis: Avro serialization support for Mu microservices description: You can use @mu-avro@ to read AVRO Schema Declarations for mu-haskell@@ -28,23 +28,24 @@ Mu.Quasi.Avro.Example build-depends:- aeson- , avro >=0.5.1- , base >=4.12 && <5- , bytestring- , containers- , deepseq- , language-avro >=0.1.3.1- , mu-rpc >=0.3.0- , mu-schema >=0.3.0- , sop-core- , tagged- , template-haskell >=2.12- , text- , time- , unordered-containers- , uuid- , vector+ aeson >=1.4 && <2+ , avro >=0.5.1 && <0.6+ , base >=4.12 && <5+ , bytestring >=0.10 && <0.11+ , containers >=0.6 && <0.7+ , deepseq >=1.4 && <2+ , language-avro >=0.1.3 && <0.2+ , mu-rpc ==0.4.*+ , mu-schema ==0.3.*+ , sop-core >=0.5.0 && <0.6+ , tagged >=0.8.6 && <0.9+ , template-haskell >=2.14 && <2.16+ , text >=1.2 && <2+ , time >=1.9 && <2+ , transformers >=0.5 && <0.6+ , unordered-containers >=0.2 && <0.3+ , uuid >=1.3 && <2+ , vector >=0.12 && <0.13 hs-source-dirs: src default-language: Haskell2010@@ -53,11 +54,11 @@ executable test-avro main-is: Avro.hs build-depends:- avro >=0.5+ avro >=0.5.1 && <0.6 , base >=4.12 && <5- , bytestring- , mu-avro >=0.3.0- , mu-schema >=0.3.0+ , bytestring >=0.10 && <0.11+ , mu-avro+ , mu-schema ==0.3.* hs-source-dirs: test default-language: Haskell2010
src/Mu/Adapter/Avro.hs view
@@ -21,7 +21,6 @@ -} module Mu.Adapter.Avro () where -import Control.Applicative ((<|>)) import Control.Arrow ((***)) import qualified Data.Avro as A import qualified Data.Avro.Encoding.FromAvro as AVal@@ -30,11 +29,11 @@ import qualified Data.Avro.Schema.Schema as ASch -- 'Tagged . unTagged' can be replaced by 'coerce' -- eliminating some run-time overhead+import Control.Monad.Trans.State import Data.Avro.EitherN (putIndexedValue) import Data.ByteString.Builder (Builder, word8) import Data.Coerce (coerce) import qualified Data.HashMap.Strict as HM-import Data.List ((\\)) import Data.List.NonEmpty (NonEmpty (..)) import qualified Data.List.NonEmpty as NonEmptyList import qualified Data.Map as M@@ -51,8 +50,9 @@ toSchemalessTerm (AVal.Record s r) = case s of RSch.Record { RSch.fields = fs }- -> SLess.TRecord $ map (\(k,v) -> SLess.Field k (SLess.toSchemalessValue v))- $ zip (map RSch.fldName fs) (V.toList r)+ -> SLess.TRecord $+ zipWith (\k v -> SLess.Field k (SLess.toSchemalessValue v))+ (map RSch.fldName fs) (V.toList r) _ -> error ("this should never happen:\n" ++ show s) toSchemalessTerm (AVal.Enum _ i _) = SLess.TEnum i@@ -83,148 +83,118 @@ toSchemalessValue e@AVal.Enum {} = SLess.FSchematic (SLess.toSchemalessTerm e) -instance (HasAvroSchemas sch sch)+instance (HasAvroSchema' (Term sch (sch :/: sty))) => A.HasAvroSchema (WithSchema sch sty t) where- schema = Tagged $ ASch.Union $ schemas (Proxy @sch) (Proxy @sch) ts- where ts = typeNames (Proxy @sch) (Proxy @sch)+ schema = coerce $ evalState (schema' @(Term sch (sch :/: sty))) [] instance ( FromSchema sch sty t , A.FromAvro (Term sch (sch :/: sty)) ) => A.FromAvro (WithSchema sch sty t) where- fromAvro entire@(AVal.Union _ _ v)- = -- remove first layer of union- WithSchema . fromSchema' @_ @_ @sch <$> AVal.fromAvro v- <|> -- try with the entire thing- WithSchema . fromSchema' @_ @_ @sch <$> AVal.fromAvro entire fromAvro entire- = WithSchema . fromSchema' @_ @_ @sch <$> AVal.fromAvro entire+ = WithSchema . fromSchema' @_ @_ @sch <$> AVal.fromAvro entire instance ( ToSchema sch sty t- , A.ToAvro (Term sch (sch :/: sty))- , KnownNat (IxOf sch sty) )+ , A.ToAvro (Term sch (sch :/: sty)) ) => A.ToAvro (WithSchema sch sty t) where- toAvro (ASch.Union vs) (WithSchema v)- = putIndexedValue (fromInteger $ natVal (Proxy @(IxOf sch sty)))- vs- (toSchema' @_ @_ @sch v)- toAvro s _ = error ("this should never happen:\n" ++ show s)--class HasAvroSchemas (r :: Schema tn fn) (sch :: Schema tn fn) where- schemas :: Proxy r -> Proxy sch -> [ASch.TypeName] -> V.Vector ASch.Schema- typeNames :: Proxy r -> Proxy sch -> [ASch.TypeName]-instance HasAvroSchemas r '[] where- schemas _ _ _ = V.empty- typeNames _ _ = []-instance forall r d ds.- (HasAvroSchema' (Term r d), HasAvroSchemas r ds)- => HasAvroSchemas r (d ': ds) where- schemas pr _ tys = V.cons thisSchema (schemas pr (Proxy @ds) tys)- where thisSchema = unTagged $ schema' @(Term r d) (tys \\ typeName' (Proxy @(Term r d)))- typeNames pr _ = typeName' (Proxy @(Term r d)) ++ typeNames pr (Proxy @ds)--type family IxOf (sch :: Schema tn fn) (sty :: tn) :: Nat where- IxOf ('DRecord nm fs ': rest) nm = 0- IxOf ('DEnum nm cs ': rest) nm = 0- IxOf (other ': rest) nm = 1 + IxOf rest nm+ toAvro sch (WithSchema v)+ = A.toAvro sch (toSchema' @_ @_ @sch v) -- HasAvroSchema instances class HasAvroSchema' x where- typeName' :: Proxy x -> [ASch.TypeName]- schema' :: [ASch.TypeName] -> Tagged x ASch.Schema+ schema' :: State [ASch.TypeName] (Tagged x ASch.Schema) instance TypeError ('Text "you should never use HasAvroSchema directly on Term, use WithSchema") => A.HasAvroSchema (Term sch t) where schema = error "this should never happen" instance HasAvroSchema' (FieldValue sch t) => A.HasAvroSchema (FieldValue sch t) where- schema = schema' []+ schema = evalState schema' [] instance (KnownName name, HasAvroSchemaFields sch args) => HasAvroSchema' (Term sch ('DRecord name args)) where- typeName' _ = [nameTypeName (Proxy @name)]- schema' visited- = if recordName `elem` visited- then Tagged $ ASch.NamedType recordName- else Tagged $ ASch.Record recordName [] Nothing fields- where recordName = nameTypeName (Proxy @name)- fields = schemaF (Proxy @sch) (Proxy @args) visited+ schema'+ = do let recordName = nameTypeName (Proxy @name)+ visited <- gets (recordName `elem`)+ if visited+ then pure $ Tagged $ ASch.NamedType recordName+ else do modify (recordName :)+ fields <- schemaF (Proxy @sch) (Proxy @args)+ pure $ Tagged $ ASch.Record recordName [] Nothing fields instance (KnownName name, HasAvroSchemaEnum choices) => HasAvroSchema' (Term sch ('DEnum name choices)) where- typeName' _ = [nameTypeName (Proxy @name)]- schema' visited- = if enumName `elem` visited- then Tagged $ ASch.NamedType enumName- else Tagged $ ASch.mkEnum enumName [] Nothing choicesNames- where enumName = nameTypeName (Proxy @name)- choicesNames = schemaE (Proxy @choices)+ schema'+ = do let enumName = nameTypeName (Proxy @name)+ choicesNames = schemaE (Proxy @choices)+ visited <- gets (enumName `elem`)+ if visited+ then pure $ Tagged $ ASch.NamedType enumName+ else do modify (enumName :)+ pure $ Tagged $ ASch.mkEnum enumName [] Nothing choicesNames+ instance HasAvroSchema' (FieldValue sch t) => HasAvroSchema' (Term sch ('DSimple t)) where- typeName' _ = []- schema' visited = coerce $ schema' @(FieldValue sch t) visited+ schema' = coerce <$> schema' @(FieldValue sch t) instance HasAvroSchema' (FieldValue sch 'TNull) where- typeName' _ = []- schema' _ = Tagged ASch.Null+ schema' = pure $ Tagged ASch.Null instance A.HasAvroSchema t => HasAvroSchema' (FieldValue sch ('TPrimitive t)) where- typeName' _ = []- schema' _ = coerce $ A.schema @t+ schema' = pure $ coerce $ A.schema @t instance (HasAvroSchema' (Term sch (sch :/: t))) => HasAvroSchema' (FieldValue sch ('TSchematic t)) where- typeName' _ = []- schema' visited = coerce $ schema' @(Term sch (sch :/: t)) visited+ schema' = coerce <$> schema' @(Term sch (sch :/: t)) instance forall sch choices. HasAvroSchemaUnion (FieldValue sch) choices => HasAvroSchema' (FieldValue sch ('TUnion choices)) where- typeName' _ = []- schema' visited- = Tagged $ ASch.mkUnion $ schemaU (Proxy @(FieldValue sch)) (Proxy @choices) visited+ schema' = do+ schs <- schemaU (Proxy @(FieldValue sch)) (Proxy @choices)+ pure $ Tagged $ ASch.mkUnion schs instance HasAvroSchema' (FieldValue sch t) => HasAvroSchema' (FieldValue sch ('TOption t)) where- typeName' _ = []- schema' visited- = Tagged $ ASch.mkUnion $ ASch.Null :| [iSchema]- where iSchema = unTagged $ schema' @(FieldValue sch t) visited+ schema' = do+ iSchema <- unTagged <$> schema' @(FieldValue sch t)+ pure $ Tagged $ ASch.mkUnion $ ASch.Null :| [iSchema] instance HasAvroSchema' (FieldValue sch t) => HasAvroSchema' (FieldValue sch ('TList t)) where- typeName' _ = []- schema' visited- = Tagged $ ASch.Array iSchema- where iSchema = unTagged $ schema' @(FieldValue sch t) visited+ schema' = do+ iSchema <- unTagged <$> schema' @(FieldValue sch t)+ pure $ Tagged $ ASch.Array iSchema -- These are the only two versions of Map supported by the library instance HasAvroSchema' (FieldValue sch v) => HasAvroSchema' (FieldValue sch ('TMap ('TPrimitive T.Text) v)) where- typeName' _ = []- schema' visited- = Tagged $ ASch.Map iSchema- where iSchema = unTagged $ schema' @(FieldValue sch v) visited+ schema' = do+ iSchema <- unTagged <$> schema' @(FieldValue sch v)+ pure $ Tagged $ ASch.Map iSchema instance HasAvroSchema' (FieldValue sch v) => HasAvroSchema' (FieldValue sch ('TMap ('TPrimitive String) v)) where- typeName' _ = []- schema' visited- = Tagged $ ASch.Map iSchema- where iSchema = unTagged $ schema' @(FieldValue sch v) visited+ schema' = do+ iSchema <- unTagged <$> schema' @(FieldValue sch v)+ pure $ Tagged $ ASch.Map iSchema class HasAvroSchemaUnion (f :: k -> *) (xs :: [k]) where- schemaU :: Proxy f -> Proxy xs -> [ASch.TypeName] -> NonEmpty ASch.Schema+ schemaU :: Proxy f -> Proxy xs -> State [ASch.TypeName] (NonEmpty ASch.Schema) instance HasAvroSchema' (f v) => HasAvroSchemaUnion f '[v] where- schemaU _ _ visited = vSchema :| []- where vSchema = unTagged (schema' @(f v) visited)+ schemaU _ _ = do+ vSchema <- unTagged <$> schema' @(f v)+ pure $ vSchema :| [] instance (HasAvroSchema' (f x), HasAvroSchemaUnion f (y ': zs)) => HasAvroSchemaUnion f (x ': y ': zs) where- schemaU p _ visited = xSchema :| NonEmptyList.toList yzsSchema- where xSchema = unTagged (schema' @(f x) visited)- yzsSchema = schemaU p (Proxy @(y ': zs)) visited+ schemaU p _ = do+ xSchema <- unTagged <$> schema' @(f x)+ yzsSchema <- schemaU p (Proxy @(y ': zs))+ pure $ xSchema :| NonEmptyList.toList yzsSchema class HasAvroSchemaFields sch (fs :: [FieldDef tn fn]) where- schemaF :: Proxy sch -> Proxy fs -> [ASch.TypeName] -> [ASch.Field]+ schemaF :: Proxy sch -> Proxy fs -> State [ASch.TypeName] [ASch.Field] instance HasAvroSchemaFields sch '[] where- schemaF _ _ _ = []+ schemaF _ _ = pure [] instance (KnownName name, HasAvroSchema' (FieldValue sch t), HasAvroSchemaFields sch fs) => HasAvroSchemaFields sch ('FieldDef name t ': fs) where- schemaF psch _ visited = schemaThis : schemaF psch (Proxy @fs) visited- where fieldName = nameText (Proxy @name)- schemaT = unTagged $ schema' @(FieldValue sch t) visited- schemaThis = ASch.Field fieldName [] Nothing Nothing schemaT Nothing+ schemaF psch _ = do+ let fieldName = nameText (Proxy @name)+ schemaT <- unTagged <$> schema' @(FieldValue sch t)+ let schemaThis = ASch.Field fieldName [] Nothing Nothing schemaT Nothing+ rest <- schemaF psch (Proxy @fs)+ pure $ schemaThis : rest class HasAvroSchemaEnum (fs :: [ChoiceDef fn]) where schemaE :: Proxy fs -> [T.Text]@@ -317,7 +287,8 @@ = A.record s $ toAvroF fields -- if we don't have a record, fall back to the one from schema toAvro _ (TRecord fields)- = A.record (unTagged $ schema' @(Term sch ('DRecord name args)) []) $ toAvroF fields+ = A.record sch (toAvroF fields)+ where sch = unTagged $ evalState (schema' @(Term sch ('DRecord name args))) [] instance (KnownName name, ToAvroEnum choices, HasAvroSchemaEnum choices) => A.ToAvro (Term sch ('DEnum name choices)) where toAvro ASch.Enum { ASch.symbols = ss } (TEnum n)
src/Mu/Quasi/Avro.hs view
@@ -1,4 +1,5 @@ {-# language DataKinds #-}+{-# language KindSignatures #-} {-# language LambdaCase #-} {-# language NamedFieldPuns #-} {-# language OverloadedStrings #-}@@ -39,6 +40,7 @@ import Data.Time.Millis import Data.UUID import qualified Data.Vector as V+import GHC.TypeLits import Language.Avro.Parser import qualified Language.Avro.Types as A import Language.Haskell.TH@@ -88,12 +90,12 @@ schemaDec <- tySynD schemaName' [] (schemaFromAvro $ S.toList (A.types protocol)) serviceDec <- tySynD serviceName' [] [t| 'Package $(pkgType (A.ns protocol))- '[ 'Service $(textToStrLit (A.pname protocol)) '[]+ '[ 'Service $(textToStrLit (A.pname protocol)) $(typesToList <$> mapM (avroMethodToType schemaName') (S.toList $ A.messages protocol)) ] |] pure [schemaDec, serviceDec] where- pkgType Nothing = [t| 'Nothing |]+ pkgType Nothing = [t| ('Nothing :: Maybe Symbol) |] pkgType (Just (A.Namespace p)) = [t| 'Just $(textToStrLit (T.intercalate "." p)) |] @@ -176,13 +178,13 @@ avroMethodToType :: Name -> A.Method -> Q Type avroMethodToType schemaName m- = [t| 'Method $(textToStrLit (A.mname m)) '[]+ = [t| 'Method $(textToStrLit (A.mname m)) $(typesToList <$> mapM argToType (A.args m)) $(retToType (A.result m)) |] where argToType :: A.Argument -> Q Type argToType (A.Argument (A.NamedType a) _)- = [t| 'ArgSingle 'Nothing '[] ('SchemaRef $(conT schemaName) $(textToStrLit (A.baseName a))) |]+ = [t| 'ArgSingle ('Nothing :: Maybe Symbol) ('SchemaRef $(conT schemaName) $(textToStrLit (A.baseName a))) |] argToType (A.Argument _ _) = fail "only named types may be used as arguments" @@ -195,7 +197,7 @@ = fail "only named types may be used as results" typesToList :: [Type] -> Type-typesToList = foldr (\y ys -> AppT (AppT PromotedConsT y) ys) PromotedNilT+typesToList = foldr (AppT . AppT PromotedConsT) PromotedNilT textToStrLit :: T.Text -> Q Type textToStrLit s = litT $ strTyLit $ T.unpack s
src/Mu/Quasi/Avro/Example.hs view
@@ -1,6 +1,9 @@-{-# language CPP #-}-{-# language DataKinds #-}-{-# language QuasiQuotes #-}+{-# language CPP #-}+{-# language DataKinds #-}+{-# language KindSignatures #-}+{-# language QuasiQuotes #-}+{-# language TemplateHaskell #-}+ {-| Description : Examples for Avro quasi-quoters @@ -8,7 +11,7 @@ -} module Mu.Quasi.Avro.Example where -import Mu.Quasi.Avro (avro, avroFile)+import Mu.Quasi.Avro (avdl, avro, avroFile) type Example = [avro| {@@ -44,4 +47,10 @@ type ExampleFromFile = [avroFile|adapter/avro/test/avro/example.avsc|] #else type ExampleFromFile = [avroFile|test/avro/example.avsc|]+#endif++#if __GHCIDE__+avdl "ExampleProtocol" "ExampleService" "." "adapter/avro/test/avro/example.avdl"+#else+avdl "ExampleProtocol" "ExampleService" "." "test/avro/example.avdl" #endif
test/avro/example.avsc view
@@ -1,23 +1,23 @@-[ { "type": "enum",- "name": "gender",- "symbols" : ["male", "female", "nb"]- }-, { "type": "record"- , "name": "address"- , "fields": [- {"name": "postcode", "type": "string"},- {"name": "country", "type": "string"}- ]- }-,{ "type": "record",- "name": "person",- "fields": [- {"name": "firstName", "type": "string"},- {"name": "lastName", "type": "string"},- {"name": "age", "type": ["long", "null"]},- {"name": "gender", "type": ["gender", "null"]},- {"name": "address", "type": "address"},- {"name": "lucky_numbers", "type": { "type": "array", "items": "long" } }- ]- }-]+{ "type": "record",+ "name": "person",+ "fields": [+ {"name": "firstName", "type": "string"},+ {"name": "lastName", "type": "string"},+ {"name": "age", "type": ["long", "null"]},+ {"name": "gender", "type": [+ { "type": "enum",+ "name": "gender",+ "symbols" : ["male", "female", "nb"]+ } , "null"]},+ {"name": "address", "type":+ { "type": "record"+ , "name": "address"+ , "fields": [+ {"name": "postcode", "type": "string"},+ {"name": "country", "type": "string"}+ ]+ }+ },+ {"name": "lucky_numbers", "type": { "type": "array", "items": "long" } }+ ]+}