/// <summary> /// Creates a deserializer. /// </summary> /// <param name="registryClient"> /// The client to use for Schema Registry operations. (The client will not be disposed.) /// </param> /// <param name="deserializerBuilder"> /// The deserializer builder used to generate deserialization functions for C# types. If /// none is provided, the default deserializer builder will be used. /// </param> /// <param name="schemaReader"> /// The JSON schema reader used to convert schemas received from the registry into abstract /// representations. If none is provided, the default schema reader will be used. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when the registry client is null. /// </exception> public AsyncSchemaRegistryDeserializer( ISchemaRegistryClient registryClient, IBinaryDeserializerBuilder deserializerBuilder = null, IJsonSchemaReader schemaReader = null, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { if (registryClient == null) { throw new ArgumentNullException(nameof(registryClient)); } if (tombstoneBehavior != TombstoneBehavior.None && default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } DeserializerBuilder = deserializerBuilder ?? new BinaryDeserializerBuilder(); RegistryClient = registryClient; SchemaReader = schemaReader ?? new JsonSchemaReader(); TombstoneBehavior = tombstoneBehavior; _cache = new Dictionary <int, Task <Func <Stream, T> > >(); _disposeRegistryClient = false; }
/// <summary> /// Sets an Avro deserializer for values. /// </summary> /// <typeparam name="TKey"> /// The type of key to be deserialized. /// </typeparam> /// <typeparam name="TValue"> /// The type of value to be deserialized. /// </typeparam> /// <param name="consumerBuilder"> /// A <see cref="ConsumerBuilder{TKey, TValue}" /> instance to be configured. /// </param> /// <param name="deserializerBuilder"> /// A deserializer builder. /// </param> /// <param name="subject"> /// The subject of the schema that should be used to deserialize values. The latest version /// of the subject will be resolved. /// </param> /// <param name="tombstoneBehavior"> /// How the deserializer should handle tombstone records. /// </param> /// <returns> /// <paramref name="consumerBuilder" /> with an Avro deserializer configured for /// <typeparamref name="TValue" />. /// </returns> public static async Task <ConsumerBuilder <TKey, TValue> > SetAvroValueDeserializer <TKey, TValue>( this ConsumerBuilder <TKey, TValue> consumerBuilder, ISchemaRegistryDeserializerBuilder deserializerBuilder, string subject, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) => consumerBuilder.SetValueDeserializer( await deserializerBuilder.Build <TValue>(subject, tombstoneBehavior).ConfigureAwait(false));
/// <summary> /// Initializes a new instance of the <see cref="AsyncSchemaRegistrySerializer{T}" /> /// class with a Schema Registry configuration. /// </summary> /// <param name="registryConfiguration"> /// A Schema Registry configuration. Using the <see cref="SchemaRegistryConfig" /> class is /// highly recommended. /// </param> /// <param name="registerAutomatically"> /// Whether the serializer should automatically register schemas that match the type being /// serialized. /// </param> /// <param name="schemaBuilder"> /// A schema builder instance that should be used to create schemas for .NET <see cref="Type" />s /// when registering automatically. If none is provided, the default <see cref="SchemaBuilder" /> /// will be used. /// </param> /// <param name="schemaReader"> /// A schema reader instance that should be used to convert schemas received from the /// Registry into abstract representations. If none is provided, the default /// <see cref="JsonSchemaReader" /> will be used. /// </param> /// <param name="schemaWriter"> /// A schema writer instance that should be used to convert abstract schema representations /// when registering automatically. If none is provided, the default <see cref="JsonSchemaWriter" /> /// will be used. /// </param> /// <param name="serializerBuilder"> /// A serializer builder instance that should be used to generate serialization functions /// for .NET <see cref="Type" />s. If none is provided, the default <see cref="BinarySerializerBuilder" /> /// will be used. /// </param> /// <param name="subjectNameBuilder"> /// A function that determines a subject name given the topic name and a component type /// (key or value). If none is provided, the default <c>{topic name}-{component}</c> naming /// convention will be used. /// </param> /// <param name="tombstoneBehavior"> /// How the serializer should handle tombstone records. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when <paramref name="registryConfiguration" /> is <c>null</c>. /// </exception> /// <exception cref="UnsupportedTypeException"> /// Thrown when <paramref name="tombstoneBehavior" /> is incompatible with /// <typeparamref name="T" />. /// </exception> public AsyncSchemaRegistrySerializer( IEnumerable <KeyValuePair <string, string> > registryConfiguration, AutomaticRegistrationBehavior registerAutomatically = AutomaticRegistrationBehavior.Never, Abstract.ISchemaBuilder schemaBuilder = null, IJsonSchemaReader schemaReader = null, IJsonSchemaWriter schemaWriter = null, IBinarySerializerBuilder serializerBuilder = null, Func <SerializationContext, string> subjectNameBuilder = null, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { if (registryConfiguration == null) { throw new ArgumentNullException(nameof(registryConfiguration)); } if (tombstoneBehavior != TombstoneBehavior.None && default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } RegisterAutomatically = registerAutomatically; RegistryClient = new CachedSchemaRegistryClient(registryConfiguration); SchemaBuilder = schemaBuilder ?? new Abstract.SchemaBuilder(); SchemaReader = schemaReader ?? new JsonSchemaReader(); SchemaWriter = schemaWriter ?? new JsonSchemaWriter(); SerializerBuilder = serializerBuilder ?? new BinarySerializerBuilder(); SubjectNameBuilder = subjectNameBuilder ?? (c => $"{c.Topic}-{(c.Component == MessageComponentType.Key ? "key" : "value")}"); TombstoneBehavior = tombstoneBehavior; cache = new Dictionary <string, Task <Func <T, byte[]> > >(); disposeRegistryClient = true; }
/// <summary> /// Creates a serializer. /// </summary> /// <param name="registryClient"> /// The client to use for Schema Registry operations. (The client will not be disposed.) /// </param> /// <param name="registerAutomatically"> /// When to automatically register schemas that match the type being serialized. /// </param> /// <param name="schemaBuilder"> /// The schema builder to use to create a schema for a C# type when registering automatically. /// If none is provided, the default schema builder will be used. /// </param> /// <param name="schemaReader"> /// The JSON schema reader to use to convert schemas received from the registry into abstract /// representations. If none is provided, the default schema reader will be used. /// </param> /// <param name="schemaWriter"> /// The JSON schema writer to use to convert abstract schema representations when registering /// automatically. If none is provided, the default schema writer will be used. /// </param> /// <param name="serializerBuilder"> /// The deserializer builder to use to build serialization functions for C# types. If none /// is provided, the default serializer builder will be used. /// </param> /// <param name="subjectNameBuilder"> /// A function that determines the subject name given the topic name and a component type /// (key or value). If none is provided, the default "{topic name}-{component}" naming /// convention will be used. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when the registry client is null. /// </exception> public AsyncSchemaRegistrySerializer( ISchemaRegistryClient registryClient, AutomaticRegistrationBehavior registerAutomatically = AutomaticRegistrationBehavior.Never, Abstract.ISchemaBuilder schemaBuilder = null, IJsonSchemaReader schemaReader = null, IJsonSchemaWriter schemaWriter = null, IBinarySerializerBuilder serializerBuilder = null, Func <SerializationContext, string> subjectNameBuilder = null, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { if (registryClient == null) { throw new ArgumentNullException(nameof(registryClient)); } if (tombstoneBehavior != TombstoneBehavior.None && default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } RegisterAutomatically = registerAutomatically; SchemaBuilder = schemaBuilder ?? new Abstract.SchemaBuilder(); SchemaReader = schemaReader ?? new JsonSchemaReader(); SchemaWriter = schemaWriter ?? new JsonSchemaWriter(); SerializerBuilder = serializerBuilder ?? new BinarySerializerBuilder(); SubjectNameBuilder = subjectNameBuilder ?? (c => $"{c.Topic}-{(c.Component == MessageComponentType.Key ? "key" : "value")}"); TombstoneBehavior = tombstoneBehavior; _cache = new Dictionary <string, Task <Func <T, byte[]> > >(); _register = (subject, json) => registryClient.RegisterSchemaAsync(subject, json); _resolve = subject => registryClient.GetLatestSchemaAsync(subject); }
/// <summary> /// Builds a deserializer for a specific schema. /// </summary> /// <param name="id"> /// The ID of the schema that should be used to deserialize data. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> /// <exception cref="AggregateException"> /// Thrown when the type is incompatible with the retrieved schema. /// </exception> public virtual async Task <IDeserializer <T> > Build <T>( int id, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { return(Build <T>(id, await RegistryClient.GetSchemaAsync(id).ConfigureAwait(false), tombstoneBehavior)); }
/// <summary> /// Initializes a new instance of the <see cref="DelegateSerializer{T}" /> class. /// </summary> /// <param name="delegate"> /// A serialization function for <typeparamref name="T" />. /// </param> /// <param name="schemaId"> /// The ID of the schema that <paramref name="delegate" /> was built against. /// </param> /// <param name="tombstoneBehavior"> /// How the serializer should handle tombstone records. /// </param> public DelegateSerializer(Implementation @delegate, int schemaId, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { SchemaId = schemaId; TombstoneBehavior = tombstoneBehavior; this.@delegate = @delegate ?? throw new ArgumentNullException(nameof(@delegate)); }
/// <summary> /// Creates a deserializer. /// </summary> /// <param name="registryConfiguration"> /// Schema Registry configuration. Using the <see cref="SchemaRegistryConfig" /> class is /// highly recommended. /// </param> /// <param name="deserializerBuilder"> /// The deserializer builder to use to to generate deserialization functions for C# types. /// If none is provided, the default deserializer builder will be used. /// </param> /// <param name="schemaReader"> /// The JSON schema reader to use to convert schemas received from the registry into abstract /// representations. If none is provided, the default schema reader will be used. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when the registry configuration is null. /// </exception> public AsyncSchemaRegistryDeserializer( IEnumerable <KeyValuePair <string, string> > registryConfiguration, IBinaryDeserializerBuilder deserializerBuilder = null, IJsonSchemaReader schemaReader = null, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { if (registryConfiguration == null) { throw new ArgumentNullException(nameof(registryConfiguration)); } if (tombstoneBehavior != TombstoneBehavior.None && default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } DeserializerBuilder = deserializerBuilder ?? new BinaryDeserializerBuilder(); RegistryClient = new CachedSchemaRegistryClient(registryConfiguration); SchemaReader = schemaReader ?? new JsonSchemaReader(); TombstoneBehavior = tombstoneBehavior; _cache = new Dictionary <int, Task <Func <Stream, T> > >(); _disposeRegistryClient = true; }
/// <summary> /// Builds a serializer for a specific schema. /// </summary> /// <param name="id"> /// The ID of the schema that should be used to serialize data. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> /// <exception cref="AggregateException"> /// Thrown when the type is incompatible with the retrieved schema. /// </exception> public async Task <ISerializer <T> > Build <T>( int id, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { var schema = await RegistryClient.GetSchemaAsync(id).ConfigureAwait(false); return(Build <T>(id, schema, tombstoneBehavior)); }
/// <summary> /// Set the message value deserializer. /// </summary> /// <param name="consumerBuilder"> /// The <see cref="ConsumerBuilder{TKey, TValue}" /> instance to be configured. /// </param> /// <param name="registryConfiguration"> /// Schema Registry configuration. Using the <see cref="SchemaRegistryConfig" /> class is /// highly recommended. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> public static ConsumerBuilder <TKey, TValue> SetAvroValueDeserializer <TKey, TValue>( this ConsumerBuilder <TKey, TValue> consumerBuilder, IEnumerable <KeyValuePair <string, string> > registryConfiguration, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) => consumerBuilder.SetValueDeserializer( new AsyncSchemaRegistryDeserializer <TValue>( registryConfiguration, tombstoneBehavior: tombstoneBehavior ).AsSyncOverAsync());
/// <summary> /// Builds a deserializer for a specific schema. /// </summary> /// <param name="subject"> /// The subject of the schema that should be used to deserialize data. The latest version /// of the subject will be resolved. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> /// <exception cref="AggregateException"> /// Thrown when the type is incompatible with the retrieved schema. /// </exception> public virtual async Task <IDeserializer <T> > Build <T>( string subject, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { var schema = await RegistryClient.GetLatestSchemaAsync(subject).ConfigureAwait(false); return(Build <T>(schema.Id, schema.SchemaString, tombstoneBehavior)); }
/// <summary> /// Set the message value deserializer. /// </summary> /// <param name="consumerBuilder"> /// The <see cref="ConsumerBuilder{TKey, TValue}" /> instance to be configured. /// </param> /// <param name="registryClient"> /// The client to use for Schema Registry operations. The client should only be disposed /// after the consumer; the deserializer will use it to request schemas as messages are /// being consumed. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> public static ConsumerBuilder <TKey, TValue> SetAvroValueDeserializer <TKey, TValue>( this ConsumerBuilder <TKey, TValue> consumerBuilder, ISchemaRegistryClient registryClient, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) => consumerBuilder.SetValueDeserializer( new AsyncSchemaRegistryDeserializer <TValue>( registryClient, tombstoneBehavior: tombstoneBehavior ).AsSyncOverAsync());
/// <summary> /// Builds a serializer for the Confluent wire format. /// </summary> /// <param name="id"> /// A schema ID to include in each serialized payload. /// </param> /// <param name="json"> /// The schema to build the Avro serializer from. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> protected virtual ISerializer <T> Build <T>( int id, string json, TombstoneBehavior tombstoneBehavior ) { var schema = SchemaReader.Read(json); if (tombstoneBehavior != TombstoneBehavior.None) { if (default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } var hasNull = schema is Abstract.NullSchema || (schema is Abstract.UnionSchema union && union.Schemas.Any(s => s is Abstract.NullSchema)); if (tombstoneBehavior == TombstoneBehavior.Strict && hasNull) { throw new UnsupportedSchemaException(schema, "Tombstone serialization is not supported for schemas that can represent null values."); } } var bytes = BitConverter.GetBytes(id); if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } var serialize = SerializerBuilder.BuildDelegate <T>(schema); return(new DelegateSerializer <T>((data, context) => { if (data == null && tombstoneBehavior != TombstoneBehavior.None) { if (context.Component == MessageComponentType.Value || tombstoneBehavior != TombstoneBehavior.Strict) { return null; } } var stream = new MemoryStream(); using (stream) { stream.WriteByte(0x00); stream.Write(bytes, 0, bytes.Length); serialize(data, stream); } return stream.ToArray(); })); }
/// <summary> /// Creates a serializer. /// </summary> /// <param name="registryConfiguration"> /// Schema Registry configuration. Using the <see cref="SchemaRegistryConfig" /> class is /// highly recommended. /// </param> /// <param name="registerAutomatically"> /// When to automatically register schemas that match the type being serialized. /// </param> /// <param name="schemaBuilder"> /// The schema builder to use to create a schema for a C# type when registering automatically. /// If none is provided, the default schema builder will be used. /// </param> /// <param name="schemaReader"> /// The JSON schema reader to use to convert schemas received from the registry into abstract /// representations. If none is provided, the default schema reader will be used. /// </param> /// <param name="schemaWriter"> /// The JSON schema writer to use to convert abstract schema representations when registering /// automatically. If none is provided, the default schema writer will be used. /// </param> /// <param name="serializerBuilder"> /// The deserializer builder to use to build serialization functions for C# types. If none /// is provided, the default serializer builder will be used. /// </param> /// <param name="subjectNameBuilder"> /// A function that determines the subject name given the topic name and a component type /// (key or value). If none is provided, the default "{topic name}-{component}" naming /// convention will be used. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> /// <exception cref="ArgumentNullException"> /// Thrown when the registry configuration is null. /// </exception> public AsyncSchemaRegistrySerializer( IEnumerable <KeyValuePair <string, string> > registryConfiguration, AutomaticRegistrationBehavior registerAutomatically = AutomaticRegistrationBehavior.Never, Abstract.ISchemaBuilder schemaBuilder = null, IJsonSchemaReader schemaReader = null, IJsonSchemaWriter schemaWriter = null, IBinarySerializerBuilder serializerBuilder = null, Func <SerializationContext, string> subjectNameBuilder = null, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { if (registryConfiguration == null) { throw new ArgumentNullException(nameof(registryConfiguration)); } if (tombstoneBehavior != TombstoneBehavior.None && default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } RegisterAutomatically = registerAutomatically; SchemaBuilder = schemaBuilder ?? new Abstract.SchemaBuilder(); SchemaReader = schemaReader ?? new JsonSchemaReader(); SchemaWriter = schemaWriter ?? new JsonSchemaWriter(); SerializerBuilder = serializerBuilder ?? new BinarySerializerBuilder(); SubjectNameBuilder = subjectNameBuilder ?? (c => $"{c.Topic}-{(c.Component == MessageComponentType.Key ? "key" : "value")}"); TombstoneBehavior = tombstoneBehavior; _cache = new Dictionary <string, Task <Func <T, byte[]> > >(); _register = async(subject, json) => { using (var registry = new CachedSchemaRegistryClient(registryConfiguration)) { return(await registry.RegisterSchemaAsync(subject, new Schema(json, SchemaType.Avro)).ConfigureAwait(false)); } }; _resolve = async subject => { using (var registry = new CachedSchemaRegistryClient(registryConfiguration)) { var schema = await registry.GetLatestSchemaAsync(subject).ConfigureAwait(false); if (schema.SchemaType != SchemaType.Avro) { throw new UnsupportedSchemaException(null, $"The latest schema with subject {subject} is not an Avro schema."); } return(schema); } }; }
/// <summary> /// Builds a serializer for a specific schema. /// </summary> /// <param name="subject"> /// The subject of the schema that should be used to serialize data. /// </param> /// <param name="version"> /// The version of the subject to be resolved. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> /// <exception cref="AggregateException"> /// Thrown when the type is incompatible with the retrieved schema. /// </exception> public virtual async Task <ISerializer <T> > Build <T>( string subject, int version, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None ) { var schema = await RegistryClient.GetSchemaAsync(subject, version).ConfigureAwait(false); var id = await RegistryClient.GetSchemaIdAsync(subject, schema).ConfigureAwait(false); return(Build <T>(id, schema, tombstoneBehavior)); }
/// <summary> /// Sets an Avro deserializer for values. /// </summary> /// <typeparam name="TKey"> /// The type of key to be deserialized. /// </typeparam> /// <typeparam name="TValue"> /// The type of value to be deserialized. /// </typeparam> /// <param name="consumerBuilder"> /// A <see cref="ConsumerBuilder{TKey, TValue}" /> instance to be configured. /// </param> /// <param name="registryClient"> /// A Schema Registry client to use to resolve the schema. (The client will not be /// disposed.) /// </param> /// <param name="id"> /// The ID of the schema that should be used to deserialize values. /// </param> /// <param name="tombstoneBehavior"> /// How the deserializer should handle tombstone records. /// </param> /// <returns> /// <paramref name="consumerBuilder" /> with an Avro deserializer configured for /// <typeparamref name="TValue" />. /// </returns> public static async Task <ConsumerBuilder <TKey, TValue> > SetAvroValueDeserializer <TKey, TValue>( this ConsumerBuilder <TKey, TValue> consumerBuilder, ISchemaRegistryClient registryClient, int id, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { using var deserializerBuilder = new SchemaRegistryDeserializerBuilder(registryClient); return(await consumerBuilder .SetAvroValueDeserializer(deserializerBuilder, id, tombstoneBehavior) .ConfigureAwait(false)); }
/// <summary> /// Sets an Avro deserializer for values. /// </summary> /// <typeparam name="TKey"> /// The type of key to be deserialized. /// </typeparam> /// <typeparam name="TValue"> /// The type of value to be deserialized. /// </typeparam> /// <param name="consumerBuilder"> /// A <see cref="ConsumerBuilder{TKey, TValue}" /> instance to be configured. /// </param> /// <param name="registryConfiguration"> /// A Schema Registry configuration. Using the <see cref="SchemaRegistryConfig" /> class is /// highly recommended. /// </param> /// <param name="subject"> /// The subject of the schema that should be used to deserialize values. The latest version /// of the subject will be resolved. /// </param> /// <param name="tombstoneBehavior"> /// How the deserializer should handle tombstone records. /// </param> /// <returns> /// <paramref name="consumerBuilder" /> with an Avro deserializer configured for /// <typeparamref name="TValue" />. /// </returns> public static async Task <ConsumerBuilder <TKey, TValue> > SetAvroValueDeserializer <TKey, TValue>( this ConsumerBuilder <TKey, TValue> consumerBuilder, IEnumerable <KeyValuePair <string, string> > registryConfiguration, string subject, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { using var deserializerBuilder = new SchemaRegistryDeserializerBuilder(registryConfiguration); return(await consumerBuilder .SetAvroValueDeserializer(deserializerBuilder, subject, tombstoneBehavior) .ConfigureAwait(false)); }
/// <exception cref="UnsupportedTypeException"> /// Thrown when <typeparamref name="T" /> is incompatible with the retrieved schema. /// </exception> /// <inheritdoc /> public virtual async Task <IDeserializer <T> > Build <T>( string subject, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { var schema = await RegistryClient.GetLatestSchemaAsync(subject).ConfigureAwait(false); if (schema.SchemaType != SchemaType.Avro) { throw new UnsupportedSchemaException(null, $"The latest schema with subject {subject} is not an Avro schema."); } return(Build <T>(schema.Id, schema.SchemaString, tombstoneBehavior)); }
/// <exception cref="UnsupportedTypeException"> /// Thrown when <typeparamref name="T" /> is incompatible with the retrieved schema. /// </exception> /// <inheritdoc /> public async Task <ISerializer <T> > Build <T>( int id, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { var schema = await RegistryClient.GetSchemaAsync(id).ConfigureAwait(false); if (schema.SchemaType != SchemaType.Avro) { throw new UnsupportedSchemaException(null, $"The schema with ID {id} is not an Avro schema."); } return(Build <T>(id, schema.SchemaString, tombstoneBehavior)); }
/// <summary> /// Builds a serializer for the Confluent wire format. /// </summary> /// <typeparam name="T"> /// The type to be deserialized. /// </typeparam> /// <param name="id"> /// A schema ID to include in each serialized payload. /// </param> /// <param name="json"> /// The schema to build the Avro serializer from. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the serializer on tombstone records. /// </param> /// <returns> /// A <see cref="ISerializer{T}" /> based on <paramref name="json" />. /// </returns> protected virtual ISerializer <T> Build <T>( int id, string json, TombstoneBehavior tombstoneBehavior) { var schema = SchemaReader.Read(json); if (tombstoneBehavior != TombstoneBehavior.None) { if (default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } var hasNull = schema is Abstract.NullSchema || (schema is Abstract.UnionSchema union && union.Schemas.Any(s => s is Abstract.NullSchema)); if (tombstoneBehavior == TombstoneBehavior.Strict && hasNull) { throw new UnsupportedSchemaException(schema, "Tombstone serialization is not supported for schemas that can represent null values."); } } var inner = SerializerBuilder.BuildDelegateExpression <T>(schema); var stream = Expression.Parameter(typeof(Stream)); var value = inner.Parameters[0]; var writerConstructor = inner.Parameters[1].Type .GetConstructor(new[] { stream.Type }); if (schema is Abstract.BytesSchema) { inner = new WireFormatBytesSerializerRewriter(stream) .VisitAndConvert(inner, GetType().Name); } return(new DelegateSerializer <T>( Expression .Lambda <DelegateSerializer <T> .Implementation>( Expression.Invoke( inner, value, Expression.New(writerConstructor, stream)), new[] { value, stream }) .Compile(), id, tombstoneBehavior)); }
/// <exception cref="UnsupportedTypeException"> /// Thrown when <typeparamref name="T" /> is incompatible with the retrieved schema. /// </exception> /// <inheritdoc /> public virtual async Task <IDeserializer <T> > Build <T>( string subject, int version, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { var schema = await RegistryClient.GetRegisteredSchemaAsync(subject, version).ConfigureAwait(false); if (schema.SchemaType != SchemaType.Avro) { throw new UnsupportedSchemaException(null, $"The schema with subject {subject} and version {version} is not an Avro schema."); } var id = await RegistryClient.GetSchemaIdAsync(subject, schema).ConfigureAwait(false); return(Build <T>(id, schema.SchemaString, tombstoneBehavior)); }
/// <exception cref="UnsupportedTypeException"> /// Thrown when <typeparamref name="T" /> is incompatible with the retrieved schema. /// </exception> /// <inheritdoc /> public async Task <ISerializer <T> > Build <T>( string subject, AutomaticRegistrationBehavior registerAutomatically = AutomaticRegistrationBehavior.Never, TombstoneBehavior tombstoneBehavior = TombstoneBehavior.None) { switch (registerAutomatically) { case AutomaticRegistrationBehavior.Always: var json = SchemaWriter.Write(SchemaBuilder.BuildSchema <T>()); var id = await RegistryClient.RegisterSchemaAsync(subject, new Schema(json, SchemaType.Avro)).ConfigureAwait(false); return(Build <T>(id, json, tombstoneBehavior)); case AutomaticRegistrationBehavior.Never: var existing = await RegistryClient.GetLatestSchemaAsync(subject).ConfigureAwait(false); return(Build <T>(existing.Id, existing.SchemaString, tombstoneBehavior)); default: throw new ArgumentOutOfRangeException(nameof(registerAutomatically)); } }
/// <summary> /// Builds a deserializer for the Confluent wire format. /// </summary> /// <param name="id"> /// A schema ID that all payloads must be serialized with. If a received schema ID does not /// match this ID, <see cref="InvalidDataException" /> will be thrown. /// </param> /// <param name="json"> /// The schema to build the Avro deserializer from. /// </param> /// <param name="tombstoneBehavior"> /// The behavior of the deserializer on tombstone records. /// </param> protected virtual IDeserializer <T> Build <T>( int id, string json, TombstoneBehavior tombstoneBehavior ) { var schema = SchemaReader.Read(json); if (tombstoneBehavior != TombstoneBehavior.None) { if (default(T) != null) { throw new UnsupportedTypeException(typeof(T), $"{typeof(T)} cannot represent tombstone values."); } var hasNull = schema is NullSchema || (schema is UnionSchema union && union.Schemas.Any(s => s is NullSchema)); if (tombstoneBehavior == TombstoneBehavior.Strict && hasNull) { throw new UnsupportedSchemaException(schema, "Tombstone deserialization is not supported for schemas that can represent null values."); } } var deserialize = DeserializerBuilder.BuildDelegate <T>(schema); return(new DelegateDeserializer <T>((data, isNull, context) => { if (isNull && tombstoneBehavior != TombstoneBehavior.None) { if (context.Component == MessageComponentType.Value || tombstoneBehavior != TombstoneBehavior.Strict) { return default; } } using (var stream = new MemoryStream(data, false)) { var bytes = new byte[4]; if (stream.ReadByte() != 0x00 || stream.Read(bytes, 0, bytes.Length) != bytes.Length) { throw new InvalidDataException("Data does not conform to the Confluent wire format."); } if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } var received = BitConverter.ToInt32(bytes, 0); if (received != id) { throw new InvalidDataException($"The received schema ({received}) does not match the specified schema ({id})."); } return deserialize(stream); } })); }