Skip to content

Implement resource serialization #50229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 26, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion eng/Packages.Data.props
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,6 @@
<PropertyGroup>
<TestProxyVersion>1.0.0-dev.20250501.1</TestProxyVersion>
<UnbrandedGeneratorVersion>1.0.0-alpha.20250523.1</UnbrandedGeneratorVersion>
<AzureGeneratorVersion>1.0.0-alpha.20250515.2</AzureGeneratorVersion>
<AzureGeneratorVersion>1.0.0-alpha.20250523.1</AzureGeneratorVersion>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ private static void BuildResourceCore(List<ResourceClientProvider> resources, Li
protected override TypeProvider[] BuildTypeProviders()
{
var (resources, collections) = BuildResources();
return [.. base.BuildTypeProviders().Where(t => t is not InheritableSystemObjectModelProvider), ArmOperation, GenericArmOperation, .. resources, .. collections, .. resources.Select(r => r.Source)];
return [
.. base.BuildTypeProviders().Where(t => t is not InheritableSystemObjectModelProvider),
ArmOperation,
GenericArmOperation,
.. resources,
.. collections,
.. resources.Select(r => r.Source),
.. resources.SelectMany(r => r.SerializationProviders)];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ protected override PropertyProvider[] BuildProperties()
return [hasDataProperty, dataProperty];
}

protected override TypeProvider[] BuildSerializationProviders() => [new ResourceSerializationProvider(this)];

protected override ConstructorProvider[] BuildConstructors()
=> [ConstructorProviderHelper.BuildMockingConstructor(this), BuildResourceDataConstructor(), BuildResourceIdentifierConstructor()];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public ResourceCollectionClientProvider(InputClient inputClient, ResourceMetadat
}
}

protected override TypeProvider[] BuildSerializationProviders() => [];

protected override string BuildName() => $"{SpecName}Collection";

protected override CSharpType[] BuildImplements() =>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Microsoft.TypeSpec.Generator.ClientModel.Snippets;
using Microsoft.TypeSpec.Generator.Expressions;
using Microsoft.TypeSpec.Generator.Primitives;
using Microsoft.TypeSpec.Generator.Providers;
using System;
using System.ClientModel.Primitives;
using System.IO;
using System.Text.Json;
using static Microsoft.TypeSpec.Generator.Snippets.Snippet;

