/// <summary>
        ///     Provide a functional implementation corresponding to the enum value.
        /// </summary>
        public static SubjectNameStrategyDelegate ToDelegate(this SubjectNameStrategy strategy)
        {
            switch (strategy)
            {
            case SubjectNameStrategy.Topic:
                return((context, recordType) => $"{context.Topic}" + (context.Component == MessageComponentType.Key ? "-key" : "-value"));

            case SubjectNameStrategy.Record:
                return((context, recordType) =>
                {
                    if (recordType == null)
                    {
                        throw new ArgumentNullException($"recordType must not be null for SubjectNameStrategy.Record");
                    }
                    return $"{recordType}";
                });

            case SubjectNameStrategy.TopicRecord:
                return((context, recordType) =>
                {
                    if (recordType == null)
                    {
                        throw new ArgumentNullException($"recordType must not be null for SubjectNameStrategy.Record");
                    }
                    return $"{context.Topic}-{recordType}";
                });

            default:
                throw new ArgumentException($"Unknown SubjectNameStrategy: {strategy}");
            }
        }
示例#2
0
        private static SubjectNameStrategy GetValueSubjectNameStrategy(IEnumerable <KeyValuePair <string, string> > config)
        {
            var valueSubjectNameStrategyString           = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryValueSubjectNameStrategy).Value ?? "";
            SubjectNameStrategy valueSubjectNameStrategy = SubjectNameStrategy.Topic;

            if (valueSubjectNameStrategyString != "" &&
                !Enum.TryParse <SubjectNameStrategy>(valueSubjectNameStrategyString, out valueSubjectNameStrategy))
            {
                throw new ArgumentException($"Unknown ValueSubjectNameStrategy: {valueSubjectNameStrategyString}");
            }
            return(valueSubjectNameStrategy);
        }
