-
Notifications
You must be signed in to change notification settings - Fork 207
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: support additional property serialization
- Loading branch information
1 parent
9a28a9c
commit 8bbc09f
Showing
151 changed files
with
13,366 additions
and
150 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
405 changes: 349 additions & 56 deletions
405
...or/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
164 changes: 164 additions & 0 deletions
164
...rp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/AdditionalPropertiesTest.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using Microsoft.Generator.CSharp.ClientModel.Providers; | ||
using Microsoft.Generator.CSharp.Input; | ||
using Microsoft.Generator.CSharp.Tests.Common; | ||
using NUnit.Framework; | ||
|
||
namespace Microsoft.Generator.CSharp.ClientModel.Tests.Providers.MrwSerializationTypeDefinitions | ||
{ | ||
internal class AdditionalPropertiesTest | ||
{ | ||
[TestCaseSource(nameof(TestBuildDeserializationMethodTestCases))] | ||
public void TestBuildDeserializationMethod( | ||
InputType additionalPropsValueType, | ||
string[] expectedValueTypeNames, | ||
string[] expectedValueKindChecks) | ||
{ | ||
var inputModel = InputFactory.Model("cat", | ||
properties: | ||
[ | ||
InputFactory.Property("color", InputPrimitiveType.String, isRequired: true), | ||
], | ||
additionalProperties: additionalPropsValueType); | ||
MockHelpers.LoadMockPlugin(inputModels: () => [inputModel]); | ||
|
||
var model = ClientModelPlugin.Instance.TypeFactory.CreateModel(inputModel); | ||
var serializations = model!.SerializationProviders.FirstOrDefault() as MrwSerializationTypeDefinition; | ||
Assert.IsNotNull(serializations); | ||
var deserializationMethod = serializations!.BuildDeserializationMethod(); | ||
Assert.IsNotNull(deserializationMethod); | ||
|
||
var signature = deserializationMethod?.Signature; | ||
|
||
Assert.IsNotNull(signature); | ||
Assert.AreEqual(model.Type, signature?.ReturnType); | ||
|
||
var methodBody = deserializationMethod?.BodyStatements; | ||
Assert.IsNotNull(methodBody); | ||
|
||
var methodBodyString = methodBody!.ToDisplayString(); | ||
// validate the additional properties variable declarations | ||
for (var i = 0; i < expectedValueTypeNames.Length; i++) | ||
{ | ||
var expectedVariableName = i == 0 ? "additionalProperties" : $"additional{expectedValueTypeNames[i].ToCleanName()}Properties"; | ||
var expectedDeclaration = $"global::System.Collections.Generic.IDictionary<string, {expectedValueTypeNames[i].ToVariableName()}> {expectedVariableName}"; | ||
Assert.IsTrue(methodBodyString.Contains(expectedDeclaration, StringComparison.InvariantCultureIgnoreCase)); | ||
} | ||
|
||
// validate the additional properties value kind check statements | ||
foreach (var expectedValueKindCheck in expectedValueKindChecks) | ||
{ | ||
Assert.IsTrue(methodBodyString.Contains(expectedValueKindCheck)); | ||
} | ||
|
||
// validate return statement | ||
if (expectedValueTypeNames.Length > 1) | ||
{ | ||
// skip the first value type name as it is already included in the return statement | ||
var additionalPropertiesVariables = "additionalProperties, " + string.Join(", ", expectedValueTypeNames.Skip(1).Select(v => $"additional{v.ToCleanName()}Properties,")); | ||
var expectedReturnStatement = $"return new global::sample.namespace.Models.Cat(color, {additionalPropertiesVariables} additionalBinaryDataProperties);"; | ||
Assert.IsTrue(methodBodyString.Contains(expectedReturnStatement)); | ||
} | ||
else | ||
{ | ||
Assert.IsTrue(methodBodyString.Contains("return new global::sample.namespace.Models.Cat(color, additionalProperties, additionalBinaryDataProperties);")); | ||
} | ||
} | ||
|
||
[TestCaseSource(nameof(TestBuildJsonModelWriteCoreTestCases))] | ||
public void TestBuildJsonModelWriteCore( | ||
InputType additionalPropsValueType) | ||
{ | ||
var inputModel = InputFactory.Model("cat", | ||
properties: | ||
[ | ||
InputFactory.Property("color", InputPrimitiveType.String, isRequired: true), | ||
], | ||
additionalProperties: additionalPropsValueType); | ||
MockHelpers.LoadMockPlugin(inputModels: () => [inputModel]); | ||
|
||
var model = ClientModelPlugin.Instance.TypeFactory.CreateModel(inputModel); | ||
var serializations = model!.SerializationProviders.FirstOrDefault() as MrwSerializationTypeDefinition; | ||
Assert.IsNotNull(serializations); | ||
|
||
var writeCoreMethod = serializations!.BuildJsonModelWriteCoreMethod(); | ||
|
||
Assert.IsNotNull(writeCoreMethod); | ||
var methodBody = writeCoreMethod?.BodyStatements; | ||
Assert.IsNotNull(methodBody); | ||
|
||
var methodBodyString = methodBody!.ToDisplayString(); | ||
var additionalPropertiesProps = model.Properties.Where(p => p.BackingField != null && p.Name.StartsWith("Additional")).ToList(); | ||
|
||
// validate each additional property is serialized | ||
foreach (var additionalProperty in additionalPropertiesProps) | ||
{ | ||
var expectedSerializationStatement = $"foreach (var item in {additionalProperty.Name})"; | ||
Assert.IsTrue(methodBodyString.Contains(expectedSerializationStatement)); | ||
} | ||
} | ||
|
||
public static IEnumerable<TestCaseData> TestBuildDeserializationMethodTestCases | ||
{ | ||
get | ||
{ | ||
// string additional properties | ||
yield return new TestCaseData( | ||
InputPrimitiveType.String, | ||
new string[] { "string" }, | ||
new string[] { "case global::System.Text.Json.JsonValueKind.String:", "additionalProperties.Add(prop.Name, prop.Value.GetString());" }); | ||
// bool additional properties | ||
yield return new TestCaseData( | ||
InputPrimitiveType.Boolean, | ||
new string[] { "bool" }, | ||
new string[] | ||
{ | ||
"case (global::System.Text.Json.JsonValueKind.True || global::System.Text.Json.JsonValueKind.False):", | ||
"additionalProperties.Add(prop.Name, prop.Value.GetBoolean());" | ||
}); | ||
// float additional properties | ||
yield return new TestCaseData( | ||
InputPrimitiveType.Float32, | ||
new string[] { "float" }, | ||
new string[] | ||
{ | ||
"case global::System.Text.Json.JsonValueKind.Number:", | ||
"if (prop.Value.TryGetSingle(out float single))", | ||
"additionalProperties.Add(prop.Name, single);" | ||
}); | ||
// union additional properties | ||
yield return new TestCaseData( | ||
new InputUnionType("union", [InputPrimitiveType.String, InputPrimitiveType.Float64]), | ||
new string[] { "string", "double" }, | ||
new string[] | ||
{ | ||
"case global::System.Text.Json.JsonValueKind.String:", | ||
"additionalProperties.Add(prop.Name, prop.Value.GetString());", | ||
"case global::System.Text.Json.JsonValueKind.Number:", | ||
"if (prop.Value.TryGetDouble(out double @double))", | ||
"additionalDoubleProperties.Add(prop.Name, @double);" | ||
}); | ||
} | ||
} | ||
|
||
public static IEnumerable<TestCaseData> TestBuildJsonModelWriteCoreTestCases | ||
{ | ||
get | ||
{ | ||
// string additional properties | ||
yield return new TestCaseData(InputPrimitiveType.String); | ||
// bool additional properties | ||
yield return new TestCaseData(InputPrimitiveType.Boolean); | ||
// float additional properties | ||
yield return new TestCaseData(InputPrimitiveType.Float32); | ||
// union additional properties | ||
yield return new TestCaseData(new InputUnionType("union", [InputPrimitiveType.String, InputPrimitiveType.Float64])); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
52 changes: 52 additions & 0 deletions
52
...wSerializationTypeDefinitions/TestData/JsonModelCoreTests/MultipleAdditionalProperties.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// <auto-generated/> | ||
|
||
#nullable disable | ||
|
||
using System; | ||
using System.ClientModel.Primitives; | ||
using System.Text.Json; | ||
|
||
namespace sample.namespace.Models | ||
{ | ||
/// <summary></summary> | ||
public partial class TestModel : global::System.ClientModel.Primitives.IJsonModel<global::sample.namespace.Models.TestModel> | ||
{ | ||
/// <param name="writer"> The JSON writer. </param> | ||
/// <param name="options"> The client options for reading and writing models. </param> | ||
protected virtual void JsonModelWriteCore(global::System.Text.Json.Utf8JsonWriter writer, global::System.ClientModel.Primitives.ModelReaderWriterOptions options) | ||
{ | ||
string format = (options.Format == "W") ? ((global::System.ClientModel.Primitives.IPersistableModel<global::sample.namespace.Models.TestModel>)this).GetFormatFromOptions(options) : options.Format; | ||
if ((format != "J")) | ||
{ | ||
throw new global::System.FormatException($"The model {nameof(global::sample.namespace.Models.TestModel)} does not support writing '{format}' format."); | ||
} | ||
writer.WritePropertyName("color"u8); | ||
writer.WriteStringValue(Color); | ||
foreach (var item in AdditionalProperties) | ||
{ | ||
writer.WritePropertyName(item.Key); | ||
writer.WriteStringValue(item.Value); | ||
} | ||
foreach (var item in AdditionalDoubleProperties) | ||
{ | ||
writer.WritePropertyName(item.Key); | ||
writer.WriteNumberValue(item.Value); | ||
} | ||
if (((options.Format != "W") && (_additionalBinaryDataProperties != null))) | ||
{ | ||
foreach (var item in _additionalBinaryDataProperties) | ||
{ | ||
writer.WritePropertyName(item.Key); | ||
#if NET6_0_OR_GREATER | ||
writer.WriteRawValue(item.Value); | ||
#else | ||
using (global::System.Text.Json.JsonDocument document = global::System.Text.Json.JsonDocument.Parse(item.Value)) | ||
{ | ||
global::System.Text.Json.JsonSerializer.Serialize(writer, document.RootElement); | ||
} | ||
#endif | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.