namespace Azure.Generator.Management.Providers
{
internal class ResourceSerializationProvider : TypeProvider
{
private readonly FieldProvider _dataField;
private readonly CSharpType _resourceDataType;
private readonly ResourceClientProvider _resoruce;
private readonly CSharpType _jsonModelInterfaceType;
public ResourceSerializationProvider(ResourceClientProvider resource)
{
_resoruce = resource;
_resourceDataType = resource.ResourceData.Type;
_jsonModelInterfaceType = new CSharpType(typeof(IJsonModel<>), _resourceDataType);
_dataField = new FieldProvider(FieldModifiers.Private | FieldModifiers.Static, _jsonModelInterfaceType, "s_dataDeserializationInstance", this);
}

protected override string BuildName() => _resoruce.Name;

protected override string BuildRelativeFilePath()
=> Path.Combine("src", "Generated", $"{Name}.Serialization.cs");

protected override TypeSignatureModifiers BuildDeclarationModifiers()
=> TypeSignatureModifiers.Public | TypeSignatureModifiers.Partial;

protected override CSharpType[] BuildImplements() => [_jsonModelInterfaceType];

protected override FieldProvider[] BuildFields() => [_dataField];

protected override PropertyProvider[] BuildProperties() =>
[
new PropertyProvider(null, MethodSignatureModifiers.Private | MethodSignatureModifiers.Static, _jsonModelInterfaceType, "DataDeserializationInstance", new ExpressionPropertyBody(new AssignmentExpression(_dataField, New.Instance(_resourceDataType), true)), this)
];

protected override MethodProvider[] BuildMethods()
{
var options = new ParameterProvider("options", $"The client options for reading and writing models.", typeof(ModelReaderWriterOptions));
var iModelTInterface = new CSharpType(typeof(IPersistableModel<>), _resourceDataType);
var data = new ParameterProvider("data", $"The binary data to be processed.", typeof(BinaryData));

// void IJsonModel<T>.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options)
var writer = new ParameterProvider("writer", $"The writer to serialize the model to.", typeof(Utf8JsonWriter));
var jsonModelWriteMethod = new MethodProvider(
new MethodSignature(nameof(IJsonModel<object>.Write), null, MethodSignatureModifiers.None, null, null, [writer, options], ExplicitInterface: _jsonModelInterfaceType),
// => ((IJsonModel<T>)Data).Write(writer, options);
new MemberExpression(null, "Data").CastTo(_jsonModelInterfaceType).Invoke(nameof(IJsonModel<object>.Write), writer, options),
this);

// T IJsonModel<T>.Create(ref Utf8JsonReader reader, ModelReaderWriterOptions options)
var reader = new ParameterProvider("reader", $"The reader for deserializing the model.", typeof(Utf8JsonReader), isRef: true);
var jsonModelCreatemethod = new MethodProvider(
new MethodSignature(nameof(IJsonModel<object>.Create), null, MethodSignatureModifiers.None, _resourceDataType, null, [reader, options], ExplicitInterface: _jsonModelInterfaceType),
// => DataDeserializationInstance.Create(reader, options);
new MemberExpression(null, "DataDeserializationInstance").Invoke(nameof(IJsonModel<object>.Create), reader, options),
this);

// BinaryData IPersistableModel<T>.Write(ModelReaderWriterOptions options)
var persistableWriteMethod = new MethodProvider(
new MethodSignature(nameof(IPersistableModel<object>.Write), null, MethodSignatureModifiers.None, typeof(BinaryData), null, [options], ExplicitInterface: iModelTInterface),
// => ModelReaderWriter.Write<ResourceData>(Data, options);
Static(typeof(ModelReaderWriter)).Invoke("Write", [new MemberExpression(null, "Data"), options, ModelReaderWriterContextSnippets.Default], [_resourceDataType], false),
this);

// T IPersistableModel<T>.Create(BinaryData data, ModelReaderWriterOptions options)
var persistableCreateMethod = new MethodProvider(
new MethodSignature(nameof(IPersistableModel<object>.Create), null, MethodSignatureModifiers.None, _resourceDataType, null, [data, options], ExplicitInterface: iModelTInterface),
// => ModelReaderWriter.Read<ResourceData>(new BinaryData(reader.ValueSequence));
Static(typeof(ModelReaderWriter)).Invoke("Read", [data, options, ModelReaderWriterContextSnippets.Default], [_resourceDataType], false),
this);

// ModelReaderWriterFormat IPersistableModel<T>.GetFormatFromOptions(ModelReaderWriterOptions options)
var persistableGetFormatMethod = new MethodProvider(
new MethodSignature(nameof(IPersistableModel<object>.GetFormatFromOptions), null, MethodSignatureModifiers.None, typeof(string), null, [options], ExplicitInterface: iModelTInterface),
// => DataDeserializationInstance.GetFormatFromOptions(options);
new MemberExpression(null, "DataDeserializationInstance").Invoke(nameof(IPersistableModel<object>.GetFormatFromOptions), options),
this);

return [jsonModelWriteMethod, jsonModelCreatemethod, persistableWriteMethod, persistableCreateMethod, persistableGetFormatMethod];
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
<Nullable>enable</Nullable>
<ImportDefaultReferences>false</ImportDefaultReferences>
<IsShippingLibrary>false</IsShippingLibrary>
<NoWarn>$(NoWarn);CS8002</NoWarn>
<NoWarn>
$(NoWarn);
CS8002;
SCM0005; <!-- Resources currently do not have a parameterless ctor so builders cannot be created for them -->
</NoWarn>
</PropertyGroup>
<!--
Add any shared properties you want for the projects under this package directory that need to be set before the auto imported Directory.Build.props
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading