private static AvroSchema ParseFixedSchema(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "size", "name"
            };
            var optionalKeys = new HashSet <string>()
            {
                "namespace"
            };

            JsonUtil.AssertKeys(jToken, keys, optionalKeys, out var tags);

            JsonUtil.AssertValue(jToken, "type", "fixed");
            var size = JsonUtil.GetValue <int>(jToken, "size");
            var name = JsonUtil.GetValue <string>(jToken, "name");

            JsonUtil.TryGetValue <string>(jToken, "namespace", out var ns);
            var fixedSchema = new FixedSchema(name, ns, size);

            if (string.IsNullOrEmpty(fixedSchema.Namespace))
            {
                fixedSchema.Namespace = enclosingNamespace.Peek();
            }
            fixedSchema.AddTags(tags);
            namedTypes.Add(fixedSchema.FullName, fixedSchema);
            return(fixedSchema);
        }
        public void AssertKeys()
        {
            var jArray = JToken.Parse("[1, 2, 3, 4]");
            var jToken = JToken.Parse(@"{""name"":""XYZ"",""type"":""record"",""fields"":[{""name"":""A"",""type"":""int""}],""happy"":""face""}");

            Assert.Throws(
                typeof(ArgumentException),
                () => JsonUtil.AssertKeys(
                    jArray,
                    new HashSet <string>()
            {
                "name"
            },
                    new HashSet <string>(),
                    out _
                    )
                );


            Assert.Throws(
                typeof(ArgumentException),
                () => JsonUtil.AssertKeys(
                    jToken,
                    new HashSet <string>(),
                    new HashSet <string>(),
                    out _
                    )
                );

            Assert.Throws(
                typeof(KeyNotFoundException),
                () => JsonUtil.AssertKeys(
                    jToken,
                    new HashSet <string>()
            {
                "name", "outlandishkey"
            },
                    new HashSet <string>(),
                    out _
                    )
                );

            JsonUtil.AssertKeys(
                jToken,
                new HashSet <string>()
            {
                "name", "type", "fields"
            },
                new HashSet <string>()
            {
                "doc", "aliases"
            },
                out var additionalTags
                );

            Assert.IsTrue(additionalTags.ContainsKey("happy"));
            Assert.IsInstanceOf <JValue>(additionalTags["happy"]);
            Assert.AreEqual("face", additionalTags["happy"].ToString());
        }
        private static AvroSchema ParseRecordSchema(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "name", "fields"
            };
            var optionalKeys = new HashSet <string>()
            {
                "namespace", "aliases", "doc"
            };

            JsonUtil.AssertKeys(jToken, keys, optionalKeys, out var tags);

            JsonUtil.AssertValues(jToken, "type", "record", "error");
            var type   = JsonUtil.GetValue <string>(jToken, "type");
            var name   = JsonUtil.GetValue <string>(jToken, "name");
            var fields = JsonUtil.GetValue <JArray>(jToken, "fields");

            var recordSchema = new RecordSchema(name);

            if (type.ToString() == "error")
            {
                recordSchema = new ErrorSchema(name);
            }
            recordSchema.AddTags(tags);

            if (JsonUtil.TryGetValue <string>(jToken, "namespace", out var ns))
            {
                recordSchema.Namespace = ns;
            }

            if (string.IsNullOrEmpty(recordSchema.Namespace))
            {
                recordSchema.Namespace = enclosingNamespace.Peek();
            }

            enclosingNamespace.Push(recordSchema.Namespace);

            if (JsonUtil.TryGetValue <JArray>(jToken, "aliases", out var aliases))
            {
                recordSchema.Aliases = aliases.Values <string>().ToArray();
            }

            if (JsonUtil.TryGetValue <string>(jToken, "doc", out var doc))
            {
                recordSchema.Doc = doc;
            }

            namedTypes.Add(recordSchema.FullName, recordSchema);

            foreach (var field in ParseRecordFieldSchema(fields, namedTypes, enclosingNamespace))
            {
                recordSchema.Add(field);
            }

            enclosingNamespace.Pop();

            return(recordSchema);
        }
        private static AvroProtocol ParseProtocol(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            switch (jToken.Type)
            {
            case JTokenType.Object:
                var keys = new HashSet <string>()
                {
                    "protocol"
                };
                var optionalKeys = new HashSet <string>()
                {
                    "namespace", "doc", "types", "messages"
                };
                JsonUtil.AssertKeys(jToken, keys, optionalKeys, out _);

                var name     = JsonUtil.GetValue <string>(jToken, "protocol");
                var protocol = new AvroProtocol(name);

                if (JsonUtil.TryGetValue <string>(jToken, "namespace", out var ns))
                {
                    protocol.Namespace = ns;
                }

                if (string.IsNullOrEmpty(protocol.Namespace))
                {
                    protocol.Namespace = enclosingNamespace.Peek();
                }

                enclosingNamespace.Push(protocol.Namespace);

                if (JsonUtil.TryGetValue <string>(jToken, "doc", out var doc))
                {
                    protocol.Doc = doc;
                }
                if (JsonUtil.TryGetValue <JArray>(jToken, "types", out var types))
                {
                    foreach (var type in ParseProtocolTypes(types, namedTypes, enclosingNamespace))
                    {
                        protocol.AddType(type);
                    }
                }
                if (JsonUtil.TryGetValue <JObject>(jToken, "messages", out var messages))
                {
                    foreach (var message in ParseMessages(messages, protocol.Types.ToDictionary(r => r.FullName), enclosingNamespace))
                    {
                        protocol.AddMessage(message);
                    }
                }

                enclosingNamespace.Pop();

                return(protocol);

            default:
                throw new AvroParseException($"Unexpected Json token: '{jToken.Type}'");
            }
        }
        private static IList <RecordSchema.Field> ParseRecordFieldSchema(JArray jArray, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "name"
            };
            var optionalKeys = new HashSet <string>()
            {
                "aliases", "doc", "default", "order"
            };
            var fields = new List <RecordSchema.Field>();

            foreach (var jToken in jArray)
            {
                JsonUtil.AssertKeys(jToken, keys, optionalKeys, out var tags);

                var name = JsonUtil.GetValue <string>(jToken, "name");
                var type = JsonUtil.GetValue <JToken>(jToken, "type");

                var fieldType         = ParseSchema(type, namedTypes, enclosingNamespace);
                var recordFieldSchema = new RecordSchema.Field(name, fieldType);
                recordFieldSchema.AddTags(tags);

                if (JsonUtil.TryGetValue <JArray>(jToken, "aliases", out var aliases))
                {
                    recordFieldSchema.Aliases = aliases.Values <string>().ToArray();
                }

                if (JsonUtil.TryGetValue <string>(jToken, "doc", out var doc))
                {
                    recordFieldSchema.Doc = doc;
                }

                if (JsonUtil.TryGetValue <JToken>(jToken, "default", out var def))
                {
                    recordFieldSchema.Default = def;
                }

                if (JsonUtil.TryGetValue <string>(jToken, "order", out var order))
                {
                    recordFieldSchema.Order = order;
                }

                fields.Add(recordFieldSchema);
            }
            return(fields);
        }
        private static AvroSchema ParseDurationSchema(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "logicalType", "type"
            };

            JsonUtil.AssertKeys(jToken, keys, null, out var tags);

            JsonUtil.AssertValue(jToken, "logicalType", "duration");
            var type           = JsonUtil.GetValue <JToken>(jToken, "type");
            var underlyingType = ParseSchema(type, namedTypes, enclosingNamespace);
            var durationSchema = new DurationSchema(underlyingType);

            durationSchema.AddTags(tags);
            return(durationSchema);
        }
        private static AvroSchema ParseMapSchema(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "values"
            };

            JsonUtil.AssertKeys(jToken, keys, null, out var tags);

            JsonUtil.AssertValue(jToken, "type", "map");
            var values       = JsonUtil.GetValue <JToken>(jToken, "values");
            var valuesSchema = ParseSchema(values, namedTypes, enclosingNamespace);
            var mapSchema    = new MapSchema(valuesSchema);

            mapSchema.AddTags(tags);
            return(mapSchema);
        }
        private static AvroSchema ParseArraySchema(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "items"
            };

            JsonUtil.AssertKeys(jToken, keys, null, out var tags);

            JsonUtil.AssertValue(jToken, "type", "array");
            var items       = JsonUtil.GetValue <JToken>(jToken, "items");
            var itemsSchema = ParseSchema(items, namedTypes, enclosingNamespace);
            var arraySchema = new ArraySchema(itemsSchema);

            arraySchema.AddTags(tags);
            return(arraySchema);
        }
        private static AvroSchema ParseEnumType(JToken jToken, IDictionary <string, NamedSchema> namedTypes, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "type", "name", "symbols"
            };
            var optionalKeys = new HashSet <string>()
            {
                "namespace", "aliases", "doc"
            };

            JsonUtil.AssertKeys(jToken, keys, optionalKeys, out var tags);

            JsonUtil.AssertValue(jToken, "type", "enum");
            var name    = JsonUtil.GetValue <string>(jToken, "name");
            var symbols = JsonUtil.GetValue <JArray>(jToken, "symbols");

            var enumSchema = new EnumSchema(name, null, symbols.Values <string>());

            enumSchema.AddTags(tags);
            if (JsonUtil.TryGetValue <string>(jToken, "namespace", out var ns))
            {
                enumSchema.Namespace = ns;
            }

            if (string.IsNullOrEmpty(enumSchema.Namespace))
            {
                enumSchema.Namespace = enclosingNamespace.Peek();
            }

            if (JsonUtil.TryGetValue <JArray>(jToken, "aliases", out var aliases))
            {
                enumSchema.Aliases = aliases.Values <string>().ToArray();
            }

            if (JsonUtil.TryGetValue <string>(jToken, "doc", out var doc))
            {
                enumSchema.Doc = doc;
            }

            namedTypes.Add(enumSchema.FullName, enumSchema);

            return(enumSchema);
        }
        private static IList <ParameterSchema> ParseRequests(JArray jArray, IDictionary <string, NamedSchema> types, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "name", "type"
            };
            var requests = new List <ParameterSchema>();

            foreach (var item in jArray)
            {
                JsonUtil.AssertKeys(item, keys, null, out _);
                var name = JsonUtil.GetValue <string>(item, "name");
                var type = JsonUtil.GetValue <string>(item, "type");

                type = QualifyName(type, enclosingNamespace);
                if (!types.TryGetValue(type, out var request))
                {
                    throw new AvroParseException($"Unknown request parameter type: '{type}'.");
                }
                requests.Add(new ParameterSchema(name, request));
            }
            return(requests);
        }
        private static IList <MessageSchema> ParseMessages(JObject jObject, IDictionary <string, NamedSchema> types, Stack <string> enclosingNamespace)
        {
            var keys = new HashSet <string>()
            {
                "request", "response"
            };
            var optionalKeys = new HashSet <string>()
            {
                "doc", "errors", "one-way"
            };
            var messages = new List <MessageSchema>();

            foreach (var item in JsonUtil.GetKeyValues(jObject))
            {
                var name   = item.Key;
                var jToken = item.Value;

                JsonUtil.AssertKeys(item.Value, keys, optionalKeys, out _);

                var message  = new MessageSchema(name);
                var request  = JsonUtil.GetValue <JArray>(jToken, "request");
                var response = JsonUtil.GetValue <JToken>(jToken, "response");

                foreach (var requestParameter in ParseRequests(request, types, enclosingNamespace))
                {
                    message.AddParameter(requestParameter);
                }
                message.Response = ParseSchema(response, types, enclosingNamespace);

                if (JsonUtil.TryGetValue <string>(jToken, "doc", out var doc))
                {
                    message.Doc = doc;
                }
                if (JsonUtil.TryGetValue <bool>(jToken, "one-way", out var oneway))
                {
                    if (oneway && !(message.Response is NullSchema))
                    {
                        throw new AvroParseException("One way messages must have a 'null' response");
                    }
                    else
                    {
                        message.Oneway = oneway;
                    }
                }

                if (JsonUtil.TryGetValue <JArray>(jToken, "errors", out var errors))
                {
                    foreach (var error in errors)
                    {
                        if (error.Type != JTokenType.String)
                        {
                            throw new AvroParseException($"Declared type must be a string.");
                        }

                        var declaredType = QualifyName(error.ToString(), enclosingNamespace);
                        if (!types.TryGetValue(declaredType, out var errorType))
                        {
                            throw new AvroParseException($"'{declaredType}' is not a declared type.");
                        }
                        if (!(errorType is ErrorSchema))
                        {
                            throw new AvroParseException($"'{declaredType}' is not an error type.");
                        }
                        message.AddError(errorType as ErrorSchema);
                    }
                }

                messages.Add(message);
            }
            return(messages);
        }