/// <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> /// Serialize a message. (See <see cref="IAsyncSerializer{T}.SerializeAsync(T, SerializationContext)" />.) /// </summary> public virtual async Task <byte[]> SerializeAsync(T data, SerializationContext context) { var serialize = await(_cache.GetOrAdd(SubjectNameBuilder(context), async subject => { int id; Action <T, Stream> @delegate; try { var existing = await _resolve(subject).ConfigureAwait(false); var schema = SchemaReader.Read(existing.SchemaString); @delegate = SerializerBuilder.BuildDelegate <T>(schema); id = existing.Id; } catch (Exception e) when(RegisterAutomatically && ( (e is SchemaRegistryException sre && sre.ErrorCode == 40401) || (e is AggregateException a && a.InnerExceptions.All(i => i is UnsupportedSchemaException || i is UnsupportedTypeException )) )) { var schema = SchemaBuilder.BuildSchema <T>(); var json = SchemaWriter.Write(schema); @delegate = SerializerBuilder.BuildDelegate <T>(schema); id = await _register(subject, json).ConfigureAwait(false); } var bytes = BitConverter.GetBytes(id); if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } return(value => { var stream = new MemoryStream(); using (stream) { stream.WriteByte(0x00); stream.Write(bytes, 0, bytes.Length); @delegate(value, stream); } return stream.ToArray(); }); })).ConfigureAwait(false); return(serialize(data)); }
/// <inheritdoc /> public virtual async Task <byte[]> SerializeAsync(T data, SerializationContext context) { var subject = SubjectNameBuilder(context); Task <Func <T, byte[]> > task; lock (cache) { if (!cache.TryGetValue(subject, out task) || task.IsFaulted) { cache[subject] = task = ((Func <string, Task <Func <T, byte[]> > >)(async subject => { switch (RegisterAutomatically) { case AutomaticRegistrationBehavior.Always: var schema = SchemaBuilder.BuildSchema <T>(); var id = await RegistryClient.RegisterSchemaAsync(subject, new Schema(SchemaWriter.Write(schema), SchemaType.Avro)).ConfigureAwait(false); return(Build(id, schema)); case AutomaticRegistrationBehavior.Never: var registration = await RegistryClient.GetLatestSchemaAsync(subject).ConfigureAwait(false); if (registration.SchemaType != SchemaType.Avro) { throw new UnsupportedSchemaException(null, $"The latest schema with subject {subject} is not an Avro schema."); } return(Build(registration.Id, SchemaReader.Read(registration.SchemaString))); default: throw new ArgumentOutOfRangeException(nameof(RegisterAutomatically)); } }))(subject); } } var serialize = await task.ConfigureAwait(false); if (data == null && TombstoneBehavior != TombstoneBehavior.None) { if (context.Component == MessageComponentType.Value || TombstoneBehavior != TombstoneBehavior.Strict) { return(null); } } return(serialize(data)); }
/// <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)); }
/// <summary> /// Serialize a message. (See <see cref="IAsyncSerializer{T}.SerializeAsync(T, SerializationContext)" />.) /// </summary> public virtual async Task <byte[]> SerializeAsync(T data, SerializationContext context) { var subject = SubjectNameBuilder(context); Task <Func <T, byte[]> > task; lock (_cache) { if (!_cache.TryGetValue(subject, out task) || task.IsFaulted) { _cache[subject] = task = ((Func <string, Task <Func <T, byte[]> > >)(async subject => { switch (RegisterAutomatically) { case AutomaticRegistrationBehavior.Always: var schema = SchemaBuilder.BuildSchema <T>(); var id = await _register(subject, SchemaWriter.Write(schema)).ConfigureAwait(false); return(Build(id, schema)); case AutomaticRegistrationBehavior.Never: var existing = await _resolve(subject).ConfigureAwait(false); return(Build(existing.Id, SchemaReader.Read(existing.SchemaString))); default: throw new ArgumentOutOfRangeException(nameof(RegisterAutomatically)); } }))(subject); } } var serialize = await task.ConfigureAwait(false); if (data == null && TombstoneBehavior != TombstoneBehavior.None) { if (context.Component == MessageComponentType.Value || TombstoneBehavior != TombstoneBehavior.Strict) { return(null); } } return(serialize(data)); }
/// <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="schema"> /// The schema to build the Avro serializer from. /// </param> protected virtual ISerializer <T> Build <T>(int id, string schema) { var bytes = BitConverter.GetBytes(id); if (BitConverter.IsLittleEndian) { Array.Reverse(bytes); } var serialize = SerializerBuilder.BuildDelegate <T>(SchemaReader.Read(schema)); return(new DelegateSerializer <T>((data, stream) => { stream.WriteByte(0x00); stream.Write(bytes, 0, bytes.Length); serialize(data, stream); })); }