Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
4e26020
Move MutableJsonDocument and related files to internal shared source
annelo-msft Aug 9, 2023
4c0da7f
Add simple model tests
annelo-msft Aug 9, 2023
7ebe02e
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 11, 2023
624aebe
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 11, 2023
5cb9d21
fix csproj
annelo-msft Aug 11, 2023
332695c
Move Patch model to IModelJsonSerializable
annelo-msft Aug 11, 2023
44148b4
Illustrate DateTime property set
annelo-msft Aug 11, 2023
2f10dec
Starting on nested models
annelo-msft Aug 11, 2023
189b860
Use analyzer to prevent unknown usage
annelo-msft Aug 11, 2023
27abf0a
Suppress AZC0020 for Patch models
annelo-msft Aug 11, 2023
50bd03b
Updates and tests
annelo-msft Aug 11, 2023
40ba9d2
one approach; and failing test
annelo-msft Aug 11, 2023
5fc50e3
postpone problematic case
annelo-msft Aug 11, 2023
85d2ae1
revert test files
annelo-msft Aug 11, 2023
115b597
updates
annelo-msft Aug 11, 2023
dc9796c
test nit
annelo-msft Aug 11, 2023
7ea10ef
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 14, 2023
2ebfe0a
Add tests, update comments
annelo-msft Aug 14, 2023
1cc4e90
simplify
annelo-msft Aug 14, 2023
9046a74
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 14, 2023
86be925
Merge branch 'core-make-mjdoc-internal' of https://github.com/annelo-…
annelo-msft Aug 14, 2023
55e4024
Update Patch models to use new Parse method
annelo-msft Aug 14, 2023
0d95c95
nit readonly
annelo-msft Aug 14, 2023
31d9d1b
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 15, 2023
7a68dae
add tests - WIP
annelo-msft Aug 15, 2023
ccc7d02
fixes
annelo-msft Aug 15, 2023
8650097
moving to draft - refactor and updates
annelo-msft Aug 15, 2023
1ef64b7
Merge remote-tracking branch 'upstream/main' into core-make-mjdoc-int…
annelo-msft Aug 16, 2023
d59d7da
IUtf8JsonSerializable
annelo-msft Aug 16, 2023
1e08e78
Add RoundTripPatchModel to illustrate GET/PATCH model shape
annelo-msft Aug 16, 2023
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 @@ -175,7 +175,7 @@
-->
<ItemGroup>
<PackageReference Update="Microsoft.Azure.AutoRest.CSharp" Version="3.0.0-beta.20230815.1" PrivateAssets="All" />
<PackageReference Update="Azure.ClientSdk.Analyzers" Version="0.1.1-dev.20230131.1" PrivateAssets="All" />
<PackageReference Update="Azure.ClientSdk.Analyzers" Version="0.1.1-dev.20230811.4" PrivateAssets="All" />
<PackageReference Update="coverlet.collector" Version="3.2.0" PrivateAssets="All" />
<PackageReference Update="Microsoft.CodeAnalysis.NetAnalyzers" Version="7.0.1" PrivateAssets="All" />
<PackageReference Update="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.2" PrivateAssets="All" />
Expand Down
9 changes: 9 additions & 0 deletions sdk/core/Azure.Core/src/Azure.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,15 @@
<Compile Include="Shared\HttpMessageSanitizer.cs" />
<Compile Include="Shared\InitializationConstructorAttribute.cs" />
<Compile Include="Shared\Multipart\MemoryResponse.cs" />
<Compile Include="Shared\MutableJsonChange.cs" />
<Compile Include="Shared\MutableJsonChangeKind.cs" />
<Compile Include="Shared\MutableJsonDocument.ChangeTracker.cs" />
<Compile Include="Shared\MutableJsonDocument.cs" />
<Compile Include="Shared\MutableJsonElement.ArrayEnumerator.cs" />
<Compile Include="Shared\MutableJsonElement.cs" />
<Compile Include="Shared\MutableJsonElement.ObjectEnumerator.cs" />
<Compile Include="Shared\MutableJsonElement.WriteTo.cs" />
<Compile Include="Shared\MutableJsonElement.WriteTo.MergePatch.cs" />
<Compile Include="Shared\NullableAttributes.cs" />
<Compile Include="Shared\OperationInternalBase.cs" />
<Compile Include="Shared\TrimmingAttribute.cs" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Buffers;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
internal struct MutableJsonChange
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