示例#3
0
        /// <summary>
        ///     Initialize a new instance of the SchemaRegistryClient class.
        /// </summary>
        /// <param name="config">
        ///     Configuration properties.
        /// </param>
        public CachedSchemaRegistryClient(IEnumerable <KeyValuePair <string, string> > config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config properties must be specified.");
            }

            var schemaRegistryUrisMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl);

            if (schemaRegistryUrisMaybe.Value == null)
            {
                throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl} configuration property must be specified.");
            }
            var schemaRegistryUris = (string)schemaRegistryUrisMaybe.Value;

            var timeoutMsMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs);
            int timeoutMs;

            try { timeoutMs = timeoutMsMaybe.Value == null ? DefaultTimeout : Convert.ToInt32(timeoutMsMaybe.Value); }
            catch (FormatException) { throw new ArgumentException($"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs} must be an integer."); }

            var identityMapCapacityMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas);

            try { this.identityMapCapacity = identityMapCapacityMaybe.Value == null ? DefaultMaxCachedSchemas : Convert.ToInt32(identityMapCapacityMaybe.Value); }
            catch (FormatException) { throw new ArgumentException($"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas} must be an integer."); }

            var basicAuthSource = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource).Value ?? "";
            var basicAuthInfo   = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo).Value ?? "";

            string username = null;
            string password = null;

            if (basicAuthSource == "USER_INFO" || basicAuthSource == "")
            {
                if (basicAuthInfo != "")
                {
                    var userPass = (basicAuthInfo).Split(':');
                    if (userPass.Length != 2)
                    {
                        throw new ArgumentException($"Configuration property {SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo} must be of the form 'username:password'.");
                    }
                    username = userPass[0];
                    password = userPass[1];
                }
            }
            else if (basicAuthSource == "SASL_INHERIT")
            {
                if (basicAuthInfo != "")
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but {SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo} as also specified.");
                }
                var saslUsername = config.FirstOrDefault(prop => prop.Key == "sasl.username");
                var saslPassword = config.FirstOrDefault(prop => prop.Key == "sasl.password");
                if (saslUsername.Value == null)
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but 'sasl.username' property not specified.");
                }
                if (saslPassword.Value == null)
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but 'sasl.password' property not specified.");
                }
                username = saslUsername.Value;
                password = saslPassword.Value;
            }
            else
            {
                throw new ArgumentException($"Invalid value '{basicAuthSource}' specified for property '{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource}'");
            }

            var schemaRegistrySecurityProtocol = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistrySecurityProtocol).Value ?? "";

            // read new SSL properties
            string pfxCertificatePath = string.Empty;

            if (schemaRegistrySecurityProtocol.ToUpper() == "SSL")
            {
                pfxCertificatePath = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryPfx).Value ?? "";
                if (string.IsNullOrEmpty(pfxCertificatePath) || !File.Exists(pfxCertificatePath))
                {
                    throw new ArgumentException($"can't locate provided certificate at: {pfxCertificatePath}");
                }
            }

            KeySubjectNameStrategy   = GetKeySubjectNameStrategy(config);
            ValueSubjectNameStrategy = GetValueSubjectNameStrategy(config);

            foreach (var property in config)
            {
                if (!property.Key.StartsWith("schema.registry."))
                {
                    continue;
                }

                if (property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryKeySubjectNameStrategy &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryValueSubjectNameStrategy &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistrySecurityProtocol && // check new property
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryPfx)                // check new property
                {
                    throw new ArgumentException($"Unknown configuration parameter {property.Key}");
                }
            }

            if (!string.IsNullOrEmpty(pfxCertificatePath))
            {
                this.restService = new RestService(schemaRegistryUris, timeoutMs, pfxCertificatePath);
            }
            else
            {
                this.restService = new RestService(schemaRegistryUris, timeoutMs, username, password);
            }
        }
 /// <summary>
 ///     Helper method to construct the value subject name given the specified parameters.
 /// </summary>
 public static string ConstructValueSubjectName(this SubjectNameStrategy strategy, string topic, string recordType = null)
 => strategy.ToDelegate()(new SerializationContext(MessageComponentType.Value, topic), recordType);
        private static void ProduceConsume(string bootstrapServers, string schemaRegistryServers, SubjectNameStrategy nameStrategy)
        {
            var producerConfig = new ProducerConfig
            {
                BootstrapServers = bootstrapServers
            };

            var consumerConfig = new ConsumerConfig
            {
                BootstrapServers   = bootstrapServers,
                GroupId            = Guid.NewGuid().ToString(),
                SessionTimeoutMs   = 6000,
                AutoOffsetReset    = AutoOffsetReset.Earliest,
                EnablePartitionEof = true
            };

            var schemaRegistryConfig = new SchemaRegistryConfig
            {
                Url = schemaRegistryServers,
                // Test ValueSubjectNameStrategy here,
                // and KeySubjectNameStrategy in ProduceConsumeGeneric.
                ValueSubjectNameStrategy = nameStrategy
            };

            var adminClientConfig = new AdminClientConfig
            {
                BootstrapServers = bootstrapServers
            };

            string topic = Guid.NewGuid().ToString();

            using (var adminClient = new AdminClientBuilder(adminClientConfig).Build())
            {
                adminClient.CreateTopicsAsync(
                    new List <TopicSpecification> {
                    new TopicSpecification {
                        Name = topic, NumPartitions = 1, ReplicationFactor = 1
                    }
                }).Wait();
            }

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var producer =
                           new ProducerBuilder <string, ProduceConsumeUser>(producerConfig)
                           .SetKeySerializer(new AvroSerializer <string>(schemaRegistry))
                           .SetValueSerializer(new AvroSerializer <ProduceConsumeUser>(schemaRegistry))
                           .Build())
                {
                    for (int i = 0; i < 100; ++i)
                    {
                        var user = new ProduceConsumeUser
                        {
                            name            = i.ToString(),
                            favorite_number = i,
                            favorite_color  = "blue"
                        };

                        producer
                        .ProduceAsync(topic, new Message <string, ProduceConsumeUser> {
                            Key = user.name, Value = user
                        })
                        .Wait();
                    }
                    Assert.Equal(0, producer.Flush(TimeSpan.FromSeconds(10)));
                }

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var consumer =
                           new ConsumerBuilder <string, ProduceConsumeUser>(consumerConfig)
                           .SetKeyDeserializer(new AvroDeserializer <string>(schemaRegistry).AsSyncOverAsync())
                           .SetValueDeserializer(new AvroDeserializer <ProduceConsumeUser>(schemaRegistry).AsSyncOverAsync())
                           .SetErrorHandler((_, e) => Assert.True(false, e.Reason))
                           .Build())
                {
                    consumer.Subscribe(topic);

                    int i = 0;
                    while (true)
                    {
                        var record = consumer.Consume(TimeSpan.FromMilliseconds(100));
                        if (record == null)
                        {
                            continue;
                        }
                        if (record.IsPartitionEOF)
                        {
                            break;
                        }

                        Assert.Equal(i.ToString(), record.Message.Key);
                        Assert.Equal(i.ToString(), record.Message.Value.name);
                        Assert.Equal(i, record.Message.Value.favorite_number);
                        Assert.Equal("blue", record.Message.Value.favorite_color);
                        i += 1;
                    }

                    Assert.Equal(100, i);

                    consumer.Close();
                }

            // Check that what's in schema registry is what's expected.
            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
            {
                var subjects = schemaRegistry.GetAllSubjectsAsync().Result;

                if (nameStrategy == SubjectNameStrategy.TopicRecord)
                {
                    Assert.Equal(2, (int)subjects.Where(s => s.Contains(topic)).Count());
                    Assert.Single(subjects.Where(s => s == $"{topic}-key"));
                    Assert.Single(subjects.Where(s => s == $"{topic}-{((Avro.RecordSchema)ProduceConsumeUser._SCHEMA).Fullname}"));
                }

                if (nameStrategy == SubjectNameStrategy.Topic)
                {
                    Assert.Equal(2, (int)subjects.Where(s => s.Contains(topic)).Count());
                    Assert.Single(subjects.Where(s => s == $"{topic}-key"));
                    Assert.Single(subjects.Where(s => s == $"{topic}-value"));
                }

                if (nameStrategy == SubjectNameStrategy.Record)
                {
                    Assert.Single(subjects.Where(s => s.Contains(topic))); // the string key.
                    Assert.Single(subjects.Where(s => s == $"{topic}-key"));
                    Assert.Single(subjects.Where(s => s == $"{((Avro.RecordSchema)ProduceConsumeUser._SCHEMA).Fullname}"));
                }
            }
        }
