diff --git a/sdk/communication/Azure.Communication.JobRouter/src/Azure.Communication.JobRouter.csproj b/sdk/communication/Azure.Communication.JobRouter/src/Azure.Communication.JobRouter.csproj
index b9aca635fe4a..07e9f4cda383 100644
--- a/sdk/communication/Azure.Communication.JobRouter/src/Azure.Communication.JobRouter.csproj
+++ b/sdk/communication/Azure.Communication.JobRouter/src/Azure.Communication.JobRouter.csproj
@@ -17,7 +17,6 @@
-
@@ -27,5 +26,8 @@
+
+
+
diff --git a/sdk/communication/Azure.Communication.JobRouter/src/Generated/JobRouterRestClient.cs b/sdk/communication/Azure.Communication.JobRouter/src/Generated/JobRouterRestClient.cs
index 15694ece3888..7d1f1c8a69be 100644
--- a/sdk/communication/Azure.Communication.JobRouter/src/Generated/JobRouterRestClient.cs
+++ b/sdk/communication/Azure.Communication.JobRouter/src/Generated/JobRouterRestClient.cs
@@ -53,7 +53,8 @@ internal HttpMessage CreateUpsertJobRequest(string id, RouterJob patch)
request.Headers.Add("Accept", "application/json");
request.Headers.Add("Content-Type", "application/merge-patch+json");
var content = new Utf8JsonRequestContent();
- content.JsonWriter.WriteObjectValue(patch);
+ // TODO:
+ //content.JsonWriter.WriteObjectValue(patch);
request.Content = content;
return message;
}
diff --git a/sdk/communication/Azure.Communication.JobRouter/src/Generated/Models/RouterJob.Serialization.cs b/sdk/communication/Azure.Communication.JobRouter/src/Generated/Models/RouterJob.Serialization.cs
index e2009385449a..3c2dc1dd0bd2 100644
--- a/sdk/communication/Azure.Communication.JobRouter/src/Generated/Models/RouterJob.Serialization.cs
+++ b/sdk/communication/Azure.Communication.JobRouter/src/Generated/Models/RouterJob.Serialization.cs
@@ -8,14 +8,42 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
-using Azure.Communication.JobRouter;
using Azure.Core;
+using Azure.Core.Serialization;
namespace Azure.Communication.JobRouter.Models
{
- public partial class RouterJob : IUtf8JsonSerializable
+ public partial class RouterJob : IModelJsonSerializable, IUtf8JsonSerializable
{
- void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
+ #region Serialize
+ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer) => ((IModelJsonSerializable)this).Serialize(writer, ModelSerializerOptions.DefaultWireOptions);
+
+ void IModelJsonSerializable.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options)
+ {
+ // TODO: PatchModelHelper.ValidateFormat(this, options.Format);
+
+ switch (options.Format.ToString())
+ {
+ case "J":
+ case "W":
+ Serialize(writer, options);
+ break;
+ case "P":
+ SerializePatch(writer);
+ break;
+ default:
+ // Exception was thrown by ValidateFormat.
+ break;
+ }
+ }
+
+ BinaryData IModelSerializable.Serialize(ModelSerializerOptions options)
+ {
+ // TODO: PatchModelHelper.ValidateFormat(this, options.Format);
+ return ModelSerializer.SerializeCore(this, options);
+ }
+
+ private void Serialize(Utf8JsonWriter writer, ModelSerializerOptions options)
{
writer.WriteStartObject();
if (Optional.IsDefined(ChannelReference))
@@ -48,21 +76,21 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
writer.WritePropertyName("dispositionCode"u8);
writer.WriteStringValue(DispositionCode);
}
- if (Optional.IsCollectionDefined(_requestedWorkerSelectors))
+ if (Optional.IsCollectionDefined(RequestedWorkerSelectors))
{
writer.WritePropertyName("requestedWorkerSelectors"u8);
writer.WriteStartArray();
- foreach (var item in _requestedWorkerSelectors)
+ foreach (var item in RequestedWorkerSelectors)
{
writer.WriteObjectValue(item);
}
writer.WriteEndArray();
}
- if (Optional.IsCollectionDefined(_labels))
+ if (Optional.IsCollectionDefined(Labels))
{
writer.WritePropertyName("labels"u8);
writer.WriteStartObject();
- foreach (var item in _labels)
+ foreach (var item in Labels)
{
writer.WritePropertyName(item.Key);
if (item.Value == null)
@@ -74,11 +102,11 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
}
writer.WriteEndObject();
}
- if (Optional.IsCollectionDefined(_tags))
+ if (Optional.IsCollectionDefined(Tags))
{
writer.WritePropertyName("tags"u8);
writer.WriteStartObject();
- foreach (var item in _tags)
+ foreach (var item in Tags)
{
writer.WritePropertyName(item.Key);
if (item.Value == null)
@@ -90,17 +118,18 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
}
writer.WriteEndObject();
}
- if (Optional.IsCollectionDefined(_notes))
- {
- writer.WritePropertyName("notes"u8);
- writer.WriteStartObject();
- foreach (var item in _notes)
- {
- writer.WritePropertyName(item.Key);
- writer.WriteStringValue(item.Value);
- }
- writer.WriteEndObject();
- }
+ // TODO
+ //if (Optional.IsCollectionDefined(Notes))
+ //{
+ // writer.WritePropertyName("notes"u8);
+ // writer.WriteStartObject();
+ // foreach (var item in Notes)
+ // {
+ // writer.WritePropertyName(item.Key);
+ // writer.WriteStringValue(item.Value);
+ // }
+ // writer.WriteEndObject();
+ //}
if (Optional.IsDefined(MatchingMode))
{
writer.WritePropertyName("matchingMode"u8);
@@ -109,7 +138,19 @@ void IUtf8JsonSerializable.Write(Utf8JsonWriter writer)
writer.WriteEndObject();
}
- internal static RouterJob DeserializeRouterJob(JsonElement element)
+ private void SerializePatch(Utf8JsonWriter writer)
+ {
+ throw new NotImplementedException();
+ }
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+ public static implicit operator RequestContent(RouterJob model)
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+ => RequestContent.Create(model, ModelSerializerOptions.DefaultWireOptions);
+ #endregion
+
+ #region Deserialize
+ internal static RouterJob DeserializeRouterJob(JsonElement element, ModelSerializerOptions options = default)
{
if (element.ValueKind == JsonValueKind.Null)
{
@@ -310,5 +351,29 @@ internal static RouterJob DeserializeRouterJob(JsonElement element)
}
return new RouterJob(id.Value, channelReference.Value, Optional.ToNullable(status), Optional.ToNullable(enqueuedAt), channelId.Value, classificationPolicyId.Value, queueId.Value, Optional.ToNullable(priority), dispositionCode.Value, Optional.ToList(requestedWorkerSelectors), Optional.ToList(attachedWorkerSelectors), Optional.ToDictionary(labels), Optional.ToDictionary(assignments), Optional.ToDictionary(tags), Optional.ToDictionary(notes), Optional.ToNullable(scheduledAt), matchingMode.Value);
}
+
+ RouterJob IModelJsonSerializable.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options)
+ {
+ // TODO: PatchModelHelper.ValidateFormat(this, options.Format);
+ using var doc = JsonDocument.ParseValue(ref reader);
+ return DeserializeRouterJob(doc.RootElement, options);
+ }
+
+ RouterJob IModelSerializable.Deserialize(BinaryData data, ModelSerializerOptions options)
+ {
+ // TODO: PatchModelHelper.ValidateFormat(this, options.Format);
+ return DeserializeRouterJob(JsonDocument.Parse(data.ToString()).RootElement, options);
+ }
+
+#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
+ public static explicit operator RouterJob(Response response)
+#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
+ {
+ Argument.AssertNotNull(response, nameof(response));
+
+ using JsonDocument jsonDocument = JsonDocument.Parse(response.ContentStream);
+ return DeserializeRouterJob(jsonDocument.RootElement, ModelSerializerOptions.DefaultWireOptions);
+ }
+ #endregion
}
}
diff --git a/sdk/communication/Azure.Communication.JobRouter/src/JobRouterClient.cs b/sdk/communication/Azure.Communication.JobRouter/src/JobRouterClient.cs
index 697b5a6c19e2..0bd262ec7a3f 100644
--- a/sdk/communication/Azure.Communication.JobRouter/src/JobRouterClient.cs
+++ b/sdk/communication/Azure.Communication.JobRouter/src/JobRouterClient.cs
@@ -1,4 +1,4 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
+// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
@@ -9,8 +9,9 @@
using Azure.Communication.Pipeline;
using Azure.Core;
using Azure.Core.Pipeline;
+using Azure.Core.Serialization;
- namespace Azure.Communication.JobRouter
+namespace Azure.Communication.JobRouter
{
///
/// The Azure Communication Services Router client.
@@ -115,7 +116,7 @@ public virtual async Task> CreateJobWithClassificationPolicy
scope.Start();
try
{
- var request = new RouterJob
+ var request = new RouterJob(options.JobId)
{
ChannelId = options.ChannelId,
ClassificationPolicyId = options.ClassificationPolicyId,
@@ -146,7 +147,7 @@ public virtual async Task> CreateJobWithClassificationPolicy
}
return await RestClient.UpsertJobAsync(
- id:options.JobId,
+ id: options.JobId,
patch: request,
cancellationToken: cancellationToken)
.ConfigureAwait(false);
@@ -170,7 +171,7 @@ public virtual Response CreateJobWithClassificationPolicy(
scope.Start();
try
{
- var request = new RouterJob
+ var request = new RouterJob(options.JobId)
{
ChannelId = options.ChannelId,
ClassificationPolicyId = options.ClassificationPolicyId,
@@ -201,7 +202,7 @@ public virtual Response CreateJobWithClassificationPolicy(
}
return RestClient.UpsertJob(
- id:options.JobId,
+ id: options.JobId,
patch: request,
cancellationToken: cancellationToken);
}
@@ -217,51 +218,25 @@ public virtual Response CreateJobWithClassificationPolicy(
#region Create job with direct queue assignment
/// Creates a new job to be routed.
- /// Options for creating job with direct queue assignment.
+ /// Job to create with direct queue assignment.
/// The cancellation token to use.
/// The server returned an error. See for details returned from the server.
public virtual async Task> CreateJobAsync(
- CreateJobOptions options,
+ RouterJob job,
CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(JobRouterClient)}.{nameof(CreateJob)}");
scope.Start();
try
{
- var request = new RouterJob
- {
- ChannelId = options.ChannelId,
- ChannelReference = options.ChannelReference,
- QueueId = options.QueueId,
- Priority = options.Priority,
- MatchingMode = options.MatchingMode,
- };
-
- foreach (var label in options.Labels)
+ if (job is not IModelJsonSerializable model)
{
- request.Labels[label.Key] = label.Value;
+ throw new InvalidCastException("Model is not serializable.");
}
- foreach (var tag in options.Tags)
- {
- request.Tags[tag.Key] = tag.Value;
- }
-
- foreach (var workerSelector in options.RequestedWorkerSelectors)
- {
- request.RequestedWorkerSelectors.Add(workerSelector);
- }
-
- foreach (var note in options.Notes)
- {
- request.Notes.Add(note);
- }
-
- return await RestClient.UpsertJobAsync(
- id: options.JobId,
- patch: request,
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
+ RequestContent content = RequestContent.Create(model, new ModelSerializerOptions("P"));
+ Response response = await UpdateJobAsync(job.Id, content, new() { CancellationToken = cancellationToken }).ConfigureAwait(false);
+ return Response.FromValue((RouterJob)response, response);
}
catch (Exception ex)
{
@@ -271,50 +246,26 @@ public virtual async Task> CreateJobAsync(
}
/// Creates a new job to be routed.
- /// Options for creating job with direct queue assignment.
+ /// Job to create with direct queue assignment.
/// The cancellation token to use.
/// The server returned an error. See for details returned from the server.
public virtual Response CreateJob(
- CreateJobOptions options,
+ RouterJob job,
CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(JobRouterClient)}.{nameof(CreateJob)}");
scope.Start();
try
{
- var request = new RouterJob
- {
- ChannelId = options.ChannelId,
- ChannelReference = options.ChannelReference,
- QueueId = options.QueueId,
- Priority = options.Priority,
- MatchingMode = options.MatchingMode,
- };
-
- foreach (var label in options.Labels)
- {
- request.Labels[label.Key] = label.Value;
- }
-
- foreach (var tag in options.Tags)
+ if (job is not IModelJsonSerializable model)
{
- request.Tags[tag.Key] = tag.Value;
+ throw new InvalidCastException("Model is not serializable.");
}
- foreach (var workerSelector in options.RequestedWorkerSelectors)
- {
- request.RequestedWorkerSelectors.Add(workerSelector);
- }
+ RequestContent content = RequestContent.Create(model, new ModelSerializerOptions("P"));
+ Response response = UpdateJob(job.Id, content, new() { CancellationToken = cancellationToken });
- foreach (var note in options.Notes)
- {
- request.Notes.Add(note);
- }
-
- return RestClient.UpsertJob(
- id: options.JobId,
- patch: request,
- cancellationToken: cancellationToken);
+ return Response.FromValue((RouterJob)response, response);
}
catch (Exception ex)
{
@@ -323,56 +274,28 @@ public virtual Response CreateJob(
}
}
- #endregion Create job with direct queue assignment
+ #endregion Update job with direct queue assignment
/// Update an existing job.
- /// Options for updating a job. Uses merge-patch semantics: https://datatracker.ietf.org/doc/html/rfc7386.
+ /// Job to update. Uses merge-patch semantics: https://datatracker.ietf.org/doc/html/rfc7386.
/// The cancellation token to use.
/// The server returned an error. See for details returned from the server.
public virtual async Task> UpdateJobAsync(
- UpdateJobOptions options,
+ RouterJob job,
CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(JobRouterClient)}.{nameof(UpdateJob)}");
scope.Start();
try
{
- var request = new RouterJob
- {
- ChannelId = options.ChannelId,
- ClassificationPolicyId = options.ClassificationPolicyId,
- ChannelReference = options.ChannelReference,
- QueueId = options.QueueId,
- Priority = options.Priority,
- DispositionCode = options.DispositionCode,
- MatchingMode = options.MatchingMode,
- };
-
- foreach (var label in options.Labels)
- {
- request.Labels[label.Key] = label.Value;
- }
-
- foreach (var tag in options.Tags)
+ if (job is not IModelJsonSerializable model)
{
- request.Tags[tag.Key] = tag.Value;
+ throw new InvalidCastException("Model is not serializable.");
}
- foreach (var workerSelector in options.RequestedWorkerSelectors)
- {
- request.RequestedWorkerSelectors.Add(workerSelector);
- }
-
- foreach (var note in options.Notes)
- {
- request.Notes.Add(note);
- }
-
- return await RestClient.UpsertJobAsync(
- id: options.JobId,
- patch: request,
- cancellationToken: cancellationToken)
- .ConfigureAwait(false);
+ RequestContent content = RequestContent.Create(model, new ModelSerializerOptions("P"));
+ Response response = await UpdateJobAsync(job.Id, content, new() { CancellationToken = cancellationToken }).ConfigureAwait(false);
+ return Response.FromValue((RouterJob)response, response);
}
catch (Exception ex)
{
@@ -382,52 +305,25 @@ public virtual async Task> UpdateJobAsync(
}
/// Update an existing job.
- /// Options for updating a job. Uses merge-patch semantics: https://datatracker.ietf.org/doc/html/rfc7386.
+ /// Job to update. Uses merge-patch semantics: https://datatracker.ietf.org/doc/html/rfc7386.
/// The cancellation token to use.
/// The server returned an error. See for details returned from the server.
public virtual Response UpdateJob(
- UpdateJobOptions options,
+ RouterJob job,
CancellationToken cancellationToken = default)
{
using DiagnosticScope scope = _clientDiagnostics.CreateScope($"{nameof(JobRouterClient)}.{nameof(UpdateJob)}");
scope.Start();
try
{
- var request = new RouterJob
+ if (job is not IModelJsonSerializable model)
{
- ChannelId = options.ChannelId,
- ClassificationPolicyId = options.ClassificationPolicyId,
- ChannelReference = options.ChannelReference,
- QueueId = options.QueueId,
- Priority = options.Priority,
- DispositionCode = options.DispositionCode,
- MatchingMode = options.MatchingMode,
- };
-
- foreach (var label in options.Labels)
- {
- request.Labels[label.Key] = label.Value;
- }
-
- foreach (var tag in options.Tags)
- {
- request.Tags[tag.Key] = tag.Value;
+ throw new InvalidCastException("Model is not serializable.");
}
- foreach (var workerSelector in options.RequestedWorkerSelectors)
- {
- request.RequestedWorkerSelectors.Add(workerSelector);
- }
-
- foreach (var note in options.Notes)
- {
- request.Notes.Add(note);
- }
-
- return RestClient.UpsertJob(
- id: options.JobId,
- patch: request,
- cancellationToken: cancellationToken);
+ RequestContent content = RequestContent.Create(model, new ModelSerializerOptions("P"));
+ Response response = UpdateJob(job.Id, content, new() { CancellationToken = cancellationToken });
+ return Response.FromValue((RouterJob)response, response);
}
catch (Exception ex)
{
@@ -1476,7 +1372,7 @@ async Task> FirstPageFunc(int? maxPageSize)
queueId: options?.QueueId,
hasCapacity: options?.HasCapacity,
maxpagesize: maxPageSize,
- cancellationToken: cancellationToken)
+ cancellationToken: cancellationToken)
.ConfigureAwait(false);
return Page.FromValues(response.Value.Value, response.Value.NextLink, response.GetRawResponse());
}
diff --git a/sdk/communication/Azure.Communication.JobRouter/src/Models/RouterJob.cs b/sdk/communication/Azure.Communication.JobRouter/src/Models/RouterJob.cs
index c8887150f83e..2101dcb95ece 100644
--- a/sdk/communication/Azure.Communication.JobRouter/src/Models/RouterJob.cs
+++ b/sdk/communication/Azure.Communication.JobRouter/src/Models/RouterJob.cs
@@ -5,7 +5,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text.Json;
using Azure.Core;
namespace Azure.Communication.JobRouter.Models
@@ -15,8 +14,10 @@ namespace Azure.Communication.JobRouter.Models
public partial class RouterJob
{
/// Initializes a new instance of RouterJob.
- internal RouterJob()
+ public RouterJob(string id)
{
+ Id = id;
+
AttachedWorkerSelectors = new ChangeTrackingList();
Assignments = new ChangeTrackingDictionary();
_requestedWorkerSelectors = new ChangeTrackingList();
@@ -28,10 +29,10 @@ internal RouterJob()
///
/// A set of key/value pairs that are identifying attributes used by the rules engines to make decisions.
///
- public Dictionary Labels { get; } = new Dictionary();
+ public IDictionary Labels { get; } = new Dictionary();
/// A set of non-identifying attributes attached to this job.
- public Dictionary Tags { get; } = new Dictionary();
+ public IDictionary Tags { get; } = new Dictionary();
/// A collection of manually specified label selectors, which a worker must satisfy in order to process this job.
public List RequestedWorkerSelectors { get; } = new List();
diff --git a/sdk/communication/Azure.Communication.JobRouter/tests/Samples/JobRouterPatchSamples.cs b/sdk/communication/Azure.Communication.JobRouter/tests/Samples/JobRouterPatchSamples.cs
new file mode 100644
index 000000000000..47ca32074bbb
--- /dev/null
+++ b/sdk/communication/Azure.Communication.JobRouter/tests/Samples/JobRouterPatchSamples.cs
@@ -0,0 +1,162 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+//
+
+#nullable disable
+
+using System;
+using Azure.Communication.JobRouter.Models;
+using Azure.Core;
+using Azure.Core.Serialization;
+using Azure.Identity;
+using NUnit.Framework;
+
+namespace Azure.Communication.JobRouter.Samples
+{
+ public class RouterJobModelSamples
+ {
+ [Test]
+ public void RoundTripRouterJobSample_ProtocolMethods()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = client.GetJob("");
+
+ Console.WriteLine($"Job ID: '{routerJob.Id}'.");
+ Console.WriteLine($"Channel ID: '{routerJob.ChannelId}'.");
+
+ var patch = new
+ {
+ labels = new
+ {
+ Some_Skill = 10
+ }
+ };
+
+ // Sends JSON """{{"labels": {"Some_Skill": 10}}}"""
+ client.UpdateJob(routerJob.Id, RequestContent.Create(patch));
+ }
+
+ [Test]
+ public void RoundTripRouterJobSample_ProtocolMethods_DeleteLabel()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = client.GetJob("");
+
+ Console.WriteLine($"Job ID: '{routerJob.Id}'.");
+ Console.WriteLine($"Channel ID: '{routerJob.ChannelId}'.");
+
+ var patch = new
+ {
+ labels = new
+ {
+ Some_Skill = (object)null
+ }
+ };
+
+ // Sends JSON """{{"labels": {"Some_Skill": null}}}"""
+ client.UpdateJob(routerJob.Id, RequestContent.Create(patch));
+ }
+
+ [Test]
+ public void RoundTripRouterJobSample_ConvenienceMethods()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = client.GetJob("");
+
+ Console.WriteLine($"Job ID: '{routerJob.Id}'.");
+ Console.WriteLine($"Channel ID: '{routerJob.ChannelId}'.");
+
+ // Sends JSON """{{"labels": {"Some_Skill": 10}}}"""
+ routerJob.Labels["Some_Skill"] = new LabelValue(10);
+
+ client.UpdateJob(routerJob);
+ }
+
+ [Test]
+ public void RoundTripRouterJobSample_ConvenienceMethods_DeleteLabel()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = client.GetJob("");
+
+ Console.WriteLine($"Job ID: '{routerJob.Id}'.");
+ Console.WriteLine($"Channel ID: '{routerJob.ChannelId}'.");
+
+ routerJob.Labels.Remove("Some_Skill");
+
+ // Sends JSON """{{"labels": {"Some_Skill": null}}}"""
+ client.UpdateJob(routerJob);
+ }
+ [Test]
+ public void PatchWithoutGetRouterJobSample_ProtocolMethods()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ var patch = new
+ {
+ labels = new
+ {
+ Some_Skill = 10
+ }
+ };
+
+ // Sends JSON """{{"labels": {"Some_Skill": 10}}}"""
+ client.UpdateJob("", RequestContent.Create(patch));
+ }
+
+ [Test]
+ public void PatchWithoutGetRouterJobSample_ProtocolMethods_DeleteLabel()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ var patch = new
+ {
+ labels = new
+ {
+ Some_Skill = (object)null
+ }
+ };
+
+ // Sends JSON """{{"labels": {"Some_Skill": null}}}"""
+ client.UpdateJob("", RequestContent.Create(patch));
+ }
+
+ [Test]
+ public void PatchWithoutGetRouterJobSample_ConvenienceMethods()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = new RouterJob("");
+
+ // Sends JSON """{{"labels": {"Some_Skill": 10}}}"""
+ routerJob.Labels["Some_Skill"] = new LabelValue(10);
+
+ client.UpdateJob(routerJob);
+ }
+
+ [Test]
+ public void PatchWithoutGetRouterJobSample_ConvenienceMethods_DeleteLabel()
+ {
+ var credential = new DefaultAzureCredential();
+ var client = new JobRouterClient("");
+
+ RouterJob routerJob = new RouterJob("");
+
+ routerJob.Labels.Remove("Some_Skill");
+
+ // Sends JSON """{{"labels": {"Some_Skill": null}}}"""
+ client.UpdateJob(routerJob);
+ }
+ }
+}
diff --git a/sdk/communication/Azure.Communication.JobRouter/tests/Samples/Sample1_KeyConcepts.cs b/sdk/communication/Azure.Communication.JobRouter/tests/Samples/Sample1_KeyConcepts.cs
index 5ee4a2ca37b8..068856b668c3 100644
--- a/sdk/communication/Azure.Communication.JobRouter/tests/Samples/Sample1_KeyConcepts.cs
+++ b/sdk/communication/Azure.Communication.JobRouter/tests/Samples/Sample1_KeyConcepts.cs
@@ -48,19 +48,19 @@ public void BasicScenario()
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_CreateQueue
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_CreateJobDirectQAssign
- Response job = routerClient.CreateJob(
- new CreateJobOptions(
- jobId: "jobId-2",
- channelId: "my-channel",
- queueId: queue.Value.Id)
- {
- ChannelReference = "12345",
- Priority = 1,
- RequestedWorkerSelectors =
- {
- new RouterWorkerSelector("Some-Skill", LabelOperator.GreaterThan, new LabelValue(10))
- },
- });
+ //Response job = routerClient.CreateJob(
+ // new CreateJobOptions(
+ // jobId: "jobId-2",
+ // channelId: "my-channel",
+ // queueId: queue.Value.Id)
+ // {
+ // ChannelReference = "12345",
+ // Priority = 1,
+ // RequestedWorkerSelectors =
+ // {
+ // new RouterWorkerSelector("Some-Skill", LabelOperator.GreaterThan, new LabelValue(10))
+ // },
+ // });
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_CreateJobDirectQAssign
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_RegisterWorker
@@ -86,68 +86,68 @@ public void BasicScenario()
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_AcceptOffer
- // fetching the offer id
- Models.RouterJobOffer jobOffer = result.Value.Offers.First(x => x.JobId == job.Value.Id);
+ //// fetching the offer id
+ //Models.RouterJobOffer jobOffer = result.Value.Offers.First(x => x.JobId == job.Value.Id);
- string offerId = jobOffer.OfferId; // `OfferId` can be retrieved directly from consuming event from Event grid
+ //string offerId = jobOffer.OfferId; // `OfferId` can be retrieved directly from consuming event from Event grid
- // accepting the offer sent to `worker-1`
- Response acceptJobOfferResult = routerClient.AcceptJobOffer(worker.Value.Id, offerId);
+ //// accepting the offer sent to `worker-1`
+ //Response acceptJobOfferResult = routerClient.AcceptJobOffer(worker.Value.Id, offerId);
- Console.WriteLine($"Offer: {jobOffer.OfferId} sent to worker: {worker.Value.Id} has been accepted");
- Console.WriteLine($"Job has been assigned to worker: {worker.Value.Id} with assignment: {acceptJobOfferResult.Value.AssignmentId}");
+ //Console.WriteLine($"Offer: {jobOffer.OfferId} sent to worker: {worker.Value.Id} has been accepted");
+ //Console.WriteLine($"Job has been assigned to worker: {worker.Value.Id} with assignment: {acceptJobOfferResult.Value.AssignmentId}");
- // verify job assignment is populated when querying job
- Response updatedJob = routerClient.GetJob(job.Value.Id);
- Console.WriteLine($"Job assignment has been successful: {updatedJob.Value.Status == RouterJobStatus.Assigned && updatedJob.Value.Assignments.ContainsKey(acceptJobOfferResult.Value.AssignmentId)}");
+ //// verify job assignment is populated when querying job
+ //Response updatedJob = routerClient.GetJob(job.Value.Id);
+ //Console.WriteLine($"Job assignment has been successful: {updatedJob.Value.Status == RouterJobStatus.Assigned && updatedJob.Value.Assignments.ContainsKey(acceptJobOfferResult.Value.AssignmentId)}");
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_AcceptOffer
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_CompleteJob
- Response completeJob = routerClient.CompleteJob(
- options: new CompleteJobOptions(
- jobId: job.Value.Id,
- assignmentId: acceptJobOfferResult.Value.AssignmentId)
- {
- Note = $"Job has been completed by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
- });
+ //Response completeJob = routerClient.CompleteJob(
+ // options: new CompleteJobOptions(
+ // jobId: job.Value.Id,
+ // assignmentId: acceptJobOfferResult.Value.AssignmentId)
+ // {
+ // Note = $"Job has been completed by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
+ // });
- Console.WriteLine($"Job has been successfully completed: {completeJob.Status == 200}");
+ //Console.WriteLine($"Job has been successfully completed: {completeJob.Status == 200}");
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_CompleteJob
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_CloseJob
- Response closeJob = routerClient.CloseJob(
- options: new CloseJobOptions(
- jobId: job.Value.Id,
- assignmentId: acceptJobOfferResult.Value.AssignmentId)
- {
- Note = $"Job has been closed by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
- });
- Console.WriteLine($"Job has been successfully closed: {closeJob.Status == 200}");
+ //Response closeJob = routerClient.CloseJob(
+ // options: new CloseJobOptions(
+ // jobId: job.Value.Id,
+ // assignmentId: acceptJobOfferResult.Value.AssignmentId)
+ // {
+ // Note = $"Job has been closed by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
+ // });
+ //Console.WriteLine($"Job has been successfully closed: {closeJob.Status == 200}");
- updatedJob = routerClient.GetJob(job.Value.Id);
- Console.WriteLine($"Updated job status: {updatedJob.Value.Status == RouterJobStatus.Closed}");
+ //updatedJob = routerClient.GetJob(job.Value.Id);
+ //Console.WriteLine($"Updated job status: {updatedJob.Value.Status == RouterJobStatus.Closed}");
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_CloseJob
#region Snippet:Azure_Communication_JobRouter_Tests_Samples_CloseJobInFuture
- // Optionally, a job can also be set up to be marked as closed in the future.
- var closeJobInFuture = routerClient.CloseJob(
- options: new CloseJobOptions(job.Value.Id, acceptJobOfferResult.Value.AssignmentId)
- {
- CloseAt = DateTimeOffset.UtcNow.AddSeconds(2), // this will mark the job as closed after 2 seconds
- Note = $"Job has been marked to close in the future by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
- });
- Console.WriteLine($"Job has been marked to close: {closeJob.Status == 202}"); // You'll received a 202 in that case
+ //// Optionally, a job can also be set up to be marked as closed in the future.
+ //var closeJobInFuture = routerClient.CloseJob(
+ // options: new CloseJobOptions(job.Value.Id, acceptJobOfferResult.Value.AssignmentId)
+ // {
+ // CloseAt = DateTimeOffset.UtcNow.AddSeconds(2), // this will mark the job as closed after 2 seconds
+ // Note = $"Job has been marked to close in the future by {worker.Value.Id} at {DateTimeOffset.UtcNow}"
+ // });
+ //Console.WriteLine($"Job has been marked to close: {closeJob.Status == 202}"); // You'll received a 202 in that case
- Thread.Sleep(TimeSpan.FromSeconds(2));
+ //Thread.Sleep(TimeSpan.FromSeconds(2));
- updatedJob = routerClient.GetJob(job.Value.Id);
- Console.WriteLine($"Updated job status: {updatedJob.Value.Status == RouterJobStatus.Closed}");
+ //updatedJob = routerClient.GetJob(job.Value.Id);
+ //Console.WriteLine($"Updated job status: {updatedJob.Value.Status == RouterJobStatus.Closed}");
#endregion Snippet:Azure_Communication_JobRouter_Tests_Samples_CloseJobInFuture
}
diff --git a/sdk/communication/Azure.Communication.sln b/sdk/communication/Azure.Communication.sln
index d44b404a05ae..fb4588125723 100644
--- a/sdk/communication/Azure.Communication.sln
+++ b/sdk/communication/Azure.Communication.sln
@@ -68,6 +68,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Communication.CallAut
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Communication.CallAutomation.Tests", "Azure.Communication.CallAutomation\tests\Azure.Communication.CallAutomation.Tests.csproj", "{8E628698-3ECB-4FA0-BDDF-62E018FDA0CA}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\core\Azure.Core\src\Azure.Core.csproj", "{AA4EFA41-28D0-4605-97A1-615A0DC82562}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -190,6 +192,10 @@ Global
{8E628698-3ECB-4FA0-BDDF-62E018FDA0CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8E628698-3ECB-4FA0-BDDF-62E018FDA0CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8E628698-3ECB-4FA0-BDDF-62E018FDA0CA}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AA4EFA41-28D0-4605-97A1-615A0DC82562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AA4EFA41-28D0-4605-97A1-615A0DC82562}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AA4EFA41-28D0-4605-97A1-615A0DC82562}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AA4EFA41-28D0-4605-97A1-615A0DC82562}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/sdk/core/Azure.Core/src/Azure.Core.csproj b/sdk/core/Azure.Core/src/Azure.Core.csproj
index f1b330f3c871..6f3c6aa98178 100644
--- a/sdk/core/Azure.Core/src/Azure.Core.csproj
+++ b/sdk/core/Azure.Core/src/Azure.Core.csproj
@@ -58,6 +58,10 @@
+
+
+
+
@@ -68,6 +72,9 @@
+
+
+
diff --git a/sdk/core/Azure.Core/src/DynamicData/MutableJsonDocument.cs b/sdk/core/Azure.Core/src/DynamicData/MutableJsonDocument.cs
index d4c508161185..ed5ed38af20a 100644
--- a/sdk/core/Azure.Core/src/DynamicData/MutableJsonDocument.cs
+++ b/sdk/core/Azure.Core/src/DynamicData/MutableJsonDocument.cs
@@ -7,6 +7,7 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Azure.Core.Serialization;
namespace Azure.Core.Json
{
diff --git a/sdk/core/Azure.Core/src/Serialization/ModelSerializerFormat.cs b/sdk/core/Azure.Core/src/Serialization/ModelSerializerFormat.cs
index 7a4b8a7ce7f8..d5d0165b1663 100644
--- a/sdk/core/Azure.Core/src/Serialization/ModelSerializerFormat.cs
+++ b/sdk/core/Azure.Core/src/Serialization/ModelSerializerFormat.cs
@@ -13,6 +13,7 @@ namespace Azure.Core.Serialization
{
internal const string JsonValue = "J";
internal const string WireValue = "W";
+ internal const string JsonMergePatchValue = "P";
///
/// Default format which will serialize all properties including read-only and additional properties.
@@ -30,6 +31,16 @@ namespace Azure.Core.Serialization
///
public static readonly ModelSerializerFormat Wire = new ModelSerializerFormat(WireValue);
+ ///
+ /// Format used to serialize this model when sending as a request to an Azure service.
+ /// It may not serialize read-only properties or additional properties.
+ /// The content-type will vary between JSON, XML, etc., depending on the service.
+ ///
+ /// Most use cases should prefer a more complete format like that includes
+ /// read-only and additional properties.
+ ///
+ public static readonly ModelSerializerFormat JsonMergePatch = new ModelSerializerFormat(JsonMergePatchValue);
+
private readonly string _value;
///
diff --git a/sdk/core/Azure.Core/src/Shared/BitVector128.cs b/sdk/core/Azure.Core/src/Shared/BitVector128.cs
new file mode 100644
index 000000000000..89ef6fb0f6ef
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/BitVector128.cs
@@ -0,0 +1,43 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Azure.Core
+{
+ internal struct BitVector128
+ {
+ private ulong _bits0;
+ private ulong _bits1;
+
+ public bool this[int i]
+ {
+ readonly get
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ ulong bit = (index == 0 ? _bits0 : _bits1) & mask;
+ return bit == mask;
+ }
+ set
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ if (index == 0)
+ {
+ _bits0 |= mask;
+ }
+ else
+ {
+ _bits1 |= mask;
+ }
+ }
+ }
+
+ public readonly bool IsNonzero() => (_bits0 > 0) || (_bits1 > 0);
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Shared/BitVector64.cs b/sdk/core/Azure.Core/src/Shared/BitVector64.cs
new file mode 100644
index 000000000000..de5b5aced822
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/BitVector64.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Azure.Core
+{
+ internal struct BitVector64
+ {
+ private ulong _bits;
+
+ public bool this[int i]
+ {
+ get
+ {
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ ulong bit = _bits & mask;
+ return bit == mask;
+ }
+ set
+ {
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ _bits |= mask;
+ }
+ }
+
+ public bool IsNonzero() => _bits > 0;
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Shared/BitVector640.cs b/sdk/core/Azure.Core/src/Shared/BitVector640.cs
new file mode 100644
index 000000000000..24b4b80b702e
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/BitVector640.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+
+namespace Azure.Core
+{
+ internal struct BitVector640
+ {
+ private ulong _bits0;
+ private ulong _bits1;
+ private ulong _bits2;
+ private ulong _bits3;
+ private ulong _bits4;
+ private ulong _bits5;
+ private ulong _bits6;
+ private ulong _bits7;
+ private ulong _bits8;
+ private ulong _bits9;
+
+ public bool this[int i]
+ {
+ readonly get
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ ulong bit = Get(index) & mask;
+ return bit == mask;
+ }
+ set
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ Set(index, mask);
+ }
+ }
+
+ public readonly bool IsNonzero() =>
+ _bits0 > 0 ||
+ _bits1 > 0 ||
+ _bits2 > 0 ||
+ _bits3 > 0 ||
+ _bits4 > 0 ||
+ _bits5 > 0 ||
+ _bits6 > 0 ||
+ _bits7 > 0 ||
+ _bits8 > 0 ||
+ _bits9 > 0;
+
+ private readonly ulong Get(int index)
+ {
+ return index switch
+ {
+ 0 => _bits0,
+ 1 => _bits1,
+ 2 => _bits2,
+ 3 => _bits3,
+ 4 => _bits4,
+ 5 => _bits5,
+ 6 => _bits6,
+ 7 => _bits7,
+ 8 => _bits8,
+ 9 => _bits9,
+ _ => throw new InvalidOperationException(),
+ };
+ }
+
+ private void Set(int index, ulong mask)
+ {
+ switch (index)
+ {
+ case 0:
+ _bits0 |= mask;
+ break;
+ case 1:
+ _bits1 |= mask;
+ break;
+ case 2:
+ _bits2 |= mask;
+ break;
+ case 3:
+ _bits3 |= mask;
+ break;
+ case 4:
+ _bits4 |= mask;
+ break;
+ case 5:
+ _bits5 |= mask;
+ break;
+ case 6:
+ _bits6 |= mask;
+ break;
+ case 7:
+ _bits7 |= mask;
+ break;
+ case 8:
+ _bits8 |= mask;
+ break;
+ case 9:
+ _bits9 |= mask;
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Shared/BitVectorHeapBacked.cs b/sdk/core/Azure.Core/src/Shared/BitVectorHeapBacked.cs
new file mode 100644
index 000000000000..27cca6cd0b98
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/BitVectorHeapBacked.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Azure.Core
+{
+ internal readonly struct BitVectorHeapBacked
+ {
+ private readonly ulong[] _bits;
+
+ public BitVectorHeapBacked(int size)
+ {
+ _bits = new ulong[(size >> 6) + 1];
+ }
+
+ public bool this[int i]
+ {
+ get
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ ulong bit = _bits[index] & mask;
+ return bit == mask;
+ }
+ set
+ {
+ int index = i >> 6;
+ int mod = i & 0b111111;
+ ulong mask = 1ul << mod;
+ _bits[index] |= mask;
+ }
+ }
+
+ public bool IsNonzero()
+ {
+ foreach (ulong value in _bits)
+ {
+ if (value > 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.Serialization.cs b/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.Serialization.cs
new file mode 100644
index 000000000000..08ab4ca5689b
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.Serialization.cs
@@ -0,0 +1,112 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace Azure.Core.Serialization
+{
+ //internal partial class MergePatchDictionary : IModelJsonSerializable>
+ //{
+ // #region Serialize
+ // void IModelJsonSerializable>.Serialize(Utf8JsonWriter writer, ModelSerializerOptions options)
+ // {
+ // ModelSerializerHelper.ValidatePatchFormat(this, options.Format);
+
+ // if (options.Format.ToString() == ModelSerializerFormat.Json ||
+ // options.Format.ToString() == ModelSerializerFormat.Wire)
+ // {
+ // SerializeFull(writer);
+ // }
+ // else if (options.Format.ToString() == ModelSerializerFormat.JsonMergePatch)
+ // {
+ // SerializePatch(writer);
+ // }
+ // }
+
+ // BinaryData IModelSerializable>.Serialize(ModelSerializerOptions options)
+ // {
+ // ModelSerializerHelper.ValidatePatchFormat(this, options.Format);
+ // return ModelSerializer.SerializeCore(this, options);
+ // }
+
+ // private void SerializeFull(Utf8JsonWriter writer)
+ // {
+ // writer.WriteStartObject();
+
+ // foreach (KeyValuePair kvp in _dictionary)
+ // {
+ // if (kvp.Value == null)
+ // {
+ // writer.WritePropertyName(kvp.Key);
+ // writer.WriteNullValue();
+ // }
+ // else
+ // {
+ // writer.WritePropertyName(kvp.Key);
+ // _serializeItem(writer, kvp.Value);
+ // }
+ // }
+
+ // writer.WriteEndObject();
+ // }
+
+ // private void SerializePatch(Utf8JsonWriter writer)
+ // {
+ // writer.WriteStartObject();
+
+ // foreach (KeyValuePair kvp in _changed)
+ // {
+ // if (kvp.Value)
+ // {
+ // if (!_dictionary.TryGetValue(kvp.Key, out T? value) || value == null)
+ // {
+ // writer.WritePropertyName(kvp.Key);
+ // writer.WriteNullValue();
+ // }
+ // else if (_itemHasChanges == null || _itemHasChanges(value))
+ // {
+ // writer.WritePropertyName(kvp.Key);
+ // _serializeItem(writer, value);
+ // }
+ // }
+ // }
+
+ // writer.WriteEndObject();
+ // }
+ // #endregion
+
+ // #region Deserialize
+
+ // public static MergePatchDictionary Deserialize(JsonElement element,
+ // Func deserializeItem,
+ // Action serializeItem,
+ // Func? itemHasChanges = default)
+ // {
+ // return new MergePatchDictionary(element, deserializeItem, serializeItem, itemHasChanges);
+ // }
+
+ // MergePatchDictionary IModelJsonSerializable>.Deserialize(ref Utf8JsonReader reader, ModelSerializerOptions options)
+ // {
+ // using JsonDocument doc = JsonDocument.ParseValue(ref reader);
+ // return Deserialize(doc.RootElement,
+ // _deserializeItem,
+ // _serializeItem,
+ // _itemHasChanges);
+ // }
+
+ // MergePatchDictionary IModelSerializable>.Deserialize(BinaryData data, ModelSerializerOptions options)
+ // {
+ // using JsonDocument doc = JsonDocument.Parse(data);
+ // return Deserialize(doc.RootElement,
+ // _deserializeItem,
+ // _serializeItem,
+ // _itemHasChanges);
+ // }
+
+ // #endregion
+ //}
+}
diff --git a/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.cs b/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.cs
new file mode 100644
index 000000000000..6aa5116cbd25
--- /dev/null
+++ b/sdk/core/Azure.Core/src/Shared/MergePatchDictionary.cs
@@ -0,0 +1,216 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace Azure.Core.Serialization
+{
+ //internal partial class MergePatchDictionary //: IDictionary
+ //{
+ // public static MergePatchDictionary GetStringDictionary()
+ // => new(e => e.GetString(), (w, s) => w.WriteStringValue(s), default);
+
+ // private readonly MergePatchChanges _changes;
+ // private readonly Dictionary _indexes;
+ // private readonly T?[] _values;
+
+ // private readonly Func _deserializeItem;
+ // private readonly Action _serializeItem;
+ // private readonly Func? _itemHasChanges;
+
+ // private bool _checkChanges;
+ // private bool _hasChanges;
+
+ // public MergePatchDictionary(
+ // Func deserializeItem,
+ // Action serializeItem,
+ // Func? hasChanges = default)
+ // {
+ // // TODO: How to size it?
+ // _changes = new(100);
+ // _indexes = new Dictionary();
+ // _values = new T?[100];
+
+ // _deserializeItem = deserializeItem;
+ // _serializeItem = serializeItem;
+ // _itemHasChanges = hasChanges;
+ // }
+
+ // ///
+ // /// Deserialization constructor
+ // ///
+ // internal MergePatchDictionary(
+ // JsonElement element,
+ // Func deserializeItem,
+ // Action serializeItem,
+ // Func? hasChanges = default)
+ // {
+ // // TODO: How to size it?
+ // _changes = new(100);
+ // _indexes = new Dictionary();
+ // _values = new T?[100];
+
+ // _deserializeItem = deserializeItem;
+ // _serializeItem = serializeItem;
+ // _itemHasChanges = hasChanges;
+
+ // // Deserialize the values from the JsonElement
+ // foreach (JsonProperty property in element.EnumerateObject())
+ // {
+ // Add(property.Name, deserializeItem(property.Value));
+ // }
+ // }
+
+ // public bool HasChanges => _hasChanges || ItemsChanged();
+
+ // private bool ItemsChanged()
+ // {
+ // if (!_checkChanges)
+ // {
+ // return false;
+ // }
+
+ // foreach (KeyValuePair item in _indexes)
+ // {
+ // bool mightHaveChanged = _changes.HasChanged(item.Value);
+ // if (mightHaveChanged)
+ // {
+ // T? value = _values[item.Value];
+ // if (_itemHasChanges != null && _itemHasChanges(value))
+ // {
+ // return true;
+ // }
+ // }
+ // }
+
+ // return false;
+ // }
+
+ // public T? this[string key]
+ // {
+ // get
+ // {
+ // // If the value is read and a reference value, it might get changed
+ // _checkChanges = _itemHasChanges != null;
+ // if (_itemHasChanges != null)
+ // {
+ // _changes.SetChanged(_indexes[key]);
+ // }
+ // return _values[_indexes[key]];
+ // }
+
+ // set
+ // {
+ // _hasChanges = true;
+ // if (_indexes.TryGetValue(key, out int index))
+ // {
+ // _changes.SetChanged(index);
+ // _values[index] = value;
+ // }
+ // else
+ // {
+ // Add(key, value);
+ // }
+ // }
+ // }
+
+ // ICollection IDictionary.Keys => _indexes.Keys;
+
+ // //ICollection IDictionary.Values => _dictionary.Values;
+
+ // //int ICollection>.Count => _indexes.Count;
+
+ // //bool ICollection>.IsReadOnly => false;
+
+ // //public void Add(string key, T? value)
+ // //{
+ // // int index = GetNextIndex();
+ // // _indexes[key] = index;
+ // // _values[index] = value;
+ // // _hasChanges = true;
+ // // _changes.SetChanged(index);
+ // //}
+
+ // //private int GetNextIndex()
+ // //{
+ // // // This will handle Resize() as well.
+ // // // See example here: https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Collections/Generic/Dictionary.cs#L848
+ // // throw new NotImplementedException();
+ // //}
+
+ // //void ICollection>.Add(KeyValuePair item)
+ // //{
+ // // int index = GetNextIndex();
+ // // _indexes[item.Key] = index;
+ // // _values[index] = item.Value;
+ // // _hasChanges = true;
+ // // _changes.SetChanged(index);
+ // //}
+
+ // //void ICollection>.Clear()
+ // //{
+ // // // TODO: this raises the question of how we track changes for deleted elements.
+ // // // Solve it here.
+
+ // // _hasChanges = true;
+ // // foreach (int index in _indexes.Values)
+ // // {
+ // // _changes.SetChanged(index);
+ // // }
+
+ // // (_dictionary as ICollection>).Clear();
+ // //}
+
+ // //bool ICollection>.Contains(KeyValuePair item) =>
+ // // (_dictionary as ICollection>).Contains(item);
+
+ // //bool IDictionary.ContainsKey(string key)
+ // // => _dictionary.ContainsKey(key);
+
+ // //void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
+ // // => (_dictionary as ICollection>).CopyTo(array, arrayIndex);
+
+ // //IEnumerator> IEnumerable>.GetEnumerator()
+ // //{
+ // // // TODO: handle item changes
+ // // return _dictionary.GetEnumerator();
+ // //}
+
+ // //IEnumerator IEnumerable.GetEnumerator()
+ // //{
+ // // // TODO: handle item changes
+ // // return (_dictionary as IEnumerable).GetEnumerator();
+ // //}
+
+ // //bool IDictionary.Remove(string key)
+ // //{
+ // // _hasChanges = true;
+ // // _changed[key] = true;
+ // // return _dictionary.Remove(key);
+ // //}
+
+ // //bool ICollection>.Remove(KeyValuePair item)
+ // //{
+ // // _hasChanges = true;
+ // // _changed[item.Key] = true;
+ // // return (_dictionary as ICollection>).Remove(item);
+ // //}
+
+ // //public bool TryGetValue(string key, out T? value)
+ // //{
+ // // if (_indexes.TryGetValue(key, out int index))
+ // // {
+ // // _checkChanges = _itemHasChanges != null;
+ // // _changed[key] = _itemHasChanges != null;
+ // // return true;
+ // // }
+
+ // // return false;
+ // //}
+ //}
+}
diff --git a/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs b/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs
index 45481db3c91a..47e790f59a74 100644
--- a/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs
+++ b/sdk/core/Azure.Core/src/Shared/ModelSerializerHelper.cs
@@ -22,5 +22,15 @@ public static void ValidateFormat(IModelSerializable model, ModelSerialize
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ValidateFormat(IModelSerializable