#nullable enable

namespace Azure.Core.Json
{
internal enum MutableJsonChangeKind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Diagnostics;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
internal partial class MutableJsonDocument
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#nullable enable

namespace Azure.Core.Json
{
/// <summary>
Expand All @@ -16,6 +18,9 @@ namespace Azure.Core.Json
[JsonConverter(typeof(MutableJsonDocumentConverter))]
internal sealed partial class MutableJsonDocument : IDisposable
{
private static readonly ReadOnlyMemory<byte> _emptyJson = "{}"u8.ToArray();
public static ReadOnlyMemory<byte> EmptyJson => _emptyJson;

private readonly ReadOnlyMemory<byte> _original;
private readonly JsonDocument _originalDocument;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using System.Diagnostics;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
internal partial struct MutableJsonElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
using System.Diagnostics;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Diagnostics;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
internal partial struct MutableJsonElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Text.Json;

#nullable enable

namespace Azure.Core.Json
{
internal partial struct MutableJsonElement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
using System.Text.Json;
using System.Text.Json.Serialization;

#nullable enable

namespace Azure.Core.Json
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,16 @@

<!-- Shared source from Azure.Core -->
<ItemGroup>
<Compile Include="$(AzureCoreSharedSources)ArrayBufferWriter.cs" LinkBase="Shared/Core" />
<Compile Include="$(AzureCoreSharedSources)ModelSerializerHelper.cs" LinkBase="Shared/Core" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonChange.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonChangeKind.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonDocument.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonDocument.ChangeTracker.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonElement.ArrayEnumerator.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonElement.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonElement.ObjectEnumerator.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonElement.WriteTo.cs" LinkBase="Shared" />
<Compile Include="$(AzureCoreSharedSources)MutableJsonElement.WriteTo.MergePatch.cs" LinkBase="Shared" />
</ItemGroup>

</Project>
64 changes: 64 additions & 0 deletions sdk/core/Azure.Core/tests/public/PatchModels/ChildPatchModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core.Json;

namespace Azure.Core.Tests.PatchModels
{
/// <summary>
/// This model illustrates a nested child model in a parent model.
/// </summary>
public partial class ChildPatchModel
Comment thread
annelo-msft marked this conversation as resolved.
{
#pragma warning disable AZC0020 // Avoid using banned types in libraries
private readonly MutableJsonElement _element;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a model is output and roundtrip only, I think storing all the data in MJE is fine. But what will we do for models that are also input models? Should these have two modes: one where data is stored in regular fields (as we do today) and one where the data is stored in MJE?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll see if I can add some benchmarks to see how favorable that approach is.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per annelo-msft#9, MJD serialization is comparable to standard model serialization, except when we modify the change list, since it's essentially a pass-through to JsonDocument when there are no changes. I'll spend some time looking at whether we can optimize the case where we access the change list.


// Note: A child patch model doesn't have a public constructor.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have public constructor currently. Is there any reason we change this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thinking is that, for Patch models, we need a way to connect the MutableJsonElement in the child model to the MutableJsonElement in the parent model, so that they point to the same root MutableJsonDocument. Without this, they don't access the same changelist and writing the Patch JSON would be a lot more complicated.

We could make it so that nested models have a public constructor but can't be set on the parent (shown here), but if you can't set the child model, then I don't know why you would have a public constructor on the child model - callers could create an instance of it but then not do anything with it, which seems like it could be frustrating to users.

There may be times when a nested model is also an input model on a service method - when that happens the model will need to have a public constructor, and we will need to make the parent model not settable so we can connect the MutableJsonElements. It would be ideal if the generator could detect that case and generate the model with the public constructor when that happens.

//
// When a nested model is also an input to a service method, it will
// need to have a public constructor. When this happens, the parent
// model it is also part of will not allow it to be set, to ensure the
// MutableJsonElements point to the same root MutableJsonDocument when
// they are part of the same input, so the Patch JSON will be correct.

/// <summary> Serialization constructor. </summary>
/// <param name="element"></param>
internal ChildPatchModel(MutableJsonElement element)
{
_element = element;
}

/// <summary>
/// Optional string property corresponding to JSON """{"a": "aaa"}""".
/// </summary>
public string A
{
get
{
if (_element.TryGetProperty("a", out MutableJsonElement value))
{
return value.GetString();
}
return null;
}
set => _element.SetProperty("a", value);
}

/// <summary>
/// Optional string property corresponding to JSON """{"b": "bbb"}""".
/// </summary>
public string B
{
get
{
if (_element.TryGetProperty("b", out MutableJsonElement value))
{
return value.GetString();
}
return null;
}
set => _element.SetProperty("b", value);
}
#pragma warning restore AZC0020 // Avoid using banned types in libraries
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System;
using System.Text.Json;
using Azure.Core.Json;
using Azure.Core.Serialization;

namespace Azure.Core.Tests.PatchModels
{
public partial class ParentPatchModel : IModelJsonSerializable<ParentPatchModel>, IUtf8JsonSerializable
{
ParentPatchModel IModelJsonSerializable<ParentPatchModel>.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options)
{
PatchModelHelper.ValidateFormat(this, options.Format);

return Deserialize(ref reader, options);
}

private static ParentPatchModel Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options)
{
MutableJsonDocument mdoc = MutableJsonDocument.Parse(ref reader);
return new ParentPatchModel(mdoc.RootElement);
}

ParentPatchModel IModelSerializable<ParentPatchModel>.Deserialize(BinaryData data, ModelSerializerOptions options)
{
PatchModelHelper.ValidateFormat(this, options.Format);

return Deserialize(data, options);
}

private static ParentPatchModel Deserialize(BinaryData data, ModelSerializerOptions options)
{
MutableJsonDocument mdoc = MutableJsonDocument.Parse(data);
return new ParentPatchModel(mdoc.RootElement);
}

void IModelJsonSerializable<ParentPatchModel>.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options)
{
PatchModelHelper.ValidateFormat(this, options.Format);

switch (options.Format.ToString())
{
case "J":
case "W":
_element.WriteTo(writer, "J");
break;
case "P":
_element.WriteTo(writer, "P");
break;
default:
// Exception was thrown by ValidateFormat.
break;
}
}

BinaryData IModelSerializable<ParentPatchModel>.Serialize(ModelSerializerOptions options)
{
PatchModelHelper.ValidateFormat(this, options.Format);

return ModelSerializer.SerializeCore(this, options);
}

public static implicit operator RequestContent(ParentPatchModel model)
=> RequestContent.Create(model, ModelSerializerOptions.DefaultWireOptions);

public static explicit operator ParentPatchModel(Response response)
{
Argument.AssertNotNull(response, nameof(response));

return Deserialize(response.Content, ModelSerializerOptions.DefaultWireOptions);
}

void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IModelJsonSerializable<SimplePatchModel>)this).Serialize(writer, ModelSerializerOptions.DefaultWireOptions);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using Azure.Core.Json;

namespace Azure.Core.Tests.PatchModels
{
/// <summary>
/// This model illustrates a patch model with properties that are nested models.
/// </summary>
public partial class ParentPatchModel
{
#pragma warning disable AZC0020 // Avoid using banned types in libraries
private readonly MutableJsonElement _element;

/// <summary>
/// Public constructor.
/// </summary>
public ParentPatchModel()
{
_element = MutableJsonDocument.Parse(MutableJsonDocument.EmptyJson).RootElement;
}

/// <summary>
/// Serialization constructor.
/// </summary>
/// <param name="element"></param>
internal ParentPatchModel(MutableJsonElement element)
{
_element = element;
}

/// <summary>
/// Optional string property corresponding to JSON """{"id": "abc"}""".
/// </summary>
public string Id
Comment thread
annelo-msft marked this conversation as resolved.
{
get
{
if (_element.TryGetProperty("id", out MutableJsonElement value))
{
return value.GetString();
}
return null;
}
set => _element.SetProperty("id", value);
}

private ChildPatchModel _child;
/// <summary>
/// Optional ChildPatchModel property corresponding to JSON """{"child": {"a":"aa", "b": "bb"}}""".
/// </summary>
public ChildPatchModel Child
{
get
{
if (_child == null)
{
if (!_element.TryGetProperty("child", out MutableJsonElement element))
{
_element.SetProperty("child", new { });
element = _element.GetProperty("child");
}

_child = new ChildPatchModel(element);
}

return _child;
}

// Note: a child patch model isn't settable on the parent.
// This is because its _element property needs to have the
// same root MutableJsonDocument as the parent.
//
// It's unclear how we would plug it in after the fact if we wanted
// to make an instance of the child model something that could be
// used in multiple places.
}
#pragma warning restore AZC0020 // Avoid using banned types in libraries
}
}
Loading