示例#6
0
        private static void ProduceConsumeNull(string bootstrapServers, string schemaRegistryServers, SubjectNameStrategy nameStrategy)
        {
            var producerConfig = new ProducerConfig
            {
                BootstrapServers = bootstrapServers
            };

            var consumerConfig = new ConsumerConfig
            {
                BootstrapServers   = bootstrapServers,
                GroupId            = Guid.NewGuid().ToString(),
                SessionTimeoutMs   = 6000,
                AutoOffsetReset    = AutoOffsetReset.Earliest,
                EnablePartitionEof = true
            };

            var schemaRegistryConfig = new SchemaRegistryConfig
            {
                Url = schemaRegistryServers,
            };

            var avroSerializerConfig = new AvroSerializerConfig
            {
                SubjectNameStrategy = nameStrategy
            };

            var adminClientConfig = new AdminClientConfig
            {
                BootstrapServers = bootstrapServers
            };

            string topic = Guid.NewGuid().ToString();

            using (var adminClient = new AdminClientBuilder(adminClientConfig).Build())
            {
                adminClient.CreateTopicsAsync(
                    new List <TopicSpecification> {
                    new TopicSpecification {
                        Name = topic, NumPartitions = 1, ReplicationFactor = 1
                    }
                }).Wait();
            }

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var producer =
                           new ProducerBuilder <string, ProduceConsumeUser>(producerConfig)
                           .SetKeySerializer(new AvroSerializer <string>(schemaRegistry))
                           // Test ValueSubjectNameStrategy here,
                           // and KeySubjectNameStrategy in ProduceConsumeGeneric.
                           .SetValueSerializer(new AvroSerializer <ProduceConsumeUser>(schemaRegistry, avroSerializerConfig))
                           .Build())
                {
                    for (int i = 0; i < 100; ++i)
                    {
                        producer
                        .ProduceAsync(topic, new Message <string, ProduceConsumeUser> {
                            Key = null, Value = null
                        })
                        .Wait();
                    }
                    Assert.Equal(0, producer.Flush(TimeSpan.FromSeconds(10)));
                }

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var consumer =
                           new ConsumerBuilder <string, ProduceConsumeUser>(consumerConfig)
                           .SetKeyDeserializer(new AvroDeserializer <string>(schemaRegistry).AsSyncOverAsync())
                           .SetValueDeserializer(new AvroDeserializer <ProduceConsumeUser>(schemaRegistry).AsSyncOverAsync())
                           .SetErrorHandler((_, e) => Assert.True(false, e.Reason))
                           .Build())
                {
                    consumer.Subscribe(topic);

                    int i = 0;
                    while (true)
                    {
                        var record = consumer.Consume(TimeSpan.FromMilliseconds(100));
                        if (record == null)
                        {
                            continue;
                        }
                        if (record.IsPartitionEOF)
                        {
                            break;
                        }

                        Assert.Null(record.Message.Key);
                        Assert.Null(record.Message.Value);
                        i += 1;
                    }

                    Assert.Equal(100, i);

                    consumer.Close();
                }
        }
