Skip to content

Commit fc7681c

Browse files
committed
Fix nullability warnings
1 parent 4cf10ec commit fc7681c

File tree

6 files changed

+98
-53
lines changed

6 files changed

+98
-53
lines changed

src/Chr.Avro.Binary/Serialization/BinaryRecordDeserializerBuilderCase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ private Expression DeserializeUsingConstructor(
225225

226226
// Fields that have a match as a constructor parameter
227227
var fieldsMatchedToConstructorParam = recordSchema.Fields
228-
.Where(f => ctorParameters.Any(p => IsMatch(f, p.Name!)))
228+
.Where(f => ctorParameters.Any(p => IsMatch(f, p.Name)))
229229
.ToDictionary(f => f.Name);
230230

231231
var members = type.GetMembers(MemberVisibility);
@@ -243,7 +243,7 @@ private Expression DeserializeUsingConstructor(
243243
.Select(field =>
244244
{
245245
// There might not be a match for a particular field, in which case it will be deserialized and then ignored
246-
var constructorParameter = ctorParameters.SingleOrDefault(parameter => IsMatch(field, parameter.Name!));
246+
var constructorParameter = ctorParameters.SingleOrDefault(parameter => IsMatch(field, parameter.Name));
247247

248248
if (constructorParameter is null)
249249
{

src/Chr.Avro.Binary/Serialization/BinaryRecordSerializerBuilderCase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public virtual BinarySerializerBuilderCaseResult BuildExpression(Expression valu
135135
})
136136
.ToList();
137137

138-
// .NET Framework doesnt permit empty block expressions:
138+
// .NET Framework doesn't permit empty block expressions:
139139
expression = writes.Count > 0
140140
? Expression.Block(writes)
141141
: Expression.Empty() as Expression;

src/Chr.Avro.Confluent/Confluent/AsyncSchemaRegistryDeserializer.cs

Lines changed: 36 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,14 @@ namespace Chr.Avro.Confluent
2727
/// <inheritdoc />
2828
public class AsyncSchemaRegistryDeserializer<T> : IAsyncDeserializer<T>, IDisposable
2929
{
30+
private readonly object cacheLock = new object();
31+
private readonly bool disposeRegistryClient;
32+
3033
#if NET8_0_OR_GREATER
3134
private FrozenDictionary<int, Task<Func<ReadOnlyMemory<byte>, T>>> cache = FrozenDictionary<int, Task<Func<ReadOnlyMemory<byte>, T>>>.Empty;
3235
#else
3336
private Dictionary<int, Task<Func<ReadOnlyMemory<byte>, T>>> cache = new Dictionary<int, Task<Func<ReadOnlyMemory<byte>, T>>>();
3437
#endif
35-
private readonly object cacheLock = new object();
36-
private readonly bool disposeRegistryClient;
3738

3839
/// <summary>
3940
/// Initializes a new instance of the <see cref="AsyncSchemaRegistryDeserializer{T}" />
@@ -215,39 +216,6 @@ public virtual async Task<T> DeserializeAsync(ReadOnlyMemory<byte> data, bool is
215216
return (await task.ConfigureAwait(false))(data);
216217
}
217218

218-
private static int DeserializeSchemaId(ReadOnlyMemory<byte> data)
219-
{
220-
if (data.Length < 5)
221-
{
222-
throw new InvalidEncodingException(0, "The encoded data does not include a Confluent wire format header.");
223-
}
224-
225-
#if NET6_0_OR_GREATER
226-
var header = data.Span[..5];
227-
228-
if (header[0] != 0x00)
229-
{
230-
throw new InvalidEncodingException(0, "The encoded data does not conform to the Confluent wire format.");
231-
}
232-
233-
return BinaryPrimitives.ReadInt32BigEndian(header[1..]);
234-
#else
235-
var header = data.Slice(0, 5).ToArray();
236-
237-
if (header[0] != 0x00)
238-
{
239-
throw new InvalidEncodingException(0, "The encoded data does not conform to the Confluent wire format.");
240-
}
241-
242-
if (BitConverter.IsLittleEndian)
243-
{
244-
Array.Reverse(header, 1, 4);
245-
}
246-
247-
return BitConverter.ToInt32(header, 1);
248-
#endif
249-
}
250-
251219
/// <summary>
252220
/// Disposes the deserializer, freeing up any resources.
253221
/// </summary>
@@ -310,5 +278,38 @@ protected virtual Func<ReadOnlyMemory<byte>, T> Build(Abstract.Schema schema)
310278

311279
return Expression.Lambda<Func<ReadOnlyMemory<byte>, T>>(reader, memory).Compile();
312280
}
281+
282+
private static int DeserializeSchemaId(ReadOnlyMemory<byte> data)
283+
{
284+
if (data.Length < 5)
285+
{
286+
throw new InvalidEncodingException(0, "The encoded data does not include a Confluent wire format header.");
287+
}
288+
289+
#if NET6_0_OR_GREATER
290+
var header = data.Span[..5];
291+
292+
if (header[0] != 0x00)
293+
{
294+
throw new InvalidEncodingException(0, "The encoded data does not conform to the Confluent wire format.");
295+
}
296+
297+
return BinaryPrimitives.ReadInt32BigEndian(header[1..]);
298+
#else
299+
var header = data.Slice(0, 5).ToArray();
300+
301+
if (header[0] != 0x00)
302+
{
303+
throw new InvalidEncodingException(0, "The encoded data does not conform to the Confluent wire format.");
304+
}
305+
306+
if (BitConverter.IsLittleEndian)
307+
{
308+
Array.Reverse(header, 1, 4);
309+
}
310+
311+
return BitConverter.ToInt32(header, 1);
312+
#endif
313+
}
313314
}
314315
}

src/Chr.Avro/Serialization/RecordDeserializerBuilderCase.cs

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ public abstract class RecordDeserializerBuilderCase : DeserializerBuilderCase
3232
/// </returns>
3333
protected virtual ConstructorInfo? GetRecordConstructor(Type type, RecordSchema schema)
3434
{
35-
// TODO: Can we handle dynamic deserialisation better?
36-
// Instead of skipping the regular handling, maybe we can incorporate dynamic setters
37-
// in the member handling in BinaryRecordDeserializerBuilderCase.BuildExpression (and the Json one)?
38-
// if (type == typeof(object))
39-
// {
40-
// return null;
41-
// }
42-
4335
// Find the constructor that matches the record's fields the best, ie:
4436
// - all the constructor parameters match a field, or has a default value if not
4537
// - the constructor maximizes the number of fields matched in the record
@@ -170,11 +162,11 @@ protected virtual bool IsMatch(RecordField field, MemberInfo member)
170162
/// <returns>
171163
/// <c>true</c> if <paramref name="name" /> is a match; <c>false</c> otherwise.
172164
/// </returns>
173-
protected virtual bool IsMatch(RecordField field, string name)
165+
protected virtual bool IsMatch(RecordField field, string? name)
174166
{
175167
return string.Equals(
176168
FuzzyCharacters.Replace(field.Name, string.Empty),
177-
FuzzyCharacters.Replace(name, string.Empty),
169+
FuzzyCharacters.Replace(name ?? string.Empty, string.Empty),
178170
StringComparison.InvariantCultureIgnoreCase);
179171
}
180172
}

tests/Chr.Avro.Binary.Tests/RecordSerializationTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,6 @@ public void RecordWithDefaultConstructor()
699699
Assert.Equivalent(person, deserialized);
700700
}
701701

702-
703702
[Fact]
704703
public void RecordWithDefaultConstructor_Test()
705704
{

tests/Chr.Avro.SchemaEvolution.Tests/NewRecordFieldTests.cs

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
using Chr.Avro.Representation;
2+
using Chr.Avro.Serialization;
3+
14
namespace Chr.Avro.SerialisationTests;
25

36
public class NewRecordFieldTests
47
{
8+
private static readonly JsonSchemaReader SchemaReader = new();
9+
private static readonly BinaryDeserializerBuilder DeserializerBuilder = new();
10+
private static readonly BinarySerializerBuilder SerializerBuilder = new();
11+
512
[Fact]
613
public void SchemaEvolution_NewFieldWithDefaultValue()
714
{
@@ -54,7 +61,7 @@ public void SchemaEvolution_NewFieldWithDefaultValue()
5461
}
5562

5663
[Fact]
57-
public void SchenaEvolution_NewFieldWithDefaultNullValue()
64+
public void SchemaEvolution_NewFieldWithDefaultNullValue()
5865
{
5966
var schema = """
6067
{
@@ -99,11 +106,57 @@ public void SchenaEvolution_NewFieldWithDefaultNullValue()
99106
deserialized = serialiser13.Deserialise(bytes);
100107
Assert.Equal(new Player("Alice", 25), deserialized);
101108

102-
// We should also be able to deeserialize to v3 DTO
109+
// We should also be able to deserialize to v3 DTO
103110
deserialized3 = serialiser33.Deserialise(bytes);
104111
Assert.Equal(new Player3("Alice", 25), deserialized3);
105112
}
106113

114+
[Fact]
115+
public void SchemaEvolution_NewFieldWithoutDefaultValueCannotBeSerialized()
116+
{
117+
var schema = """
118+
{
119+
"name": "MyRecord",
120+
"type": "record",
121+
"fields": [
122+
{"name":"name", "type":"string"},
123+
{"name":"age", "type":"int"},
124+
{"name":"score", "type":"double"}
125+
]
126+
}
127+
""";
128+
129+
// New schema and DTO without default value -> Fail
130+
var avroSchema = SchemaReader.Read(schema);
131+
132+
// Player is missing "score", and no default value exists in the schema
133+
var exception = Assert.Throws<UnsupportedTypeException>(() => SerializerBuilder.BuildDelegate<Player>(avroSchema));
134+
var typeName = typeof(Player).FullName;
135+
Assert.Equal($"{typeName} does not have a field or property that matches the score field on MyRecord.", exception.Message);
136+
}
137+
138+
[Fact]
139+
public void InvalidFieldType()
140+
{
141+
var schema = """
142+
{
143+
"name": "MyRecord",
144+
"type": "record",
145+
"fields": [
146+
{"name":"name", "type":"string"},
147+
{"name":"age", "type":"string"}
148+
]
149+
}
150+
""";
151+
152+
var avroSchema = SchemaReader.Read(schema);
153+
154+
// Player field 'age' has incorrect type
155+
var exception = Assert.Throws<UnsupportedTypeException>(() => SerializerBuilder.BuildDelegate<Player>(avroSchema));
156+
var typeName = typeof(Player).FullName;
157+
Assert.Equal($"The Age member on {typeName} could not be mapped to the age field on MyRecord.", exception.Message);
158+
}
159+
107160
public record Player(string Name, int Age);
108161

109162
public record Player2(string Name, int Age, double Score = 3.14);
@@ -123,9 +176,9 @@ public PlayerClass(string name, int age)
123176
public int Age { get; }
124177
}
125178

126-
public class PlayerClass2
179+
public class PlayerWithScore
127180
{
128-
public PlayerClass2(string name, int age, double score = 0)
181+
public PlayerWithScore(string name, int age, double score = 0)
129182
{
130183
Name = name;
131184
Age = age;

0 commit comments

Comments
 (0)