Skip to content

Commit 525faf2

Browse files
Add serialization/deserialization hooks for azure types (Azure#47605)
* add support for serialization/deserialization of azure types
1 parent 5de735f commit 525faf2

26 files changed

+1080
-812
lines changed

eng/Packages.Data.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@
259259
</ItemGroup>
260260

261261
<ItemGroup Condition="'$(IsGeneratorLibrary)' == 'true'">
262-
<PackageReference Update="Microsoft.Generator.CSharp.ClientModel" Version="1.0.0-alpha.20241217.2" />
262+
<PackageReference Update="Microsoft.Generator.CSharp.ClientModel" Version="1.0.0-alpha.20241219.2" />
263263
</ItemGroup>
264264

265265
<!--

eng/packages/http-client-csharp/generator/Azure.Generator/src/AzureTypeFactory.cs

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
using Azure.Core;
54
using Azure.Generator.Primitives;
65
using Azure.Generator.Providers;
76
using Azure.Generator.Providers.Abstraction;
87
using Microsoft.Generator.CSharp.ClientModel;
98
using Microsoft.Generator.CSharp.ClientModel.Providers;
10-
using Microsoft.Generator.CSharp.ClientModel.Snippets;
119
using Microsoft.Generator.CSharp.Expressions;
1210
using Microsoft.Generator.CSharp.Input;
1311
using Microsoft.Generator.CSharp.Primitives;
@@ -16,7 +14,6 @@
1614
using System;
1715
using System.ClientModel.Primitives;
1816
using System.Text.Json;
19-
using static Microsoft.Generator.CSharp.Snippets.Snippet;
2017

2118
namespace Azure.Generator
2219
{
@@ -66,7 +63,7 @@ public class AzureTypeFactory : ScmTypeFactory
6663
InputPrimitiveType? primitiveType = inputType;
6764
while (primitiveType != null)
6865
{
69-
if (KnownAzureTypes.PrimitiveTypes.TryGetValue(primitiveType.CrossLanguageDefinitionId, out var knownType))
66+
if (KnownAzureTypes.TryGetPrimitiveType(primitiveType.CrossLanguageDefinitionId, out var knownType))
7067
{
7168
return knownType;
7269
}
@@ -78,40 +75,34 @@ public class AzureTypeFactory : ScmTypeFactory
7875
}
7976

8077
/// <inheritdoc/>
81-
public override ValueExpression GetValueTypeDeserializationExpression(Type valueType, ScopedApi<JsonElement> element, SerializationFormat format)
78+
public override ValueExpression DeserializeJsonValue(Type valueType, ScopedApi<JsonElement> element, SerializationFormat format)
8279
{
83-
var expression = GetValueTypeDeserializationExpressionCore(valueType, element, format);
84-
return expression ?? base.GetValueTypeDeserializationExpression(valueType, element, format);
80+
var expression = DeserializeJsonValueCore(valueType, element, format);
81+
return expression ?? base.DeserializeJsonValue(valueType, element, format);
8582
}
8683

87-
private ValueExpression? GetValueTypeDeserializationExpressionCore(
84+
private ValueExpression? DeserializeJsonValueCore(
8885
Type valueType,
8986
ScopedApi<JsonElement> element,
9087
SerializationFormat format)
9188
{
92-
return valueType switch
93-
{
94-
Type t when t == typeof(ResourceIdentifier) =>
95-
New.Instance(valueType, element.GetString()),
96-
_ => null,
97-
};
89+
return KnownAzureTypes.TryGetJsonDeserializationExpression(valueType, out var deserializationExpression) ?
90+
deserializationExpression(new CSharpType(valueType), element, format) :
91+
null;
9892
}
9993

10094
/// <inheritdoc/>
101-
public override MethodBodyStatement SerializeValueType(CSharpType type, SerializationFormat serializationFormat, ValueExpression value, Type valueType, ScopedApi<Utf8JsonWriter> utf8JsonWriter, ScopedApi<ModelReaderWriterOptions> mrwOptionsParameter)
95+
public override MethodBodyStatement SerializeJsonValue(Type valueType, ValueExpression value, ScopedApi<Utf8JsonWriter> utf8JsonWriter, ScopedApi<ModelReaderWriterOptions> mrwOptionsParameter, SerializationFormat serializationFormat)
10296
{
103-
var statement = SerializeValueTypeCore(type, serializationFormat, value, valueType, utf8JsonWriter, mrwOptionsParameter);
104-
return statement ?? base.SerializeValueType(type, serializationFormat, value, valueType, utf8JsonWriter, mrwOptionsParameter);
97+
var statement = SerializeValueTypeCore(serializationFormat, value, valueType, utf8JsonWriter, mrwOptionsParameter);
98+
return statement ?? base.SerializeJsonValue(valueType, value, utf8JsonWriter, mrwOptionsParameter, serializationFormat);
10599
}
106100

107-
private MethodBodyStatement? SerializeValueTypeCore(CSharpType type, SerializationFormat serializationFormat, ValueExpression value, Type valueType, ScopedApi<Utf8JsonWriter> utf8JsonWriter, ScopedApi<ModelReaderWriterOptions> mrwOptionsParameter)
101+
private MethodBodyStatement? SerializeValueTypeCore(SerializationFormat serializationFormat, ValueExpression value, Type valueType, ScopedApi<Utf8JsonWriter> utf8JsonWriter, ScopedApi<ModelReaderWriterOptions> mrwOptionsParameter)
108102
{
109-
return valueType switch
110-
{
111-
Type t when t == typeof(ResourceIdentifier) =>
112-
utf8JsonWriter.WriteStringValue(value.Property(nameof(ResourceIdentifier.Name))),
113-
_ => null,
114-
};
103+
return KnownAzureTypes.TryGetJsonSerializationExpression(valueType, out var serializationExpression) ?
104+
serializationExpression(value, utf8JsonWriter, mrwOptionsParameter, serializationFormat) :
105+
null;
115106
}
116107
}
117108
}

eng/packages/http-client-csharp/generator/Azure.Generator/src/Primitives/KnownAzureTypes.cs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,47 @@
22
// Licensed under the MIT License.
33

44
using Azure.Core;
5+
using Microsoft.Generator.CSharp.ClientModel.Snippets;
6+
using Microsoft.Generator.CSharp.Expressions;
7+
using Microsoft.Generator.CSharp.Input;
58
using Microsoft.Generator.CSharp.Primitives;
9+
using Microsoft.Generator.CSharp.Snippets;
10+
using Microsoft.Generator.CSharp.Statements;
611
using System;
12+
using System.ClientModel.Primitives;
713
using System.Collections.Generic;
14+
using System.Diagnostics.CodeAnalysis;
815
using System.Net;
16+
using System.Text.Json;
17+
using static Microsoft.Generator.CSharp.Snippets.Snippet;
918

1019
namespace Azure.Generator.Primitives
1120
{
1221
internal static class KnownAzureTypes
1322
{
23+
public delegate MethodBodyStatement SerializationExpression(ValueExpression value, ScopedApi<Utf8JsonWriter> writer, ScopedApi<ModelReaderWriterOptions> options, SerializationFormat format);
24+
public delegate ValueExpression DeserializationExpression(CSharpType valueType, ScopedApi<JsonElement> element, SerializationFormat format);
25+
1426
private const string UuidId = "Azure.Core.uuid";
1527
private const string IPv4AddressId = "Azure.Core.ipV4Address";
1628
private const string IPv6AddressId = "Azure.Core.ipV6Address";
1729
private const string ETagId = "Azure.Core.eTag";
1830
private const string AzureLocationId = "Azure.Core.azureLocation";
1931
private const string ArmIdId = "Azure.Core.armResourceIdentifier";
2032

21-
internal static readonly IReadOnlyDictionary<string, CSharpType> PrimitiveTypes = new Dictionary<string, CSharpType>
33+
private static MethodBodyStatement SerializeTypeWithImplicitOperatorToString(ValueExpression value, ScopedApi<Utf8JsonWriter> writer, ScopedApi<ModelReaderWriterOptions> options, SerializationFormat format)
34+
=> writer.WriteStringValue(value);
35+
36+
private static ValueExpression DeserializeNewInstanceStringLikeType(CSharpType valueType, ScopedApi<JsonElement> element, SerializationFormat format)
37+
=> New.Instance(valueType, element.GetString());
38+
39+
private static MethodBodyStatement SerializeTypeWithToString(ValueExpression value, ScopedApi<Utf8JsonWriter> writer, ScopedApi<ModelReaderWriterOptions> options, SerializationFormat format)
40+
=> writer.WriteStringValue(value.InvokeToString());
41+
42+
private static ValueExpression DeserializeParsableStringLikeType(CSharpType valueType, ScopedApi<JsonElement> element, SerializationFormat format)
43+
=> Static(valueType).Invoke("Parse", element.GetString());
44+
45+
private static readonly IReadOnlyDictionary<string, CSharpType> _idToTypes = new Dictionary<string, CSharpType>
2246
{
2347
[UuidId] = typeof(Guid),
2448
[IPv4AddressId] = typeof(IPAddress),
@@ -27,5 +51,29 @@ internal static class KnownAzureTypes
2751
[AzureLocationId] = typeof(AzureLocation),
2852
[ArmIdId] = typeof(ResourceIdentifier),
2953
};
54+
55+
private static readonly IReadOnlyDictionary<Type, SerializationExpression> _typeToSerializationExpression = new Dictionary<Type, SerializationExpression>
56+
{
57+
[typeof(Guid)] = SerializeTypeWithImplicitOperatorToString,
58+
[typeof(IPAddress)] = SerializeTypeWithToString,
59+
[typeof(ETag)] = SerializeTypeWithToString,
60+
[typeof(AzureLocation)] = SerializeTypeWithImplicitOperatorToString,
61+
[typeof(ResourceIdentifier)] = SerializeTypeWithImplicitOperatorToString,
62+
};
63+
64+
private static readonly IReadOnlyDictionary<Type, DeserializationExpression> _typeToDeserializationExpression = new Dictionary<Type, DeserializationExpression>
65+
{
66+
[typeof(Guid)] = DeserializeNewInstanceStringLikeType,
67+
[typeof(IPAddress)] = DeserializeParsableStringLikeType,
68+
[typeof(ETag)] = DeserializeNewInstanceStringLikeType,
69+
[typeof(AzureLocation)] = DeserializeNewInstanceStringLikeType,
70+
[typeof(ResourceIdentifier)] = DeserializeNewInstanceStringLikeType,
71+
};
72+
73+
public static bool TryGetPrimitiveType(string id, [MaybeNullWhen(false)] out CSharpType type) => _idToTypes.TryGetValue(id, out type);
74+
75+
public static bool TryGetJsonSerializationExpression(Type type, [MaybeNullWhen(false)] out SerializationExpression expression) => _typeToSerializationExpression.TryGetValue(type, out expression);
76+
77+
public static bool TryGetJsonDeserializationExpression(Type type, [MaybeNullWhen(false)] out DeserializationExpression expression) => _typeToDeserializationExpression.TryGetValue(type, out expression);
3078
}
3179
}

eng/packages/http-client-csharp/generator/Azure.Generator/test/AzureTypeFactoryTests.cs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,17 @@
44
using Azure.Core;
55
using Azure.Generator.Tests.Common;
66
using Azure.Generator.Tests.TestHelpers;
7-
using Microsoft.Azure.Test.HttpRecorder;
7+
using Microsoft.Generator.CSharp.Expressions;
8+
using Microsoft.Generator.CSharp.Input;
9+
using Microsoft.Generator.CSharp.Primitives;
810
using Microsoft.Generator.CSharp.Providers;
11+
using Microsoft.Generator.CSharp.Snippets;
912
using NUnit.Framework;
1013
using System;
14+
using System.Buffers;
15+
using System.ClientModel.Primitives;
1116
using System.Net;
17+
using System.Text.Json;
1218

1319
namespace Azure.Generator.Tests
1420
{
@@ -20,6 +26,38 @@ public void SetUp()
2026
MockHelpers.LoadMockPlugin();
2127
}
2228

29+
[TestCase(typeof(Guid), ExpectedResult = "writer.WriteStringValue(value);\n")]
30+
[TestCase(typeof(IPAddress), ExpectedResult ="writer.WriteStringValue(value.ToString());\n")]
31+
[TestCase(typeof(ETag), ExpectedResult = "writer.WriteStringValue(value.ToString());\n")]
32+
[TestCase(typeof(AzureLocation), ExpectedResult = "writer.WriteStringValue(value);\n")]
33+
[TestCase(typeof(ResourceIdentifier), ExpectedResult = "writer.WriteStringValue(value);\n")]
34+
public string ValidateSerializationStatement(Type type)
35+
{
36+
var value = new ParameterProvider("value", $"", type).AsExpression().As(type);
37+
var writer = new ParameterProvider("writer", $"", typeof(Utf8JsonWriter)).AsExpression().As<Utf8JsonWriter>();
38+
var options = new ParameterProvider("options", $"", typeof(ModelReaderWriterOptions)).AsExpression().As<ModelReaderWriterOptions>();
39+
40+
var statement = AzureClientPlugin.Instance.TypeFactory.SerializeJsonValue(type, value, writer, options, SerializationFormat.Default);
41+
Assert.IsNotNull(statement);
42+
43+
return statement.ToDisplayString();
44+
}
45+
46+
[TestCase(typeof(Guid), ExpectedResult = "new global::System.Guid(element.GetString())")]
47+
[TestCase(typeof(IPAddress), ExpectedResult = "global::System.Net.IPAddress.Parse(element.GetString())")]
48+
[TestCase(typeof(ETag), ExpectedResult = "new global::Azure.ETag(element.GetString())")]
49+
[TestCase(typeof(AzureLocation), ExpectedResult = "new global::Azure.Core.AzureLocation(element.GetString())")]
50+
[TestCase(typeof(ResourceIdentifier), ExpectedResult = "new global::Azure.Core.ResourceIdentifier(element.GetString())")]
51+
public string ValidateDeserializationExpression(Type type)
52+
{
53+
var element = new ParameterProvider("element", $"", typeof(JsonElement)).AsExpression().As<JsonElement>();
54+
55+
var expression = AzureClientPlugin.Instance.TypeFactory.DeserializeJsonValue(type, element, SerializationFormat.Default);
56+
Assert.IsNotNull(expression);
57+
58+
return expression.ToDisplayString();
59+
}
60+
2361
[Test]
2462
public void Uuid()
2563
{

eng/packages/http-client-csharp/generator/TestProjects/Local/Mgmt-TypeSpec/src/Generated/Foos.RestClient.cs

Lines changed: 6 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)