示例#7
0
        private static void ProduceConsumeGeneric(string bootstrapServers, string schemaRegistryServers, SubjectNameStrategy nameStrategy)
        {
            var rs = (RecordSchema)RecordSchema.Parse(
                @"{
                    ""namespace"": ""Confluent.Kafka.Examples.AvroSpecific"",
                    ""type"": ""record"",
                    ""name"": ""ProduceConsumeUser2"",
                    ""fields"": [
                        {""name"": ""name"", ""type"": ""string""},
                        {""name"": ""favorite_number"",  ""type"": [""int"", ""null""]},
                        {""name"": ""favorite_color"", ""type"": [""string"", ""null""]}
                    ]
                  }"
                );

            var config = new ProducerConfig {
                BootstrapServers = bootstrapServers
            };
            var schemaRegistryConfig = new SchemaRegistryConfig
            {
                Url = schemaRegistryServers,
            };

            var avroSerializerConfig = new AvroSerializerConfig
            {
                SubjectNameStrategy = nameStrategy
            };

            var topic = Guid.NewGuid().ToString();

            DeliveryResult <GenericRecord, Null> dr;

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var p =
                           new ProducerBuilder <GenericRecord, Null>(config)
                           // Test KeySubjectNameStrategy here,
                           // and ValueSubjectNameStrategy in ProduceConsume.
                           .SetKeySerializer(new AvroSerializer <GenericRecord>(schemaRegistry, avroSerializerConfig))
                           .SetValueSerializer(Serializers.Null)
                           .Build())
                {
                    var record = new GenericRecord(rs);
                    record.Add("name", "my name 2");
                    record.Add("favorite_number", 44);
                    record.Add("favorite_color", null);
                    dr = p.ProduceAsync(topic, new Message <GenericRecord, Null> {
                        Key = record
                    }).Result;
                }

            // produce a specific record (to later consume back as a generic record).
            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var p =
                           new ProducerBuilder <ProduceConsumeUser2, Null>(config)
                           .SetKeySerializer(new AvroSerializer <ProduceConsumeUser2>(schemaRegistry, avroSerializerConfig))
                           .SetValueSerializer(Serializers.Null)
                           .Build())
                {
                    var user = new ProduceConsumeUser2
                    {
                        name            = "my name 3",
                        favorite_number = 47,
                        favorite_color  = "orange"
                    };

                    p.ProduceAsync(topic, new Message <ProduceConsumeUser2, Null> {
                        Key = user
                    }).Wait();
                }

            Assert.Null(dr.Message.Value);
            Assert.NotNull(dr.Message.Key);
            dr.Message.Key.TryGetValue("name", out object name);
            dr.Message.Key.TryGetValue("favorite_number", out object number);
            dr.Message.Key.TryGetValue("favorite_color", out object color);

            Assert.IsType <string>(name);
            Assert.IsType <int>(number);

            Assert.Equal("my name 2", name);
            Assert.Equal(44, number);
            Assert.Null(color);

            var cconfig = new ConsumerConfig {
                GroupId = Guid.NewGuid().ToString(), BootstrapServers = bootstrapServers
            };

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var consumer =
                           new ConsumerBuilder <GenericRecord, Null>(cconfig)
                           .SetKeyDeserializer(new AvroDeserializer <GenericRecord>(schemaRegistry).AsSyncOverAsync())
                           .SetValueDeserializer(Deserializers.Null)
                           .Build())
                {
                    // consume generic record produced as a generic record.
                    consumer.Assign(new List <TopicPartitionOffset> {
                        new TopicPartitionOffset(topic, 0, dr.Offset)
                    });
                    var record = consumer.Consume(new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token);
                    record.Message.Key.TryGetValue("name", out object msgName);
                    record.Message.Key.TryGetValue("favorite_number", out object msgNumber);
                    record.Message.Key.TryGetValue("favorite_color", out object msgColor);

                    Assert.IsType <string>(msgName);
                    Assert.IsType <int>(msgNumber);

                    Assert.Equal("my name 2", msgName);
                    Assert.Equal(44, msgNumber);
                    Assert.Null(msgColor);

                    // consume generic record produced as a specific record.
                    record = consumer.Consume(new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token);
                    record.Message.Key.TryGetValue("name", out msgName);
                    record.Message.Key.TryGetValue("favorite_number", out msgNumber);
                    record.Message.Key.TryGetValue("favorite_color", out msgColor);

                    Assert.IsType <string>(msgName);
                    Assert.IsType <int>(msgNumber);
                    Assert.IsType <string>(msgColor);

                    Assert.Equal("my name 3", msgName);
                    Assert.Equal(47, msgNumber);
                    Assert.Equal("orange", msgColor);
                }

            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
                using (var consumer =
                           new ConsumerBuilder <ProduceConsumeUser2, Null>(cconfig)
                           .SetKeyDeserializer(new AvroDeserializer <ProduceConsumeUser2>(schemaRegistry).AsSyncOverAsync())
                           .SetValueDeserializer(Deserializers.Null)
                           .Build())
                {
                    consumer.Assign(new List <TopicPartitionOffset> {
                        new TopicPartitionOffset(topic, 0, dr.Offset)
                    });
                    var record = consumer.Consume(new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token);
                    Assert.Equal("my name 2", record.Message.Key.name);
                    Assert.Equal(44, record.Message.Key.favorite_number);
                    Assert.Null(record.Message.Key.favorite_color);
                }

            // Check that what's in schema registry is what's expected.
            using (var schemaRegistry = new CachedSchemaRegistryClient(schemaRegistryConfig))
            {
                var subjects = schemaRegistry.GetAllSubjectsAsync().Result;

                if (nameStrategy == SubjectNameStrategy.TopicRecord)
                {
                    Assert.Single(subjects.Where(s => s.Contains(topic)));
                    Assert.Single(subjects.Where(s => s == $"{topic}-{((Avro.RecordSchema)ProduceConsumeUser2._SCHEMA).Fullname}"));
                }

                if (nameStrategy == SubjectNameStrategy.Topic)
                {
                    Assert.Single(subjects.Where(s => s.Contains(topic)));
                    Assert.Single(subjects.Where(s => s == $"{topic}-key"));
                }

                if (nameStrategy == SubjectNameStrategy.Record)
                {
                    Assert.Single(subjects.Where(s => s.Contains(topic))); // the string key.
                    Assert.Single(subjects.Where(s => s == $"{((Avro.RecordSchema)ProduceConsumeUser2._SCHEMA).Fullname}"));
                }
            }
        }
