You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@avro.apache.org by mg...@apache.org on 2022/04/19 18:27:51 UTC

[avro] branch branch-1.11 updated: AVRO-2211: SchemaBuilder equivalent or other means of schema creation (#1597)

This is an automated email from the ASF dual-hosted git repository.

mgrigorov pushed a commit to branch branch-1.11
in repository https://gitbox.apache.org/repos/asf/avro.git


The following commit(s) were added to refs/heads/branch-1.11 by this push:
     new 972e33af7 AVRO-2211: SchemaBuilder equivalent or other means of schema creation (#1597)
972e33af7 is described below

commit 972e33af7a10c9a53feaf17ea60214fe4191f02e
Author: yanivru <ya...@gmail.com>
AuthorDate: Tue Apr 19 21:27:30 2022 +0300

    AVRO-2211: SchemaBuilder equivalent or other means of schema creation (#1597)
    
    * AVRO-2211: Support schema creation
    
    * Add license info to new files
    
    * Fix documentation for FixedSchema ctor
    
    * Remove and sort using
    
    * Add missing brackets and replace var with explicit type
    
    * Fix exception type in case of parsing
    
    * Rename field to follow conventions
    
    * AVRO 2211: Inlining temporary variable in linq
    
    * AVRO-2211: Change exception type and add missing documentations
    
    * AVRO-2211: Fix RecordSchema to set the positions of it's fields, instead of verifying it
    
    * AVRO-2211: Fix RecordSchema fields assignment when creation new RecordSchema
    
    * AVRO-2211: Change constructors of schema classes to factory method
    
    * AVRO-2211: Add unit tests for RecordSchema and EnumSchema
    
    * :AVRO-2211: Remove whitespace
    
    * :AVRO-2211: Add symbol names verification for EnumSchema
    
    * AVRO-2211: Fix enum name validation
    
    * AVRO-2211: Throw AvroException consistently
    
    * AVRO-2211: Throw AvroException in RecrodSchema consistently
    
    * AVRO-2211: Remove duplicate factory methods on MapSchema
    
    * AVRO-2211: Remove redundant parameter doc
    
    * AVRO-2211: Add Schema creation tests
    
    * AVRO-2211: Change ValidateSymbol to throw exception
    
    * AVRO-2211: Fix typo
    
    * AVRO-2211: Fix code QL issues
    
    * AVRO-2211: Fix typo
    
    Co-authored-by: Martin Grigorov <ma...@users.noreply.github.com>
    (cherry picked from commit 1c84472681c888a77fa7295b43d84e06a4294641)
---
 lang/csharp/src/apache/main/AssemblyInfo.cs        |  20 ++
 lang/csharp/src/apache/main/Schema/Aliases.cs      |  36 +++
 lang/csharp/src/apache/main/Schema/ArraySchema.cs  |  22 +-
 lang/csharp/src/apache/main/Schema/EnumSchema.cs   |  75 +++++-
 lang/csharp/src/apache/main/Schema/Field.cs        |  34 ++-
 lang/csharp/src/apache/main/Schema/FixedSchema.cs  |  14 ++
 lang/csharp/src/apache/main/Schema/MapSchema.cs    |  12 +-
 .../src/apache/main/Schema/PrimitiveSchema.cs      |  16 +-
 lang/csharp/src/apache/main/Schema/RecordSchema.cs | 144 +++++++++++-
 lang/csharp/src/apache/main/Schema/UnionSchema.cs  |  37 ++-
 lang/csharp/src/apache/test/Avro.test.csproj       |   2 +
 lang/csharp/src/apache/test/Schema/AliasesTests.cs |  49 ++++
 lang/csharp/src/apache/test/Schema/SchemaTests.cs  | 259 +++++++++++++++++++--
 13 files changed, 675 insertions(+), 45 deletions(-)

diff --git a/lang/csharp/src/apache/main/AssemblyInfo.cs b/lang/csharp/src/apache/main/AssemblyInfo.cs
new file mode 100644
index 000000000..53eacc1f9
--- /dev/null
+++ b/lang/csharp/src/apache/main/AssemblyInfo.cs
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Avro.test, PublicKey=00240000048000009400000006020000002400005253413100040000010001001145636d1b96168c2781abfd60478f45d010fe83dd0f318404cbf67252bca8cd827f24648d47ff682f35e60307c05d3cd89f0b063729cf8d2ebe6510b9e7d295dec6707ec91719d859458981f7ca1cbbea79b702b2fb64d1dbf0881887315345b70fa50fcf91b59e6a937c8d23919d409ee2f1f234cc4c8dbf5a29d3d670f3c9")]
diff --git a/lang/csharp/src/apache/main/Schema/Aliases.cs b/lang/csharp/src/apache/main/Schema/Aliases.cs
new file mode 100644
index 000000000..6574e3163
--- /dev/null
+++ b/lang/csharp/src/apache/main/Schema/Aliases.cs
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avro
+{
+    internal static class Aliases
+    {
+        internal static IList<SchemaName> GetSchemaNames(IEnumerable<string> aliases, string enclosingTypeName, string enclosingTypeNamespace)
+        {
+            if (aliases == null)
+            {
+                return null;
+            }
+
+            SchemaName enclosingSchemaName = new SchemaName(enclosingTypeName, enclosingTypeNamespace, null, null);
+            return aliases.Select(alias => new SchemaName(alias, enclosingSchemaName.Namespace, null, null)).ToList();
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/main/Schema/ArraySchema.cs b/lang/csharp/src/apache/main/Schema/ArraySchema.cs
index 5b4e6a434..03a331289 100644
--- a/lang/csharp/src/apache/main/Schema/ArraySchema.cs
+++ b/lang/csharp/src/apache/main/Schema/ArraySchema.cs
@@ -29,7 +29,7 @@ namespace Avro
         /// <summary>
         /// Schema for the array 'type' attribute
         /// </summary>
-        public Schema ItemSchema { get; set;  }
+        public Schema ItemSchema { get; set; }
 
         /// <summary>
         /// Static class to return a new instance of ArraySchema
@@ -48,11 +48,23 @@ namespace Avro
         }
 
         /// <summary>
-        /// Constructor
+        /// Creates a new <see cref="ArraySchema"/>
         /// </summary>
-        /// <param name="items">schema for the array items type</param>
-        /// <param name="props">dictionary that provides access to custom properties</param>
-        private ArraySchema(Schema items, PropertyMap props) : base(Type.Array, props)
+        /// <param name="items">Schema for the array items type</param>
+        /// <param name="customAttributes">Dictionary that provides access to custom properties</param>
+        /// <returns></returns>
+        public static ArraySchema Create(Schema items, PropertyMap customAttributes = null)
+        {
+            return new ArraySchema(items, customAttributes);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="ArraySchema"/> class.
+        /// </summary>
+        /// <param name="items">Schema for the array items type</param>
+        /// <param name="customAttributes">Dictionary that provides access to custom properties</param>
+        private ArraySchema(Schema items, PropertyMap customAttributes)
+            : base(Type.Array, customAttributes)
         {
             if (null == items) throw new ArgumentNullException(nameof(items));
             this.ItemSchema = items;
diff --git a/lang/csharp/src/apache/main/Schema/EnumSchema.cs b/lang/csharp/src/apache/main/Schema/EnumSchema.cs
index f128790d1..295b73ebd 100644
--- a/lang/csharp/src/apache/main/Schema/EnumSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/EnumSchema.cs
@@ -17,7 +17,8 @@
  */
 using System;
 using System.Collections.Generic;
-using System.Text;
+using System.Linq;
+using System.Text.RegularExpressions;
 using Newtonsoft.Json.Linq;
 
 namespace Avro
@@ -30,7 +31,7 @@ namespace Avro
         /// <summary>
         /// List of strings representing the enum symbols
         /// </summary>
-        public IList<string> Symbols { get; private set;  }
+        public IList<string> Symbols { get; private set; }
 
         /// <summary>
         /// The default token to use when deserializing an enum when the provided token is not found
@@ -47,6 +48,34 @@ namespace Avro
         /// </summary>
         public int Count { get { return Symbols.Count; } }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="EnumSchema"/> class.
+        /// </summary>
+        /// <param name="name">Name of enum</param>
+        /// <param name="space">Namespace of enum</param>
+        /// <param name="aliases">List of aliases for the name</param>
+        /// <param name="symbols">List of enum symbols</param>
+        /// <param name="customProperties">Custom properties on this schema</param>
+        /// <param name="doc">Documentation for this named schema</param>
+        /// <param name="defaultSymbol"></param>
+        public static EnumSchema Create(string name,
+            IEnumerable<string> symbols,
+            string space = null,
+            IEnumerable<string> aliases = null,
+            PropertyMap customProperties = null,
+            string doc = null,
+            string defaultSymbol = null)
+        {
+            return new EnumSchema(new SchemaName(name, space, null, doc),
+                  Aliases.GetSchemaNames(aliases, name, space),
+                  symbols.ToList(),
+                  CreateSymbolsMap(symbols),
+                  customProperties,
+                  new SchemaNames(),
+                  doc,
+                  defaultSymbol);
+        }
+
         /// <summary>
         /// Static function to return new instance of EnumSchema
         /// </summary>
@@ -81,7 +110,7 @@ namespace Avro
                 return new EnumSchema(name, aliases, symbols, symbolMap, props, names,
                     JsonHelper.GetOptionalString(jtok, "doc"), JsonHelper.GetOptionalString(jtok, "default"));
             }
-            catch (SchemaParseException e)
+            catch (AvroException e)
             {
                 throw new SchemaParseException($"{e.Message} at '{jtok.Path}'", e);
             }
@@ -103,15 +132,49 @@ namespace Avro
                             string doc, string defaultSymbol)
                             : base(Type.Enumeration, name, aliases, props, names, doc)
         {
-            if (null == name.Name) throw new SchemaParseException("name cannot be null for enum schema.");
+            if (null == name.Name) throw new AvroException("name cannot be null for enum schema.");
             this.Symbols = symbols;
             this.symbolMap = symbolMap;
 
             if (null != defaultSymbol && !symbolMap.ContainsKey(defaultSymbol))
-                throw new SchemaParseException($"Default symbol: {defaultSymbol} not found in symbols");
+                throw new AvroException($"Default symbol: {defaultSymbol} not found in symbols");
             Default = defaultSymbol;
         }
 
+        /// <summary>
+        /// Creates symbols map from specified list of symbols.
+        /// Symbol map contains the names of the symbols and their index.
+        /// </summary>
+        /// <param name="symbols">List of symbols</param>
+        /// <returns>Symbol map</returns>
+        /// <exception cref="AvroException">Is thrown if the symbols list contains invalid symbol name or duplicate symbols</exception>
+        private static IDictionary<string, int> CreateSymbolsMap(IEnumerable<string> symbols)
+        {
+            IDictionary<string, int> symbolMap = new Dictionary<string, int>();
+            int i = 0;
+            foreach (var symbol in symbols)
+            {
+                ValidateSymbolName(symbol);
+
+                if (symbolMap.ContainsKey(symbol))
+                {
+                    throw new AvroException($"Duplicate symbol: {symbol}");
+                }
+
+                symbolMap[symbol] = i++;
+            }
+
+            return symbolMap;
+        }
+
+        private static void ValidateSymbolName(string symbol)
+        {
+            if(string.IsNullOrEmpty(symbol) || !Regex.IsMatch(symbol, "^([A-Za-z_][A-Za-z0-9_]*)$"))
+            {
+                throw new AvroException($"Invalid symbol name: {symbol}");
+            }
+        }
+
         /// <summary>
         /// Writes enum schema in JSON format
         /// </summary>
@@ -127,7 +190,7 @@ namespace Avro
             foreach (string s in this.Symbols)
                 writer.WriteValue(s);
             writer.WriteEndArray();
-            if (null != Default) 
+            if (null != Default)
             {
                 writer.WritePropertyName("default");
                 writer.WriteValue(Default);
diff --git a/lang/csharp/src/apache/main/Schema/Field.cs b/lang/csharp/src/apache/main/Schema/Field.cs
index bdfe9282c..08ea03305 100644
--- a/lang/csharp/src/apache/main/Schema/Field.cs
+++ b/lang/csharp/src/apache/main/Schema/Field.cs
@@ -103,7 +103,39 @@ namespace Avro
         /// <summary>
         /// Static comparer object for JSON objects such as the fields default value
         /// </summary>
-        internal static JTokenEqualityComparer JtokenEqual = new JTokenEqualityComparer();
+        internal readonly static JTokenEqualityComparer JtokenEqual = new JTokenEqualityComparer();
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="Field"/> class.
+        /// </summary>
+        /// <param name="schema">schema for the field type.</param>
+        /// <param name="name">name of the field.</param>
+        /// <param name="aliases">list of aliases for the name of the field.</param>
+        /// <param name="pos">position of the field.</param>
+        /// <param name="doc">documentation for the field.</param>
+        /// <param name="defaultValue">field's default value if it exists.</param>
+        /// <param name="sortorder">sort order of the field.</param>
+        /// <param name="customProperties">dictionary that provides access to custom properties.</param>
+        public Field(Schema schema,
+            string name,
+            int pos,
+            IList<string> aliases = null,
+            string doc = null,
+            JToken defaultValue = null,
+            SortOrder sortorder = SortOrder.ignore,
+            PropertyMap customProperties = null)
+            : this(schema, name, aliases, pos, doc, defaultValue, sortorder, customProperties)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new field based on the specified field, with a different position.
+        /// </summary>
+        /// <returns>A clone of this field with new position.</returns>
+        internal Field ChangePosition(int newPosition)
+        {
+            return new Field(Schema, Name, newPosition, Aliases, Documentation, DefaultValue, Ordering ?? SortOrder.ignore, Props);
+        }
 
         /// <summary>
         /// A flag to indicate if reader schema has a field that is missing from writer schema and has a default value
diff --git a/lang/csharp/src/apache/main/Schema/FixedSchema.cs b/lang/csharp/src/apache/main/Schema/FixedSchema.cs
index b16c1ff1d..2b24e6b86 100644
--- a/lang/csharp/src/apache/main/Schema/FixedSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/FixedSchema.cs
@@ -32,6 +32,20 @@ namespace Avro
         /// </summary>
         public int Size { get; set; }
 
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FixedSchema"/> class.
+        /// </summary>
+        /// <param name="name">Name of the fixed schema</param>
+        /// <param name="aliases">List of aliases for the name</param>
+        /// <param name="size">Fixed size</param>
+        /// <param name="space">Namespace of fixed</param>
+        /// <param name="customProperties">Custom properties on this schema</param>
+        /// <param name="doc">Documentation for this named schema</param>
+        public static FixedSchema Create(string name, int size, string space = null, IEnumerable<string> aliases = null, PropertyMap customProperties = null, string doc = null)
+        {
+            return new FixedSchema(new SchemaName(name, space, null, doc), Aliases.GetSchemaNames(aliases, name, space), size, customProperties, new SchemaNames(), doc);
+        }
+
         /// <summary>
         /// Static function to return new instance of the fixed schema class
         /// </summary>
diff --git a/lang/csharp/src/apache/main/Schema/MapSchema.cs b/lang/csharp/src/apache/main/Schema/MapSchema.cs
index 54bc05a8d..d9f4995ab 100644
--- a/lang/csharp/src/apache/main/Schema/MapSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/MapSchema.cs
@@ -36,10 +36,11 @@ namespace Avro
         /// Creates a new <see cref="MapSchema"/> from the given schema.
         /// </summary>
         /// <param name="type">Schema to create the map schema from.</param>
+        /// <param name="customProperties">Dictionary that provides access to custom properties</param>
         /// <returns>A new <see cref="MapSchema"/>.</returns>
-        public static MapSchema CreateMap(Schema type)
+        public static MapSchema CreateMap(Schema type, PropertyMap customProperties = null)
         {
-            return new MapSchema(type,null);
+            return new MapSchema(type, customProperties);
         }
 
         /// <summary>
@@ -67,9 +68,10 @@ namespace Avro
         /// <summary>
         /// Constructor for map schema class
         /// </summary>
-        /// <param name="valueSchema">schema for map values type</param>
-        /// <param name="props">dictionary that provides access to custom properties</param>
-        private MapSchema(Schema valueSchema, PropertyMap props) : base(Type.Map, props)
+        /// <param name="valueSchema">Schema for map values type</param>
+        /// <param name="cutsomProperties">Dictionary that provides access to custom properties</param>
+        private MapSchema(Schema valueSchema, PropertyMap cutsomProperties)
+            : base(Type.Map, cutsomProperties)
         {
             if (null == valueSchema) throw new ArgumentNullException(nameof(valueSchema), "valueSchema cannot be null.");
             this.ValueSchema = valueSchema;
diff --git a/lang/csharp/src/apache/main/Schema/PrimitiveSchema.cs b/lang/csharp/src/apache/main/Schema/PrimitiveSchema.cs
index 23c2cee7c..db5db2cb0 100644
--- a/lang/csharp/src/apache/main/Schema/PrimitiveSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/PrimitiveSchema.cs
@@ -30,9 +30,21 @@ namespace Avro
         /// Constructor for primitive schema
         /// </summary>
         /// <param name="type"></param>
-        /// <param name="props">dictionary that provides access to custom properties</param>
-        private PrimitiveSchema(Type type, PropertyMap props) : base(type, props)
+        /// <param name="customProperties">dictionary that provides access to custom properties</param>
+        private PrimitiveSchema(Type type, PropertyMap customProperties)
+            : base(type, customProperties)
+        {
+        }
+
+        /// <summary>
+        /// Creates a new instance of <see cref="PrimitiveSchema"/>
+        /// </summary>
+        /// <param name="type">The primitive type to create</param>
+        /// <param name="customProperties">Dictionary that provides access to custom properties</param>
+        /// <returns></returns>
+        public static PrimitiveSchema Create(Type type, PropertyMap customProperties = null)
         {
+            return new PrimitiveSchema(type, customProperties);
         }
 
         /// <summary>
diff --git a/lang/csharp/src/apache/main/Schema/RecordSchema.cs b/lang/csharp/src/apache/main/Schema/RecordSchema.cs
index 356f8baf4..910bc466f 100644
--- a/lang/csharp/src/apache/main/Schema/RecordSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/RecordSchema.cs
@@ -17,6 +17,7 @@
  */
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Newtonsoft.Json.Linq;
 
 namespace Avro
@@ -28,10 +29,26 @@ namespace Avro
     /// </summary>
     public class RecordSchema : NamedSchema
     {
+        private List<Field> _fields;
+
         /// <summary>
         /// List of fields in the record
         /// </summary>
-        public List<Field> Fields { get; private set; }
+        public List<Field> Fields
+        {
+            get
+            {
+                return _fields;
+            }
+
+            set
+            {
+                _fields = SetFieldsPositions(value);
+
+                fieldLookup = CreateFieldMap(_fields);
+                fieldAliasLookup = CreateFieldMap(_fields, true);
+            }
+        }
 
         /// <summary>
         /// Number of fields in the record
@@ -41,10 +58,109 @@ namespace Avro
         /// <summary>
         /// Map of field name and Field object for faster field lookups
         /// </summary>
-        private readonly IDictionary<string, Field> fieldLookup;
+        private IDictionary<string, Field> fieldLookup;
 
-        private readonly IDictionary<string, Field> fieldAliasLookup;
-        private bool request;
+        private IDictionary<string, Field> fieldAliasLookup;
+        private readonly bool request;
+
+        /// <summary>
+        /// Creates a new instance of <see cref="RecordSchema"/>
+        /// </summary>
+        /// <param name="name">name of the record schema</param>
+        /// <param name="fields">list of fields for the record</param>
+        /// <param name="space">type of record schema, either record or error</param>
+        /// <param name="aliases">list of aliases for the record name</param>
+        /// <param name="customProperties">custom properties on this schema</param>
+        /// <param name="doc">documentation for this named schema</param>
+        public static RecordSchema Create(string name,
+            List<Field> fields,
+            string space = null,
+            IEnumerable<string> aliases = null,
+            PropertyMap customProperties = null,
+            string doc = null)
+        {
+            return new RecordSchema(Type.Record,
+                  new SchemaName(name, space, null, doc),
+                  Aliases.GetSchemaNames(aliases, name, space),
+                  customProperties,
+                  fields,
+                  false,
+                  CreateFieldMap(fields),
+                  CreateFieldMap(fields, true),
+                  new SchemaNames(),
+                  doc);
+        }
+
+        private static IEnumerable<Schema> EnumerateSchemasRecursive(Schema schema)
+        {
+            yield return schema;
+            switch (schema.Tag)
+            {
+                case Type.Null:
+                    break;
+                case Type.Boolean:
+                    break;
+                case Type.Int:
+                    break;
+                case Type.Long:
+                    break;
+                case Type.Float:
+                    break;
+                case Type.Double:
+                    break;
+                case Type.Bytes:
+                    break;
+                case Type.String:
+                    break;
+                case Type.Record:
+                    var recordSchema = (RecordSchema)schema;
+                    recordSchema.Fields.SelectMany(f => EnumerateSchemasRecursive(f.Schema));
+                    break;
+                case Type.Enumeration:
+                    break;
+                case Type.Array:
+                    var arraySchema = (ArraySchema)schema;
+                    EnumerateSchemasRecursive(arraySchema.ItemSchema);
+                    break;
+                case Type.Map:
+                    var mapSchema = (MapSchema)schema;
+                    EnumerateSchemasRecursive(mapSchema.ValueSchema);
+                    break;
+                case Type.Union:
+                    var unionSchema = (UnionSchema)schema;
+                    foreach (var innerSchema in unionSchema.Schemas)
+                    {
+                        EnumerateSchemasRecursive(innerSchema);
+                    }
+                    break;
+                case Type.Fixed:
+                    break;
+                case Type.Error:
+                    break;
+                case Type.Logical:
+                    break;
+            }
+        }
+
+        private static IDictionary<string, Field> CreateFieldMap(List<Field> fields, bool includeAliases = false)
+        {
+            var map = new Dictionary<string, Field>();
+            if (fields != null)
+            {
+                foreach (Field field in fields)
+                {
+                    addToFieldMap(map, field.Name, field);
+
+                    if (includeAliases && field.Aliases != null)
+                    {
+                        foreach (var alias in field.Aliases)
+                            addToFieldMap(map, alias, field);
+                    }
+                }
+            }
+
+            return map;
+        }
 
         /// <summary>
         /// Static function to return new instance of the record schema
@@ -99,8 +215,10 @@ namespace Avro
                     if (null != field.Aliases)    // add aliases to field lookup map so reader function will find it when writer field name appears only as an alias on the reader field
                         foreach (string alias in field.Aliases)
                             addToFieldMap(fieldAliasMap, alias, field);
+
+                    result._fields = fields;
                 }
-                catch (SchemaParseException e)
+                catch (AvroException e)
                 {
                     throw new SchemaParseException($"{e.Message} at '{jfield.Path}'", e);
                 }
@@ -121,7 +239,7 @@ namespace Avro
         /// <param name="fieldAliasMap">map of field aliases and field objects</param>
         /// <param name="names">list of named schema already read</param>
         /// <param name="doc">documentation for this named schema</param>
-        private RecordSchema(Type type, SchemaName name, IList<SchemaName> aliases,  PropertyMap props,
+        private RecordSchema(Type type, SchemaName name, IList<SchemaName> aliases, PropertyMap props,
                                 List<Field> fields, bool request, IDictionary<string, Field> fieldMap,
                                 IDictionary<string, Field> fieldAliasMap, SchemaNames names, string doc)
                                 : base(type, name, aliases, props, names, doc)
@@ -149,7 +267,7 @@ namespace Avro
             var jorder = JsonHelper.GetOptionalString(jfield, "order");
             Field.SortOrder sortorder = Field.SortOrder.ignore;
             if (null != jorder)
-                sortorder = (Field.SortOrder) Enum.Parse(typeof(Field.SortOrder), jorder);
+                sortorder = (Field.SortOrder)Enum.Parse(typeof(Field.SortOrder), jorder);
 
             var aliases = Field.GetAliases(jfield);
             var props = Schema.GetProperties(jfield);
@@ -165,10 +283,20 @@ namespace Avro
         private static void addToFieldMap(Dictionary<string, Field> map, string name, Field field)
         {
             if (map.ContainsKey(name))
-                throw new SchemaParseException("field or alias " + name + " is a duplicate name");
+                throw new AvroException("field or alias " + name + " is a duplicate name");
             map.Add(name, field);
         }
 
+        /// <summary>
+        /// Clones the fields with updated positions. Updates the positions according to the order of the fields in the list.
+        /// </summary>
+        /// <param name="fields">List of fields</param>
+        /// <returns>New list of cloned fields with updated positions</returns>
+        private List<Field> SetFieldsPositions(List<Field> fields)
+        {
+            return fields.Select((field, i) => field.ChangePosition(i)).ToList();
+        }
+
         /// <summary>
         /// Returns the field with the given name.
         /// </summary>
diff --git a/lang/csharp/src/apache/main/Schema/UnionSchema.cs b/lang/csharp/src/apache/main/Schema/UnionSchema.cs
index e74fa9541..7e1575684 100644
--- a/lang/csharp/src/apache/main/Schema/UnionSchema.cs
+++ b/lang/csharp/src/apache/main/Schema/UnionSchema.cs
@@ -17,9 +17,8 @@
  */
 using System;
 using System.Collections.Generic;
-using System.Text;
+using System.Linq;
 using Newtonsoft.Json.Linq;
-using Newtonsoft.Json;
 
 namespace Avro
 {
@@ -68,14 +67,27 @@ namespace Avro
         }
 
         /// <summary>
-        /// Constructor for union schema
+        /// Creates a new <see cref="UnionSchema"/>
+        /// </summary>
+        /// <param name="schemas">The union schemas</param>
+        /// <param name="customProperties">Dictionary that provides access to custom properties</param>
+        /// <returns>New <see cref="UnionSchema"/></returns>
+        public static UnionSchema Create(List<Schema> schemas, PropertyMap customProperties = null)
+        {
+            return new UnionSchema(schemas, customProperties);
+        }
+
+        /// <summary>
+        /// Contructor for union schema
         /// </summary>
         /// <param name="schemas"></param>
-        /// <param name="props">dictionary that provides access to custom properties</param>
-        private UnionSchema(List<Schema> schemas, PropertyMap props) : base(Type.Union, props)
+        /// <param name="customProperties">dictionary that provides access to custom properties</param>
+        private UnionSchema(List<Schema> schemas, PropertyMap customProperties)
+            : base(Type.Union, customProperties)
         {
             if (schemas == null)
                 throw new ArgumentNullException(nameof(schemas));
+            VerifyChildSchemas(schemas);
             this.Schemas = schemas;
         }
 
@@ -161,5 +173,20 @@ namespace Avro
             result += getHashCode(Props);
             return result;
         }
+
+        private void VerifyChildSchemas(List<Schema> schemas)
+        {
+            if (schemas.Any(schema => schema.Tag == Type.Union))
+            {
+                throw new ArgumentException("Unions may not immediately contain other unions", nameof(schemas));
+            }
+
+            IGrouping<string, Schema> duplicateType = schemas.GroupBy(schema => schema.Fullname).FirstOrDefault(x => x.Count() > 1);
+
+            if (duplicateType != null)
+            {
+                throw new ArgumentException($"Duplicate type in union: {duplicateType.Key}");
+            }
+        }
     }
 }
diff --git a/lang/csharp/src/apache/test/Avro.test.csproj b/lang/csharp/src/apache/test/Avro.test.csproj
index 24fa4b31f..3ba3a0ffa 100644
--- a/lang/csharp/src/apache/test/Avro.test.csproj
+++ b/lang/csharp/src/apache/test/Avro.test.csproj
@@ -24,6 +24,8 @@
     <AssemblyName>Avro.test</AssemblyName>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <GenerateProgramFile>false</GenerateProgramFile>
+    <SignAssembly>True</SignAssembly>
+    <AssemblyOriginatorKeyFile>..\..\..\Avro.snk</AssemblyOriginatorKeyFile>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)'=='Release'">
diff --git a/lang/csharp/src/apache/test/Schema/AliasesTests.cs b/lang/csharp/src/apache/test/Schema/AliasesTests.cs
new file mode 100644
index 000000000..27ad4b23e
--- /dev/null
+++ b/lang/csharp/src/apache/test/Schema/AliasesTests.cs
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+using NUnit.Framework;
+
+namespace Avro.Test
+{
+    [TestFixture]
+    public class AliasesTests
+    {
+        [TestCase]
+        public void TestNoNamespace()
+        {
+            CollectionAssert.AreEqual(new[] { new SchemaName("alias", null, null, null) }, Aliases.GetSchemaNames(new[] { "alias" }, "name", null));
+        }
+
+        [TestCase]
+        public void TestTypeWithNamespace()
+        {
+            CollectionAssert.AreEqual(new[] { new SchemaName("space.alias", null, null, null) }, Aliases.GetSchemaNames(new[] { "alias" }, "name", "space"));
+        }
+
+        [TestCase]
+        public void TestTypeWithNamespaceInName()
+        {
+            CollectionAssert.AreEqual(new[] { new SchemaName("space.alias", null, null, null) }, Aliases.GetSchemaNames(new[] { "alias" }, "space.name", null));
+        }
+
+        [TestCase]
+        public void TestAliasWithNamespace()
+        {
+            CollectionAssert.AreEqual(new[] { new SchemaName("name.alias", null, null, null) }, Aliases.GetSchemaNames(new[] { "name.alias" }, "space.name", null));
+        }
+    }
+}
diff --git a/lang/csharp/src/apache/test/Schema/SchemaTests.cs b/lang/csharp/src/apache/test/Schema/SchemaTests.cs
index a061d5373..89c7f18c2 100644
--- a/lang/csharp/src/apache/test/Schema/SchemaTests.cs
+++ b/lang/csharp/src/apache/test/Schema/SchemaTests.cs
@@ -16,7 +16,9 @@
  * limitations under the License.
  */
 using System;
+using System.Collections.Generic;
 using NUnit.Framework;
+using System.Linq;
 
 namespace Avro.Test
 {
@@ -138,8 +140,9 @@ namespace Avro.Test
         public void TestPrimitive(string s, Schema.Type type)
         {
             Schema sc = Schema.Parse(s);
-            Assert.IsTrue(sc is PrimitiveSchema);
-            Assert.AreEqual(type, sc.Tag);
+            Schema schema = PrimitiveSchema.Create(type, null);
+
+            Assert.AreEqual(sc, schema);
 
             testEquality(s, sc);
             testToString(sc);
@@ -288,13 +291,133 @@ namespace Avro.Test
             Assert.AreEqual(expectedDoc, roundTrip.Documentation);
         }
 
-        [TestCase("{\"type\": \"enum\", \"name\": \"Test\", \"symbols\": [\"A\", \"B\"]}",
+        [TestCase("{\"type\":\"record\",\"name\":\"Longs\",\"fields\":[{\"name\":\"value\",\"default\":\"100\",\"type\":\"long\",\"aliases\":[\"oldName\"]}]}",
+            "Longs", null, null, null,
+            new[] { "value" }, new[] { Schema.Type.Long }, new[] { "100" }, new[] { "oldName" }, new string[] { null })]
+        [TestCase("{\"type\":\"record\",\"name\":\"Longs\",\"fields\":[{\"name\":\"value\",\"doc\":\"Field With Documentation\",\"default\":\"100\",\"type\":\"long\",\"aliases\":[\"oldName\"]}]}",
+            "Longs", null, null, null,
+            new[] { "value" }, new[] { Schema.Type.Long }, new[] { "100" }, new[] { "oldName" }, new string[] { "Field With Documentation" })]
+        [TestCase("{\"type\":\"record\",\"name\":\"Longs\",\"namespace\":\"space\",\"fields\":[{\"name\":\"value\",\"default\":\"100\",\"type\":\"long\",\"aliases\":[\"oldName\"]}]}",
+            "Longs", "space", null, null,
+            new[] { "value" }, new[] { Schema.Type.Long }, new[] { "100" }, new[] { "oldName" }, new string[] { null })]
+        [TestCase("{\"type\":\"record\",\"name\":\"Longs\",\"doc\":\"Record with alias\",\"namespace\":\"space\",\"aliases\":[\"space.RecordAlias\"],\"fields\":[{\"name\":\"value\",\"default\":\"100\",\"type\":\"long\",\"aliases\":[\"oldName\"]}]}",
+            "Longs", "space", "RecordAlias", "Record with alias",
+            new[] { "value" }, new[] { Schema.Type.Long }, new[] { "100" }, new[] { "oldName" }, new string[] { null })]
+        [TestCase("{\"type\":\"record\",\"name\":\"Longs\",\"doc\":\"Record with two fields\",\"namespace\":\"space\",\"aliases\":[\"space.RecordAlias\"],\"fields\":[{\"name\":\"value\",\"doc\":\"first field\",\"default\":\"100\",\"type\":\"long\",\"aliases\":[\"oldName\"]},{\"name\":\"field2\",\"default\":\"true\",\"type\":\"boolean\"}]}",
+            "Longs", "space", "RecordAlias", "Record with two fields",
+            new[] { "value", "field2" }, new[] { Schema.Type.Long, Schema.Type.Boolean }, new[] { "100", "true" }, new[] { "oldName", null }, new string[] { "first field", null })]
+        public void TestRecordCreation(string expectedSchema, string name, string space, string alias, string documentation, string[] fieldsNames, Schema.Type[] fieldsTypes, object[] fieldsDefaultValues, string[] fieldsAliases, string[] fieldsDocs)
+        {
+            IEnumerable<Field> recordFields = fieldsNames.Select((fieldName, i) => new Field(PrimitiveSchema.Create(fieldsTypes[i]),
+                fieldName,
+                fieldsAliases[i] == null? null: new List<string> { fieldsAliases[i] },
+                i,
+                fieldsDocs[i],
+                fieldsDefaultValues[i].ToString(),
+                Field.SortOrder.ignore,
+                null));
+
+            string[] aliases = alias == null ? null : new[] { alias };
+
+            RecordSchema recordSchema = RecordSchema.Create(name, recordFields.ToList(), space, aliases, null, documentation);
+
+            for(int i = 0; i < fieldsNames.Length; i++)
+            {
+                var fieldByName = recordSchema[fieldsNames[i]];
+                if (fieldsAliases[i] != null)
+                {
+                    recordSchema.TryGetFieldAlias(fieldsAliases[i], out Field fieldByAlias);
+
+                    Assert.AreSame(fieldByAlias, fieldByName);
+                }
+                Assert.AreEqual(expectedSchema, recordSchema.ToString());
+                Assert.AreEqual(fieldsNames[i], fieldByName.Name);
+                Assert.AreEqual(i, fieldByName.Pos);
+                Assert.AreEqual(fieldsTypes[i], fieldByName.Schema.Tag);
+                Assert.AreEqual(fieldsDocs[i], fieldByName.Documentation);
+                Assert.AreEqual(fieldsDefaultValues[i], fieldByName.DefaultValue.ToString());
+                CollectionAssert.AreEqual(fieldsAliases[i] == null? null: new[] {fieldsAliases[i]}, fieldByName.Aliases);
+            }
+        }
+
+        [TestCase]
+        public void TestRecordCreationWithDuplicateFields()
+        {
+            var recordField = new Field(PrimitiveSchema.Create(Schema.Type.Long),
+                "value",
+                new List<string> { "oldName" },
+                0,
+                null,
+                "100",
+                Field.SortOrder.ignore,
+                null);
+
+            Assert.Throws<AvroException>(() => RecordSchema.Create("Longs",
+                new List<Field>
+                {
+                    recordField,
+                    recordField
+                }));
+        }
+
+        [TestCase]
+        public void TestRecordCreationWithRecursiveRecord()
+        {
+            string schema = "{\"type\":\"record\",\"name\":\"LongList\",\"aliases\":[\"LinkedLongs\"],\"fields\":[{\"name\":\"value\",\"type\":\"long\"},{\"name\":\"next\",\"type\":[\"null\",\"LongList\"]}]}";
+
+            var recordSchema = RecordSchema.Create("LongList", new List<Field>(), null, new[] { "LinkedLongs" });
+
+            recordSchema.Fields = new List<Field>
+                {
+                    new Field(PrimitiveSchema.Create(Schema.Type.Long),
+                        "value",
+                        null,
+                        0,
+                        null,
+                        null,
+                        Field.SortOrder.ignore,
+                        null),
+                    new Field(UnionSchema.Create(
+                        new List<Schema>
+                        {
+                            PrimitiveSchema.Create(Schema.Type.Null), recordSchema
+                        }),
+                        "next",
+                        1)
+                };
+
+            Assert.AreEqual(schema, recordSchema.ToString());
+        }
+
+        [TestCase("{\"type\":\"enum\",\"name\":\"Test\",\"symbols\":[\"A\",\"B\"]}",
+                new string[] { "A", "B" })]
+
+        [TestCase("{\"type\":\"enum\",\"name\":\"Test\",\"symbols\":[\"A\",\"B\"]}",
             new string[] { "A", "B" })]
-        [TestCase("{\"type\": \"enum\",\"name\":\"Market\",\"symbols\":[\"UNKNOWN\",\"A\",\"B\"],\"default\":\"UNKNOWN\"}",
-            new string[] { "UNKNOWN", "A", "B" })]
-        public void TestEnum(string s, string[] symbols)
+        [TestCase("{\"type\":\"enum\",\"name\":\"Test\",\"doc\":\"Some explanation\",\"namespace\":\"mynamespace\",\"aliases\":[\"mynamespace.Alias\"],\"symbols\":[\"UNKNOWN\",\"A\",\"B\"],\"default\":\"UNKNOWN\",\"propertyKey\":\"propertyValue\"}",
+            new string[] { "UNKNOWN", "A", "B" }, "mynamespace", new string[] { "Alias" }, "Some explanation", true, "UNKNOWN")]
+        [TestCase("{\"type\":\"enum\",\"name\":\"Test\",\"doc\":\"Some explanation\",\"namespace\":\"space\",\"aliases\":[\"internalNamespace.Alias\"],\"symbols\":[\"UNKNOWN\",\"A\",\"B\"]}",
+            new string[] { "UNKNOWN", "A", "B" }, "space", new string[] { "internalNamespace.Alias" }, "Some explanation")]
+        [TestCase("{\"type\":\"enum\",\"name\":\"Test\",\"doc\":\"Some explanation\",\"namespace\":\"space\",\"aliases\":[\"internalNamespace.Alias\"],\"symbols\":[]}",
+            new string[] { }, "space", new string[] { "internalNamespace.Alias" }, "Some explanation")]
+
+        public void TestEnum(string s, string[] symbols, string space = null, IEnumerable<string> aliases = null, string doc = null, bool? usePropertyMap = null, string defaultSymbol = null)
         {
             Schema sc = Schema.Parse(s);
+
+            PropertyMap propertyMap = new PropertyMap();
+            propertyMap.Add("propertyKey", "\"propertyValue\"");
+            Schema schema = EnumSchema.Create("Test",
+                symbols,
+                space,
+                aliases,
+                usePropertyMap == true ? propertyMap : null,
+                doc,
+                defaultSymbol);
+
+            Assert.AreEqual(sc, schema);
+            Assert.AreEqual(s, schema.ToString());
+
             Assert.AreEqual(Schema.Type.Enumeration, sc.Tag);
             EnumSchema es = sc as EnumSchema;
             Assert.AreEqual(symbols.Length, es.Count);
@@ -333,18 +456,66 @@ namespace Avro.Test
             Assert.Throws<SchemaParseException>(() => Schema.Parse(s));
         }
 
+        [TestCase("name", new string[] { "A", "B" }, "s", new[] { "L1", "L2" }, "regular enum", null, "name", "s")]
+        [TestCase("s.name", new string[] { "A", "B" }, null, new[] { "L1", "L2" }, "internal namespace", null, "name", "s")]
+        [TestCase("name", new string[] { "A", "B" }, null, new[] { "L1", "L2" }, "no namespace", null, "name", null)]
+        [TestCase("name", new string[] { "A", "B" }, null, new[] { "L1", "L2" }, "with default value", "A", "name", null)]
+        [TestCase("name", new string[] { "A1B2", "B4324" }, null, new[] { "L1", "L2" }, "with longer symbols", "B4324", "name", null)]
+        [TestCase("name", new string[] { "_A1B2_", "B4324" }, null, new[] { "L1", "L2" }, "underscore in symbols", "_A1B2_", "name", null)]
+        public void TestEnumCreation(string name, string[] symbols, string space, string[] aliases, string doc, string defaultSymbol, string expectedName, string expectedNamespace)
+        {
+            EnumSchema enumSchema = EnumSchema.Create(name, symbols, space, aliases, null, doc, defaultSymbol);
+
+            Assert.AreEqual(expectedName, enumSchema.Name);
+            CollectionAssert.AreEqual(symbols, enumSchema.Symbols);
+            Assert.AreEqual(expectedNamespace, enumSchema.Namespace);
+            Assert.AreEqual(Schema.Type.Enumeration, enumSchema.Tag);
+            Assert.AreEqual(doc, enumSchema.Documentation);
+            Assert.AreEqual(defaultSymbol, enumSchema.Default);
+        }
+
+        [TestCase(new[] {"A", "B"}, "C")]
+        [TestCase(new[] {null, "B"}, null)]
+        [TestCase(new[] {"", "B" }, null)]
+        [TestCase(new[] {"8", "B" }, null)]
+        [TestCase(new[] {"8", "B" }, null)]
+        [TestCase(new[] {"A", "A" }, null)]
+        [TestCase(new[] {" ", "A" }, null)]
+        [TestCase(new[] {"9A23", "A" }, null)]
+        public void TestEnumInvalidSymbols(string[] symbols, string defaultSymbol)
+        {
+            Assert.Throws<AvroException>(() => EnumSchema.Create("name", symbols, defaultSymbol: defaultSymbol));
+        }
+
         [TestCase("{\"type\": \"array\", \"items\": \"long\"}", "long")]
         public void TestArray(string s, string item)
         {
             Schema sc = Schema.Parse(s);
             Assert.AreEqual(Schema.Type.Array, sc.Tag);
-            ArraySchema ars = sc as ArraySchema;
+            ArraySchema ars = (ArraySchema)sc;
             Assert.AreEqual(item, ars.ItemSchema.Name);
 
             testEquality(s, sc);
             testToString(sc);
         }
 
+        [TestCase]
+        public void TestArrayCreation()
+        {
+            PrimitiveSchema itemsSchema = PrimitiveSchema.Create(Schema.Type.String);
+            ArraySchema arraySchema = ArraySchema.Create(itemsSchema);
+
+            Assert.AreEqual("array", arraySchema.Name);
+            Assert.AreEqual(Schema.Type.Array, arraySchema.Tag);
+            Assert.AreEqual(itemsSchema, arraySchema.ItemSchema);
+        }
+
+        [TestCase]
+        public void TestInvalidArrayCreation()
+        {
+            Assert.Throws<ArgumentNullException>(() => ArraySchema.Create(null));
+        }
+
         [TestCase("{\"type\": \"int\", \"logicalType\": \"date\"}", "int", "date")]
         public void TestLogicalPrimitive(string s, string baseType, string logicalType)
         {
@@ -371,33 +542,82 @@ namespace Avro.Test
         {
             Schema sc = Schema.Parse(s);
             Assert.AreEqual(Schema.Type.Map, sc.Tag);
-            MapSchema ms = sc as MapSchema;
+            MapSchema ms = (MapSchema)sc;
             Assert.AreEqual(value, ms.ValueSchema.Name);
 
             testEquality(s, sc);
             testToString(sc);
         }
 
-        [TestCase("[\"string\", \"null\", \"long\"]", new string[] { "string", "null", "long" })]
-        public void TestUnion(string s, string[] types)
+        [TestCase]
+        public void TestMapCreation()
+        {
+            PrimitiveSchema mapType = PrimitiveSchema.Create(Schema.Type.Float);
+            MapSchema mapSchema = MapSchema.CreateMap(mapType);
+
+            Assert.AreEqual("map", mapSchema.Fullname);
+            Assert.AreEqual("map", mapSchema.Name);
+            Assert.AreEqual(Schema.Type.Map, mapSchema.Tag);
+            Assert.AreEqual(mapType, mapSchema.ValueSchema);
+        }
+
+        [TestCase]
+        public void TestInvalidMapCreation()
+        {
+            Assert.Throws<ArgumentNullException>(() => MapSchema.CreateMap(null));
+        }
+
+        [TestCase("[\"string\", \"null\", \"long\"]",
+            new Schema.Type[] { Schema.Type.String, Schema.Type.Null, Schema.Type.Long })]
+        public void TestUnion(string s, Schema.Type[] types)
         {
             Schema sc = Schema.Parse(s);
+
+            UnionSchema schema = UnionSchema.Create(types.Select(t => (Schema)PrimitiveSchema.Create(t)).ToList());
+            Assert.AreEqual(sc, schema);
+            
             Assert.AreEqual(Schema.Type.Union, sc.Tag);
-            UnionSchema us = sc as UnionSchema;
+            UnionSchema us = (UnionSchema)sc;
             Assert.AreEqual(types.Length, us.Count);
 
             for (int i = 0; i < us.Count; i++)
             {
-                Assert.AreEqual(types[i], us[i].Name);
+                Assert.AreEqual(types[i].ToString().ToLower(), us[i].Name);
             }
             testEquality(s, sc);
             testToString(sc);
         }
 
-        [TestCase("{ \"type\": \"fixed\", \"name\": \"Test\", \"size\": 1}", 1)]
+        [TestCase]
+        public void TestUnionCreation()
+        {
+            UnionSchema unionSchema = UnionSchema.Create(new List<Schema> { PrimitiveSchema.Create(Schema.Type.Null), PrimitiveSchema.Create(Schema.Type.String) });
+
+            CollectionAssert.AreEqual(new List<Schema> { PrimitiveSchema.Create(Schema.Type.Null), PrimitiveSchema.Create(Schema.Type.String) },
+                unionSchema.Schemas);
+        }
+
+        [TestCase]
+        public void TestUnionCreationWithDuplicateSchemas()
+        {
+            Assert.Throws<ArgumentException>(() => UnionSchema.Create(new List<Schema> { PrimitiveSchema.Create(Schema.Type.String), PrimitiveSchema.Create(Schema.Type.String) }));
+        }
+
+        [TestCase]
+        public void TestUnionNestedUnionCreation()
+        {
+            Assert.Throws<ArgumentException>(() => UnionSchema.Create(new List<Schema> { UnionSchema.Create(new List<Schema>()), PrimitiveSchema.Create(Schema.Type.String) }));
+        }
+
+        [TestCase("{\"type\":\"fixed\",\"name\":\"Test\",\"size\":1}", 1)]
         public void TestFixed(string s, int size)
         {
             Schema sc = Schema.Parse(s);
+            FixedSchema schema = FixedSchema.Create("Test", 1);
+
+            Assert.AreEqual(sc, schema);
+            Assert.AreEqual(s, schema.ToString());
+
             Assert.AreEqual(Schema.Type.Fixed, sc.Tag);
             FixedSchema fs = sc as FixedSchema;
             Assert.AreEqual(size, fs.Size);
@@ -415,6 +635,19 @@ namespace Avro.Test
             Assert.AreEqual(expectedDoc, fs.Documentation);
         }
 
+        [TestCase]
+        public void TestFixedCreation()
+        {
+            string s = @"{""type"":""fixed"",""name"":""fixedName"",""namespace"":""space"",""aliases"":[""space.fixedOldName""],""size"":10}";
+
+            FixedSchema fixedSchema = FixedSchema.Create("fixedName", 10, "space", new[] { "fixedOldName" }, null);
+
+            Assert.AreEqual("fixedName", fixedSchema.Name);
+            Assert.AreEqual("space.fixedName", fixedSchema.Fullname);
+            Assert.AreEqual(10, fixedSchema.Size);
+            Assert.AreEqual(s, fixedSchema.ToString());
+        }
+
         [TestCase("a", "o.a.h", ExpectedResult = "o.a.h.a")]
         public string testFullname(string s1, string s2)
         {