Skip to content

Commit ff1f572

Browse files
Fix issue with null options and add constructor that omits groupName. (Azure#27687)
* Fix issue with null options and add constructor that omits groupName. * Fix tests and correct CompareBodies default value * CompareBodies patch
1 parent 0d020af commit ff1f572

File tree

17 files changed

+547
-7
lines changed

17 files changed

+547
-7
lines changed

sdk/core/Azure.Core.TestFramework/src/RecordedTestBase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,9 @@ public string ReplacementHost
116116

117117
/// <summary>
118118
/// Whether or not to compare bodies from the request and the recorded request during playback.
119+
/// The default value is <value>true</value>.
119120
/// </summary>
120-
public bool CompareBodies { get; set; }
121+
public bool CompareBodies { get; set; } = true;
121122

122123
/// <summary>
123124
/// Request headers whose values can change between recording and playback without causing request matching

sdk/formrecognizer/Azure.AI.FormRecognizer/tests/Infrastructure/FormRecognizerLiveTestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ public FormRecognizerLiveTestBase(bool isAsync, FormRecognizerClientOptions.Serv
3030
JsonPathSanitizers.Add("$..accessToken");
3131
JsonPathSanitizers.Add("$..containerUrl");
3232
SanitizedHeaders.Add(Constants.AuthorizationHeader);
33+
// temporary until https://github.com/Azure/azure-sdk-for-net/issues/27688 is addressed
34+
CompareBodies = false;
3335
}
3436

3537
/// <summary>

sdk/keyvault/Azure.Security.KeyVault.Certificates/tests/CertificatesTestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public CertificatesTestBase(bool isAsync, CertificateClientOptions.ServiceVersio
4242
: base(isAsync, mode)
4343
{
4444
_serviceVersion = serviceVersion;
45+
// temporary until https://github.com/Azure/azure-sdk-for-net/issues/27688 is addressed
46+
CompareBodies = false;
4547
}
4648

4749
internal CertificateClient GetClient()

sdk/keyvault/Azure.Security.KeyVault.Keys/tests/KeysTestBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ protected KeysTestBase(bool isAsync, KeyClientOptions.ServiceVersion serviceVers
3939
: base(isAsync, mode /* RecordedTestMode.Record */)
4040
{
4141
_serviceVersion = serviceVersion;
42+
// temporary until https://github.com/Azure/azure-sdk-for-net/issues/27688 is addressed
43+
CompareBodies = false;
4244
}
4345

4446
[SetUp]

sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/api/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro.netstandard2.0.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ public override void GetObjectData(System.Runtime.Serialization.SerializationInf
1212
}
1313
public partial class SchemaRegistryAvroSerializer
1414
{
15-
public SchemaRegistryAvroSerializer(Azure.Data.SchemaRegistry.SchemaRegistryClient client, string groupName, Microsoft.Azure.Data.SchemaRegistry.ApacheAvro.SchemaRegistryAvroSerializerOptions options = null) { }
15+
public SchemaRegistryAvroSerializer(Azure.Data.SchemaRegistry.SchemaRegistryClient client) { }
16+
public SchemaRegistryAvroSerializer(Azure.Data.SchemaRegistry.SchemaRegistryClient client, string groupName) { }
17+
public SchemaRegistryAvroSerializer(Azure.Data.SchemaRegistry.SchemaRegistryClient client, string groupName, Microsoft.Azure.Data.SchemaRegistry.ApacheAvro.SchemaRegistryAvroSerializerOptions options) { }
1618
public object Deserialize(Azure.BinaryContent content, System.Type dataType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
1719
public System.Threading.Tasks.ValueTask<object> DeserializeAsync(Azure.BinaryContent content, System.Type dataType, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
1820
public System.Threading.Tasks.ValueTask<TData> DeserializeAsync<TData>(Azure.BinaryContent content, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }

sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/src/SchemaRegistryAvroSerializer.cs

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,42 @@ public class SchemaRegistryAvroSerializer
2929
private const int CacheCapacity = 128;
3030

3131
/// <summary>
32-
/// Initializes new instance of <see cref="SchemaRegistryAvroSerializer"/>.
32+
/// Initializes a new instance of the <see cref="SchemaRegistryAvroSerializer"/>. This constructor can only be used to create an
33+
/// instance which will be used for deserialization. In order to serialize (or both serialize and deserialize) you will need to use
34+
/// one of the constructors that have a <code>groupName</code> parameter.
3335
/// </summary>
34-
public SchemaRegistryAvroSerializer(SchemaRegistryClient client, string groupName, SchemaRegistryAvroSerializerOptions options = null)
36+
/// <param name="client">The <see cref="SchemaRegistryClient"/> instance to use for looking up schemas.</param>
37+
/// <exception cref="ArgumentNullException"></exception>
38+
public SchemaRegistryAvroSerializer(SchemaRegistryClient client)
39+
: this(client, null, new SchemaRegistryAvroSerializerOptions())
40+
{
41+
}
42+
43+
/// <summary>
44+
/// Initializes a new instance of the <see cref="SchemaRegistryAvroSerializer"/>. This constructor can be used to create an instance
45+
/// that will work for both serialization and deserialization.
46+
/// </summary>
47+
/// <param name="client">The <see cref="SchemaRegistryClient"/> instance to use for looking up schemas.</param>
48+
/// <param name="groupName">The Schema Registry group name that contains the schemas that will be used to serialize.</param>
49+
/// <exception cref="ArgumentNullException"></exception>
50+
public SchemaRegistryAvroSerializer(SchemaRegistryClient client, string groupName)
51+
: this(client, groupName, new SchemaRegistryAvroSerializerOptions())
52+
{
53+
}
54+
55+
/// <summary>
56+
/// Initializes a new instance of the <see cref="SchemaRegistryAvroSerializer"/>. This constructor can be used to create an instance
57+
/// that will work for both serialization and deserialization.
58+
/// </summary>
59+
/// <param name="client">The <see cref="SchemaRegistryClient"/> instance to use for looking up schemas.</param>
60+
/// <param name="groupName">The Schema Registry group name that contains the schemas that will be used to serialize.</param>
61+
/// <param name="options">The set of options to customize the <see cref="SchemaRegistryAvroSerializer"/>.</param>
62+
/// <exception cref="ArgumentNullException"></exception>
63+
public SchemaRegistryAvroSerializer(SchemaRegistryClient client, string groupName, SchemaRegistryAvroSerializerOptions options)
3564
{
3665
_client = client ?? throw new ArgumentNullException(nameof(client));
37-
_groupName = groupName ?? throw new ArgumentNullException(nameof(groupName));
38-
_options = options;
66+
_groupName = groupName;
67+
_options = options?.Clone() ?? new SchemaRegistryAvroSerializerOptions();
3968
}
4069

4170
private readonly LruCache<string, Schema> _idToSchemaMap = new(CacheCapacity);
@@ -118,12 +147,20 @@ internal async ValueTask<BinaryContent> SerializeInternalAsync(
118147
bool async,
119148
CancellationToken cancellationToken)
120149
{
150+
if (_groupName == null)
151+
{
152+
throw new InvalidOperationException(
153+
"A group name must be specified for in the SchemaRegistryAvroSerializer constructor if you will be attempting to serialize. " +
154+
"The group name can be omitted if only deserializing.");
155+
}
156+
121157
messageType ??= typeof(BinaryContent);
122158
if (messageType.GetConstructor(Type.EmptyTypes) == null)
123159
{
124160
throw new InvalidOperationException(
125161
$"The type {messageType} must have a public parameterless constructor in order to use it as the 'MessageContent' type to serialize to.");
126162
}
163+
127164
var message = (BinaryContent)Activator.CreateInstance(messageType);
128165

129166
(string schemaId, BinaryData bd) = async

sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/src/SchemaRegistryAvroSerializerOptions.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace Microsoft.Azure.Data.SchemaRegistry.ApacheAvro
55
{
66
/// <summary>
7-
/// Options for <see cref="SchemaRegistryAvroSerializer"/>.
7+
/// The set of options for the <see cref="SchemaRegistryAvroSerializer"/>.
88
/// </summary>
99
public class SchemaRegistryAvroSerializerOptions
1010
{
@@ -15,5 +15,11 @@ public class SchemaRegistryAvroSerializerOptions
1515
/// The default is false.
1616
/// </summary>
1717
public bool AutoRegisterSchemas { get; set; }
18+
19+
internal SchemaRegistryAvroSerializerOptions Clone()
20+
=> new()
21+
{
22+
AutoRegisterSchemas = AutoRegisterSchemas
23+
};
1824
}
1925
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// ------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// Generated by ApacheAvroTestTool, version 1.10.0.0
4+
// Changes to this file may cause incorrect behavior and will be lost if code
5+
// is regenerated
6+
// </auto-generated>
7+
// ------------------------------------------------------------------------------
8+
namespace TestSchema
9+
{
10+
using System;
11+
using System.Collections.Generic;
12+
using System.Text;
13+
using Avro;
14+
using Avro.Specific;
15+
16+
public partial class Employee_Unregistered : ISpecificRecord
17+
{
18+
public static Schema _SCHEMA = Avro.Schema.Parse("{\"type\":\"record\",\"name\":\"Employee_Unregistered\",\"aliases\": [\"EmployeeV2\"],\"namespace\":\"TestSchema\",\"fields\":[{\"name\":\"Na" +
19+
"me\",\"type\":\"string\"},{\"name\":\"Age\",\"type\":\"int\"}]}");
20+
private string _Name;
21+
private int _Age;
22+
public virtual Schema Schema
23+
{
24+
get
25+
{
26+
return Employee_Unregistered._SCHEMA;
27+
}
28+
}
29+
public string Name
30+
{
31+
get
32+
{
33+
return this._Name;
34+
}
35+
set
36+
{
37+
this._Name = value;
38+
}
39+
}
40+
public int Age
41+
{
42+
get
43+
{
44+
return this._Age;
45+
}
46+
set
47+
{
48+
this._Age = value;
49+
}
50+
}
51+
public virtual object Get(int fieldPos)
52+
{
53+
switch (fieldPos)
54+
{
55+
case 0: return this.Name;
56+
case 1: return this.Age;
57+
default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Get()");
58+
};
59+
}
60+
public virtual void Put(int fieldPos, object fieldValue)
61+
{
62+
switch (fieldPos)
63+
{
64+
case 0: this.Name = (System.String)fieldValue; break;
65+
case 1: this.Age = (System.Int32)fieldValue; break;
66+
default: throw new AvroRuntimeException("Bad index " + fieldPos + " in Put()");
67+
};
68+
}
69+
}
70+
}

sdk/schemaregistry/Microsoft.Azure.Data.SchemaRegistry.ApacheAvro/tests/SchemaRegistryAvroObjectSerializerLiveTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,25 @@ public async Task CanSerializeAndDeserialize()
4141
Assert.AreEqual(42, deserializedEmployee.Age);
4242
}
4343

44+
[RecordedTest]
45+
public async Task CanSerializeAndDeserializeWithSeparateInstances()
46+
{
47+
var client = CreateClient();
48+
var groupName = TestEnvironment.SchemaRegistryGroup;
49+
var employee = new Employee { Age = 42, Name = "Caketown" };
50+
51+
var serializer = new SchemaRegistryAvroSerializer(client, groupName, new SchemaRegistryAvroSerializerOptions { AutoRegisterSchemas = true });
52+
BinaryContent content = await serializer.SerializeAsync<BinaryContent, Employee>(employee);
53+
54+
// validate that we can use the constructor that only takes the client when deserializing since groupName is not necessary
55+
serializer = new SchemaRegistryAvroSerializer(client);
56+
Employee deserializedEmployee = await serializer.DeserializeAsync<Employee>(content);
57+
58+
Assert.IsNotNull(deserializedEmployee);
59+
Assert.AreEqual("Caketown", deserializedEmployee.Name);
60+
Assert.AreEqual(42, deserializedEmployee.Age);
61+
}
62+
4463
[RecordedTest]
4564
public async Task CanSerializeAndDeserializeWithCompatibleSchema()
4665
{
@@ -301,6 +320,27 @@ public void SerializingToMessageTypeWithoutConstructorThrows()
301320
async () => await serializer.SerializeAsync(new Employee { Age = 42, Name = "Caketown" }, messageType: typeof(BinaryContentWithNoConstructor)));
302321
}
303322

323+
[RecordedTest]
324+
public void SerializingWithoutAutoRegisterThrowsForNonExistentSchema()
325+
{
326+
var client = CreateClient();
327+
var groupName = TestEnvironment.SchemaRegistryGroup;
328+
329+
var serializer = new SchemaRegistryAvroSerializer(client, groupName);
330+
Assert.ThrowsAsync<RequestFailedException>(
331+
async () => await serializer.SerializeAsync(new Employee_Unregistered { Age = 42, Name = "Caketown"}));
332+
}
333+
334+
[RecordedTest]
335+
public void SerializingWithoutGroupNameThrows()
336+
{
337+
var client = CreateClient();
338+
339+
var serializer = new SchemaRegistryAvroSerializer(client);
340+
Assert.ThrowsAsync<InvalidOperationException>(
341+
async () => await serializer.SerializeAsync(new Employee { Age = 42, Name = "Caketown" }));
342+
}
343+
304344
private class InvalidAvroModel : ISpecificRecord
305345
{
306346
// the schema is invalid because it doesn't contain any fields
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Azure;
7+
using Azure.Core.TestFramework;
8+
using Azure.Data.SchemaRegistry;
9+
using Moq;
10+
using NUnit.Framework;
11+
using TestSchema;
12+
13+
namespace Microsoft.Azure.Data.SchemaRegistry.ApacheAvro.Tests
14+
{
15+
public class SchemaRegistryAvroObjectSerializerTests
16+
{
17+
[Test]
18+
public async Task SerializeWorksWithNoOptions()
19+
{
20+
var mockClient = new Mock<SchemaRegistryClient>();
21+
mockClient
22+
.Setup(
23+
client => client.GetSchemaPropertiesAsync(
24+
"groupName",
25+
Employee._SCHEMA.Fullname,
26+
Employee._SCHEMA.ToString(),
27+
SchemaFormat.Avro,
28+
CancellationToken.None))
29+
.Returns(
30+
Task.FromResult(
31+
Response.FromValue(
32+
SchemaRegistryModelFactory.SchemaProperties(SchemaFormat.Avro, "schemaId"), new MockResponse(200))));
33+
34+
var serializer = new SchemaRegistryAvroSerializer(mockClient.Object, "groupName");
35+
var content = await serializer.SerializeAsync(new Employee {Age = 42, Name = "Caketown"});
36+
Assert.AreEqual("schemaId", content.ContentType.ToString().Split('+')[1]);
37+
38+
// also validate explicitly passing null
39+
serializer = new SchemaRegistryAvroSerializer(mockClient.Object, "groupName", null);
40+
content = await serializer.SerializeAsync(new Employee {Age = 42, Name = "Caketown"});
41+
Assert.AreEqual("schemaId", content.ContentType.ToString().Split('+')[1]);
42+
}
43+
44+
[Test]
45+
public void CloneCopiesAllProperties()
46+
{
47+
var options = new SchemaRegistryAvroSerializerOptions() { AutoRegisterSchemas = true };
48+
Assert.True(options.AutoRegisterSchemas);
49+
50+
var cloned = options.Clone();
51+
Assert.AreEqual(options.AutoRegisterSchemas, cloned.AutoRegisterSchemas);
52+
}
53+
}
54+
}

0 commit comments

Comments
 (0)