示例#8
0
        /// <summary>
        ///     Initialize a new instance of the SchemaRegistryClient class.
        /// </summary>
        /// <param name="config">
        ///     Configuration properties.
        /// </param>
        public CachedSchemaRegistryClient(IEnumerable <KeyValuePair <string, string> > config)
        {
            if (config == null)
            {
                throw new ArgumentNullException("config properties must be specified.");
            }

            var schemaRegistryUrisMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl);

            if (schemaRegistryUrisMaybe.Value == null)
            {
                throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl} configuration property must be specified.");
            }
            var schemaRegistryUris = (string)schemaRegistryUrisMaybe.Value;

            var timeoutMsMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs);
            int timeoutMs;

            try { timeoutMs = timeoutMsMaybe.Value == null ? DefaultTimeout : Convert.ToInt32(timeoutMsMaybe.Value); }
            catch (FormatException) { throw new ArgumentException($"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs} must be an integer."); }

            var identityMapCapacityMaybe = config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas);

            try { this.identityMapCapacity = identityMapCapacityMaybe.Value == null ? DefaultMaxCachedSchemas : Convert.ToInt32(identityMapCapacityMaybe.Value); }
            catch (FormatException) { throw new ArgumentException($"Configured value for {SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas} must be an integer."); }

            var basicAuthSource = Convert.ToString(config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource).Value) ?? "";
            var basicAuthInfo   = Convert.ToString(config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo).Value) ?? "";

            string username = null;
            string password = null;

            if (basicAuthSource == "USER_INFO" || basicAuthSource == "")
            {
                if (basicAuthInfo != "")
                {
                    var userPass = (basicAuthInfo).Split(':');
                    if (userPass.Length != 2)
                    {
                        throw new ArgumentException($"Configuration property {SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo} must be of the form 'username:password'.");
                    }
                    username = userPass[0];
                    password = userPass[1];
                }
            }
            else if (basicAuthSource == "SASL_INHERIT")
            {
                if (basicAuthInfo != "")
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but {SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo} as also specified.");
                }
                var saslUsername = config.FirstOrDefault(prop => prop.Key == "sasl.username");
                var saslPassword = config.FirstOrDefault(prop => prop.Key == "sasl.password");
                if (saslUsername.Value == null)
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but 'sasl.username' property not specified.");
                }
                if (saslPassword.Value == null)
                {
                    throw new ArgumentException($"{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource} set to 'SASL_INHERIT', but 'sasl.password' property not specified.");
                }
                username = Convert.ToString(saslUsername.Value);
                password = Convert.ToString(saslPassword.Value);
            }
            else
            {
                throw new ArgumentException($"Invalid value '{basicAuthSource}' specified for property '{SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource}'");
            }

            var keySubjNameStrategy   = Convert.ToString(config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryKeySubjectNameStrategy).Value) ?? "";
            var valueSubjNameStrategy = Convert.ToString(config.FirstOrDefault(prop => prop.Key.ToLower() == SchemaRegistryConfig.PropertyNames.SchemaRegistryValueSubjectNameStrategy).Value) ?? "";

            if (Enum.TryParse <SubjectNameStrategy>(keySubjNameStrategy, out var strategy))
            {
                KeySubjectNameStrategy = strategy;
            }

            if (Enum.TryParse <SubjectNameStrategy>(valueSubjNameStrategy, out strategy))
            {
                ValueSubjectNameStrategy = strategy;
            }

            foreach (var property in config)
            {
                if (!property.Key.StartsWith("schema.registry."))
                {
                    continue;
                }

                if (property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryUrl &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryRequestTimeoutMs &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryMaxCachedSchemas &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthCredentialsSource &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryBasicAuthUserInfo &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryKeySubjectNameStrategy &&
                    property.Key != SchemaRegistryConfig.PropertyNames.SchemaRegistryValueSubjectNameStrategy)
                {
                    throw new ArgumentException($"Unknown configuration parameter {property.Key}");
                }
            }

            this.restService = new RestService(schemaRegistryUris, timeoutMs, username, password);
        }