diff --git a/Microsoft.Azure.Cosmos/src/CosmosClient.cs b/Microsoft.Azure.Cosmos/src/CosmosClient.cs index 07fb7ea22e..4770d36398 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClient.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClient.cs @@ -150,7 +150,7 @@ protected CosmosClient() /// /// public CosmosClient( - string connectionString, + string connectionString, CosmosClientOptions clientOptions = null) : this( CosmosClientOptions.GetAccountEndpoint(connectionString), @@ -612,18 +612,19 @@ internal void Init( this.RequestHandler = clientPipelineBuilder.Build(); + CosmosSerializer userSerializer = this.ClientOptions.GetCosmosSerializerWithWrapperOrDefault(); this.ResponseFactory = new CosmosResponseFactory( defaultJsonSerializer: this.ClientOptions.PropertiesSerializer, - userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault); + userJsonSerializer: userSerializer); CosmosSerializer sqlQuerySpecSerializer = CosmosSqlQuerySpecJsonConverter.CreateSqlQuerySpecSerializer( - this.ClientOptions.CosmosSerializerWithWrapperOrDefault, - this.ClientOptions.PropertiesSerializer); + cosmosSerializer: userSerializer, + propertiesSerializer: this.ClientOptions.PropertiesSerializer); this.ClientContext = new ClientContextCore( client: this, clientOptions: this.ClientOptions, - userJsonSerializer: this.ClientOptions.CosmosSerializerWithWrapperOrDefault, + userJsonSerializer: userSerializer, defaultJsonSerializer: this.ClientOptions.PropertiesSerializer, sqlQuerySpecSerializer: sqlQuerySpecSerializer, cosmosResponseFactory: this.ResponseFactory, @@ -632,7 +633,7 @@ internal void Init( documentQueryClient: new DocumentQueryClient(this.DocumentClient)); } - internal async virtual Task GetAccountConsistencyLevelAsync() + internal virtual async Task GetAccountConsistencyLevelAsync() { if (!this.accountConsistencyLevel.HasValue) { diff --git a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs index 5f15d74769..c3a7afd2db 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosClientOptions.cs @@ -8,7 +8,6 @@ namespace Microsoft.Azure.Cosmos using System.Collections.ObjectModel; using System.Data.Common; using System.Linq; - using System.Runtime.ConstrainedExecution; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Client; @@ -17,6 +16,18 @@ namespace Microsoft.Azure.Cosmos /// /// Defines all the configurable options that the CosmosClient requires. /// + /// + /// An example on how to configure the serialization option to ignore null values + /// CosmosClientOptions clientOptions = new CosmosClientOptions() + /// { + /// SerializerOptions = new CosmosSerializationOptions(){ + /// IgnoreNullValues = true + /// }, + /// ConnectionMode = ConnectionMode.Gateway, + /// }; + /// + /// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions); + /// public class CosmosClientOptions { /// @@ -39,11 +50,12 @@ public class CosmosClientOptions /// private static readonly CosmosSerializer propertiesSerializer = new CosmosJsonSerializerWrapper(new CosmosJsonDotNetSerializer()); - private readonly Collection customHandlers; private readonly string currentEnvironmentInformation; private int gatewayModeMaxConnectionLimit; private string applicationName; + private CosmosSerializationOptions serializerOptions; + private CosmosSerializer serializer; /// /// Creates a new CosmosClientOptions @@ -59,7 +71,7 @@ public CosmosClientOptions() this.ConnectionMode = CosmosClientOptions.DefaultConnectionMode; this.ConnectionProtocol = CosmosClientOptions.DefaultProtocol; this.ApiType = CosmosClientOptions.DefaultApiType; - this.customHandlers = new Collection(); + this.CustomHandlers = new Collection(); } /// @@ -132,10 +144,7 @@ public int GatewayModeMaxConnectionLimit /// /// [JsonConverter(typeof(ClientOptionJsonConverter))] - public Collection CustomHandlers - { - get => this.customHandlers; - } + public Collection CustomHandlers { get; } /// /// Get or set the connection mode used by the client when connecting to the Azure Cosmos DB service. @@ -157,7 +166,7 @@ public Collection CustomHandlers public ConsistencyLevel? ConsistencyLevel { get; set; } /// - /// Get ot set the number of times client should retry on rate throttled requests. + /// Get or set the number of times client should retry on rate throttled requests. /// /// public int? MaxRetryAttemptsOnRateLimitedRequests { get; set; } @@ -171,6 +180,35 @@ public Collection CustomHandlers /// public TimeSpan? MaxRetryWaitTimeOnRateLimitedRequests { get; set; } + /// + /// Get to set optional serializer options. + /// + /// + /// An example on how to configure the serialization option to ignore null values + /// CosmosClientOptions clientOptions = new CosmosClientOptions() + /// { + /// SerializerOptions = new CosmosSerializationOptions(){ + /// IgnoreNullValues = true + /// } + /// }; + /// + /// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions); + /// + public CosmosSerializationOptions SerializerOptions + { + get => this.serializerOptions; + set + { + if (this.Serializer != null) + { + throw new ArgumentException( + $"{nameof(this.SerializerOptions)} is not compatible with {nameof(this.Serializer)}. Only one can be set. "); + } + + this.serializerOptions = value; + } + } + /// /// Get to set an optional JSON serializer. The client will use it to serialize or de-serialize user's cosmos request/responses. /// SDK owned types such as DatabaseProperties and ContainerProperties will always use the SDK default serializer. @@ -182,10 +220,7 @@ public Collection CustomHandlers /// /// // An example on how to configure the serializer to ignore null values /// CosmosSerializer ignoreNullSerializer = new CosmosJsonDotNetSerializer( - /// new JsonSerializerSettings() - /// { - /// NullValueHandling = NullValueHandling.Ignore - /// }); + /// NullValueHandling = NullValueHandling.Ignore); /// /// CosmosClientOptions clientOptions = new CosmosClientOptions() /// { @@ -195,7 +230,20 @@ public Collection CustomHandlers /// CosmosClient client = new CosmosClient("endpoint", "key", clientOptions); /// [JsonConverter(typeof(ClientOptionJsonConverter))] - public CosmosSerializer Serializer { get; set; } + public CosmosSerializer Serializer + { + get => this.serializer; + set + { + if (this.SerializerOptions != null) + { + throw new ArgumentException( + $"{nameof(this.Serializer)} is not compatible with {nameof(this.SerializerOptions)}. Only one can be set. "); + } + + this.serializer = value; + } + } /// /// A JSON serializer used by the CosmosClient to serialize or de-serialize cosmos request/responses. @@ -205,12 +253,6 @@ public Collection CustomHandlers [JsonConverter(typeof(ClientOptionJsonConverter))] internal CosmosSerializer PropertiesSerializer => CosmosClientOptions.propertiesSerializer; - /// - /// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default - /// - [JsonIgnore] - internal CosmosSerializer CosmosSerializerWithWrapperOrDefault => this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer); - /// /// Gets or sets the connection protocol when connecting to the Azure Cosmos service. /// @@ -309,6 +351,22 @@ public Collection CustomHandlers /// internal bool? EnableCpuMonitor { get; set; } + /// + /// Gets the user json serializer with the CosmosJsonSerializerWrapper or the default + /// + internal CosmosSerializer GetCosmosSerializerWithWrapperOrDefault() + { + if (this.SerializerOptions != null) + { + CosmosJsonDotNetSerializer cosmosJsonDotNetSerializer = new CosmosJsonDotNetSerializer(this.SerializerOptions); + return new CosmosJsonSerializerWrapper(cosmosJsonDotNetSerializer); + } + else + { + return this.Serializer == null ? this.PropertiesSerializer : new CosmosJsonSerializerWrapper(this.Serializer); + } + } + internal CosmosClientOptions Clone() { CosmosClientOptions cloneConfiguration = (CosmosClientOptions)this.MemberwiseClone(); diff --git a/Microsoft.Azure.Cosmos/src/CosmosElements/CosmosElementSerializer.cs b/Microsoft.Azure.Cosmos/src/CosmosElements/CosmosElementSerializer.cs index 72bd457455..ba921dd142 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosElements/CosmosElementSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/CosmosElements/CosmosElementSerializer.cs @@ -29,7 +29,7 @@ static class CosmosElementSerializer internal static CosmosArray ToCosmosElements( MemoryStream memoryStream, ResourceType resourceType, - CosmosSerializationOptions cosmosSerializationOptions = null) + CosmosSerializationFormatOptions cosmosSerializationOptions = null) { if (!memoryStream.CanRead) { @@ -103,7 +103,7 @@ internal static Stream ToStream( string containerRid, IEnumerable cosmosElements, ResourceType resourceType, - CosmosSerializationOptions cosmosSerializationOptions = null) + CosmosSerializationFormatOptions cosmosSerializationOptions = null) { IJsonWriter jsonWriter; if (cosmosSerializationOptions != null) @@ -172,7 +172,7 @@ internal static IEnumerable Deserialize( IEnumerable cosmosElements, ResourceType resourceType, CosmosSerializer jsonSerializer, - CosmosSerializationOptions cosmosSerializationOptions = null) + CosmosSerializationFormatOptions cosmosSerializationOptions = null) { if (!cosmosElements.Any()) { diff --git a/Microsoft.Azure.Cosmos/src/FeedOptions.cs b/Microsoft.Azure.Cosmos/src/FeedOptions.cs index fa9f9dbb62..6552360e5f 100644 Binary files a/Microsoft.Azure.Cosmos/src/FeedOptions.cs and b/Microsoft.Azure.Cosmos/src/FeedOptions.cs differ diff --git a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs index c5e5b32211..5a0bf04685 100644 --- a/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs +++ b/Microsoft.Azure.Cosmos/src/Fluent/CosmosClientBuilder.cs @@ -255,6 +255,19 @@ public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeO return this; } + /// + /// Set a custom serializer option. + /// + /// The custom class that implements + /// The object + /// + /// + public CosmosClientBuilder WithSerializerOptions(CosmosSerializationOptions cosmosSerializerOptions) + { + this.clientOptions.SerializerOptions = cosmosSerializerOptions; + return this; + } + /// /// Set a custom JSON serializer. /// @@ -262,8 +275,7 @@ public CosmosClientBuilder WithThrottlingRetryOptions(TimeSpan maxRetryWaitTimeO /// The object /// /// - public CosmosClientBuilder WithCustomSerializer( - CosmosSerializer cosmosJsonSerializer) + public CosmosClientBuilder WithCustomSerializer(CosmosSerializer cosmosJsonSerializer) { this.clientOptions.Serializer = cosmosJsonSerializer; return this; diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs index 43110d3519..ca53dac640 100644 --- a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs +++ b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs @@ -140,7 +140,7 @@ public ConsistencyLevel? ConsistencyLevel /// internal string SessionToken { get; set; } - internal CosmosSerializationOptions CosmosSerializationOptions { get; set; } + internal CosmosSerializationFormatOptions CosmosSerializationOptions { get; set; } /// /// Gets or sets the flag that enables skip take across partitions. diff --git a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/QueryResponse.cs b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/QueryResponse.cs index c581a1107e..cd761d3684 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/QueryResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/QueryResponses/QueryResponse.cs @@ -72,7 +72,7 @@ private QueryResponse( /// internal ClientSideRequestStatistics RequestStatistics { get; } - internal virtual CosmosSerializationOptions CosmosSerializationOptions { get; set; } + internal virtual CosmosSerializationFormatOptions CosmosSerializationOptions { get; set; } internal bool GetHasMoreResults() { @@ -137,7 +137,7 @@ internal class QueryResponse : FeedResponse { private readonly IEnumerable cosmosElements; private readonly CosmosSerializer jsonSerializer; - private readonly CosmosSerializationOptions serializationOptions; + private readonly CosmosSerializationFormatOptions serializationOptions; private IEnumerable resources; private QueryResponse( @@ -145,7 +145,7 @@ private QueryResponse( IEnumerable cosmosElements, CosmosQueryResponseMessageHeaders responseMessageHeaders, CosmosSerializer jsonSerializer, - CosmosSerializationOptions serializationOptions) + CosmosSerializationFormatOptions serializationOptions) { this.cosmosElements = cosmosElements; this.QueryHeaders = responseMessageHeaders; diff --git a/Microsoft.Azure.Cosmos/src/CosmosJsonDotNetSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs similarity index 58% rename from Microsoft.Azure.Cosmos/src/CosmosJsonDotNetSerializer.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs index 12aee89848..a0e0187a85 100644 --- a/Microsoft.Azure.Cosmos/src/CosmosJsonDotNetSerializer.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonDotNetSerializer.cs @@ -7,11 +7,12 @@ namespace Microsoft.Azure.Cosmos using System.IO; using System.Text; using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; /// - /// The default Cosmos JSON.NET serializer + /// The default Cosmos JSON.NET serializer. /// - public sealed class CosmosJsonDotNetSerializer : CosmosSerializer + internal sealed class CosmosJsonDotNetSerializer : CosmosSerializer { private static readonly Encoding DefaultEncoding = new UTF8Encoding(false, true); private readonly JsonSerializer Serializer; @@ -19,20 +20,48 @@ public sealed class CosmosJsonDotNetSerializer : CosmosSerializer /// /// Create a serializer that uses the JSON.net serializer /// - /// Optional serializer settings - public CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSettings = null) + /// + /// This is internal to reduce exposure of JSON.net types so + /// it is easier to convert to System.Text.Json + /// + internal CosmosJsonDotNetSerializer() { - if (jsonSerializerSettings == null) + this.Serializer = JsonSerializer.Create(); + } + + /// + /// Create a serializer that uses the JSON.net serializer + /// + /// + /// This is internal to reduce exposure of JSON.net types so + /// it is easier to convert to System.Text.Json + /// + internal CosmosJsonDotNetSerializer(CosmosSerializationOptions cosmosSerializerOptions) + { + JsonSerializerSettings jsonSerializerSettings = new JsonSerializerSettings() { - jsonSerializerSettings = new JsonSerializerSettings() - { - NullValueHandling = NullValueHandling.Include - }; - } + NullValueHandling = cosmosSerializerOptions.IgnoreNullValues ? NullValueHandling.Ignore : NullValueHandling.Include, + Formatting = cosmosSerializerOptions.Indented ? Formatting.Indented : Formatting.None, + ContractResolver = cosmosSerializerOptions.PropertyNamingPolicy == CosmosPropertyNamingPolicy.CamelCase + ? new CamelCasePropertyNamesContractResolver() + : null + }; this.Serializer = JsonSerializer.Create(jsonSerializerSettings); } + /// + /// Create a serializer that uses the JSON.net serializer + /// + /// + /// This is internal to reduce exposure of JSON.net types so + /// it is easier to convert to System.Text.Json + /// + internal CosmosJsonDotNetSerializer(JsonSerializerSettings jsonSerializerSettings) + { + this.Serializer = JsonSerializer.Create(jsonSerializerSettings); + } + /// /// Convert a Stream to the passed in type. /// @@ -45,7 +74,7 @@ public override T FromStream(Stream stream) { if (typeof(Stream).IsAssignableFrom(typeof(T))) { - return (T)(object)(stream); + return (T)(object)stream; } using (StreamReader sr = new StreamReader(stream)) diff --git a/Microsoft.Azure.Cosmos/src/CosmosJsonSerializerWrapper.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonSerializerWrapper.cs similarity index 100% rename from Microsoft.Azure.Cosmos/src/CosmosJsonSerializerWrapper.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosJsonSerializerWrapper.cs diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosPropertyNamingPolicy.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosPropertyNamingPolicy.cs new file mode 100644 index 0000000000..ead587fa71 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosPropertyNamingPolicy.cs @@ -0,0 +1,23 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// Determines the naming policy used to convert a string-based name to another format, such as a camel-casing where the first letter is lower case. + /// + public enum CosmosPropertyNamingPolicy + { + /// + /// No custom naming policy. + /// The property name will be the same as the source. + /// + Default = 0, + + /// + /// First letter in the property name is lower case. + /// + CamelCase = 1, + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosSerializationOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationFormatOptions.cs similarity index 94% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosSerializationOptions.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationFormatOptions.cs index 76c4eeb6ca..a9e9ca74a6 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosSerializationOptions.cs +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationFormatOptions.cs @@ -6,7 +6,7 @@ namespace Microsoft.Azure.Cosmos using System; using Microsoft.Azure.Cosmos.Json; - internal sealed class CosmosSerializationOptions + internal sealed class CosmosSerializationFormatOptions { public delegate IJsonNavigator CreateCustomNavigator(byte[] content); @@ -27,7 +27,7 @@ internal sealed class CosmosSerializationOptions         /// public CreateCustomWriter CreateCustomWriterCallback { get; } - public CosmosSerializationOptions( + public CosmosSerializationFormatOptions( string contentSerializationFormat, CreateCustomNavigator createCustomNavigator, CreateCustomWriter createCustomWriter) diff --git a/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationOptions.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationOptions.cs new file mode 100644 index 0000000000..965d824012 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializationOptions.cs @@ -0,0 +1,49 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos +{ + /// + /// This class provides a way to configure basic + /// serializer settings. + /// + public sealed class CosmosSerializationOptions + { + /// + /// Create an instance of CosmosSerializationOptions + /// with the default values for the Cosmos SDK + /// + public CosmosSerializationOptions() + { + this.IgnoreNullValues = false; + this.Indented = false; + this.PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default; + } + + /// + /// Gets or sets if the serializer should ignore null properties + /// + /// + /// The default value is false + /// + public bool IgnoreNullValues { get; set; } + + /// + /// Gets or sets if the serializer should use indentation + /// + /// + /// The default value is false + /// + public bool Indented { get; set; } + + /// + /// Gets or sets whether the naming policy used to convert a string-based name to another format, + /// such as a camel-casing format. + /// + /// + /// The default value is CosmosPropertyNamingPolicy.Default + /// + public CosmosPropertyNamingPolicy PropertyNamingPolicy { get; set; } + } +} diff --git a/Microsoft.Azure.Cosmos/src/CosmosSerializer.cs b/Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs similarity index 100% rename from Microsoft.Azure.Cosmos/src/CosmosSerializer.cs rename to Microsoft.Azure.Cosmos/src/Serializer/CosmosSerializer.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs index 89db954e14..c67e3e1048 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemTests.cs @@ -1080,7 +1080,7 @@ public async Task ItemQueryStreamSerializationSetting() IList deleteList = await ToDoActivity.CreateRandomItems(this.Container, 101, randomPartitionKey: true); QueryDefinition sql = new QueryDefinition("SELECT * FROM toDoActivity t ORDER BY t.taskNum"); - CosmosSerializationOptions options = new CosmosSerializationOptions( + CosmosSerializationFormatOptions options = new CosmosSerializationFormatOptions( ContentSerializationFormat.CosmosBinary.ToString(), (content) => JsonNavigator.Create(content), () => JsonWriter.Create(JsonSerializationFormat.Binary)); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ContractEnforcement.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ContractEnforcement.cs index 492a836fec..9bf7b2c51f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ContractEnforcement.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ContractEnforcement.cs @@ -18,8 +18,8 @@ public class ContractEnforcement [TestMethod] public void ContractChanges() { - Assert.IsTrue( - ContractEnforcement.CheckBreakingChanges("Microsoft.Azure.Cosmos.Client", BaselinePath, BreakingChangesPath), + Assert.IsFalse( + ContractEnforcement.DoesContractContainBreakingChanges("Microsoft.Azure.Cosmos.Client", BaselinePath, BreakingChangesPath), $@"Public API has changed. If this is expected, then refresh {BaselinePath} with {Environment.NewLine} Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/testbaseline.cmd /update after this test is run locally. To see the differences run testbaselines.cmd /diff" ); } @@ -69,7 +69,8 @@ private static IEnumerable RemoveDebugSpecificAttributes(IE !x.AttributeType.Name.Contains("NonVersionableAttribute") && !x.AttributeType.Name.Contains("ReliabilityContractAttribute") && !x.AttributeType.Name.Contains("NonVersionableAttribute") && - !x.AttributeType.Name.Contains("DebuggerStepThroughAttribute") + !x.AttributeType.Name.Contains("DebuggerStepThroughAttribute") && + !x.AttributeType.Name.Contains("IsReadOnlyAttribute") ); } @@ -115,7 +116,7 @@ private static TypeTree BuildTypeTree(TypeTree root, Type[] types) return root; } - private static bool CheckBreakingChanges(string dllName, string baselinePath, string breakingChangesPath) + private static bool DoesContractContainBreakingChanges(string dllName, string baselinePath, string breakingChangesPath) { TypeTree locally = new TypeTree(typeof(object)); ContractEnforcement.BuildTypeTree(locally, ContractEnforcement.GetAssemblyLocally(dllName).GetExportedTypes()); @@ -126,28 +127,16 @@ private static bool CheckBreakingChanges(string dllName, string baselinePath, st File.WriteAllText($"{breakingChangesPath}", localJson); string baselineJson = JsonConvert.SerializeObject(baseline, Formatting.Indented); - for (int i=0; i < baselineJson.Length && i < localJson.Length; i++) + System.Diagnostics.Trace.TraceWarning($"String length Expected: {baselineJson.Length};Actual:{localJson.Length}"); + if (string.Equals(localJson, baselineJson, StringComparison.InvariantCulture)) { - if (baselineJson[i] != localJson[i]) - { - // First byte of diff, trace next 200 bytes if-exists - ContractEnforcement.TraceSubpartIfExists(baselineJson, 0, 200); - ContractEnforcement.TraceSubpartIfExists(localJson, 0, 200); - } - } - - return baselineJson == localJson; - } - - private static void TraceSubpartIfExists(string input, int position, int desiredLength) - { - if (position + desiredLength > input.Length) - { - System.Diagnostics.Trace.TraceWarning($"baseline: {input.Substring(position)}"); + return false; } else { - System.Diagnostics.Trace.TraceWarning($"baseline: {input.Substring(position, desiredLength)}"); + System.Diagnostics.Trace.TraceWarning($"Expected: {baselineJson}"); + System.Diagnostics.Trace.TraceWarning($"Actual: {localJson}"); + return true; } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs index e562f6f443..2cf8e442fb 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosClientOptionsUnitTests.cs @@ -5,8 +5,7 @@ namespace Microsoft.Azure.Cosmos.Tests { using System; - using System.Linq; - using System.Reflection; + using System.IO; using Microsoft.Azure.Cosmos.Client.Core.Tests; using Microsoft.Azure.Cosmos.Fluent; using Microsoft.Azure.Documents; @@ -34,6 +33,11 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() ApiType apiType = ApiType.Sql; int maxRetryAttemptsOnThrottledRequests = 9999; TimeSpan maxRetryWaitTime = TimeSpan.FromHours(6); + CosmosSerializationOptions cosmosSerializerOptions = new CosmosSerializationOptions() + { + IgnoreNullValues = true, + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase, + }; CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder( accountEndpoint: endpoint, @@ -53,6 +57,8 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() Assert.AreNotEqual(userAgentSuffix, clientOptions.ApplicationName); Assert.AreNotEqual(apiType, clientOptions.ApiType); Assert.AreEqual(0, clientOptions.CustomHandlers.Count); + Assert.IsNull(clientOptions.SerializerOptions); + Assert.IsNull(clientOptions.Serializer); //Verify GetConnectionPolicy returns the correct values for default ConnectionPolicy policy = clientOptions.GetConnectionPolicy(); @@ -67,7 +73,8 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() .WithApplicationName(userAgentSuffix) .AddCustomHandlers(preProcessHandler) .WithApiType(apiType) - .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests); + .WithThrottlingRetryOptions(maxRetryWaitTime, maxRetryAttemptsOnThrottledRequests) + .WithSerializerOptions(cosmosSerializerOptions); cosmosClient = cosmosClientBuilder.Build(new MockDocumentClient()); clientOptions = cosmosClient.ClientOptions; @@ -82,6 +89,9 @@ public void VerifyCosmosConfigurationPropertiesGetUpdated() Assert.AreEqual(apiType, clientOptions.ApiType); Assert.AreEqual(maxRetryAttemptsOnThrottledRequests, clientOptions.MaxRetryAttemptsOnRateLimitedRequests); Assert.AreEqual(maxRetryWaitTime, clientOptions.MaxRetryWaitTimeOnRateLimitedRequests); + Assert.AreEqual(cosmosSerializerOptions.IgnoreNullValues, clientOptions.SerializerOptions.IgnoreNullValues); + Assert.AreEqual(cosmosSerializerOptions.PropertyNamingPolicy, clientOptions.SerializerOptions.PropertyNamingPolicy); + Assert.AreEqual(cosmosSerializerOptions.Indented, clientOptions.SerializerOptions.Indented); //Verify GetConnectionPolicy returns the correct values policy = clientOptions.GetConnectionPolicy(); @@ -119,8 +129,8 @@ public void ThrowOnNullEndpoint() [TestMethod] public void UserAgentContainsEnvironmentInformation() { - var environmentInformation = new EnvironmentInformation(); - var expectedValue = environmentInformation.ToString(); + EnvironmentInformation environmentInformation = new EnvironmentInformation(); + string expectedValue = environmentInformation.ToString(); CosmosClientOptions cosmosClientOptions = new CosmosClientOptions(); string userAgentSuffix = "testSuffix"; cosmosClientOptions.ApplicationName = userAgentSuffix; @@ -133,6 +143,86 @@ public void UserAgentContainsEnvironmentInformation() Assert.IsTrue(connectionPolicy.UserAgentSuffix.Contains(expectedValue)); } + [TestMethod] + public void GetCosmosSerializerWithWrapperOrDefaultTest() + { + CosmosJsonDotNetSerializer serializer = new CosmosJsonDotNetSerializer(); + CosmosClientOptions options = new CosmosClientOptions() + { + Serializer = serializer + }; + + CosmosSerializer cosmosSerializer = options.GetCosmosSerializerWithWrapperOrDefault(); + Assert.AreNotEqual(cosmosSerializer, options.PropertiesSerializer, "Serializer should be custom not the default"); + Assert.AreNotEqual(cosmosSerializer, serializer, "Serializer should be in the CosmosJsonSerializerWrapper"); + + CosmosJsonSerializerWrapper cosmosJsonSerializerWrapper = cosmosSerializer as CosmosJsonSerializerWrapper; + Assert.IsNotNull(cosmosJsonSerializerWrapper); + Assert.AreEqual(cosmosJsonSerializerWrapper.InternalJsonSerializer, serializer); + } + + [TestMethod] + public void GetCosmosSerializerWithWrapperOrDefaultWithOptionsTest() + { + CosmosSerializationOptions serializerOptions = new CosmosSerializationOptions(); + Assert.IsFalse(serializerOptions.IgnoreNullValues); + Assert.IsFalse(serializerOptions.Indented); + Assert.AreEqual(CosmosPropertyNamingPolicy.Default, serializerOptions.PropertyNamingPolicy); + + CosmosClientOptions options = new CosmosClientOptions() + { + SerializerOptions = new CosmosSerializationOptions() + { + IgnoreNullValues = true, + Indented = true, + PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase + } + }; + + CosmosSerializer cosmosSerializer = options.GetCosmosSerializerWithWrapperOrDefault(); + Assert.AreNotEqual(cosmosSerializer, options.PropertiesSerializer, "Serializer should be custom not the default"); + + CosmosJsonSerializerWrapper cosmosJsonSerializerWrapper = cosmosSerializer as CosmosJsonSerializerWrapper; + Assert.IsNotNull(cosmosJsonSerializerWrapper); + + // Verify the custom settings are being honored + dynamic testItem = new { id = "testid", description = (string)null, CamelCaseProperty = "TestCamelCase" }; + using (Stream stream = cosmosSerializer.ToStream(testItem)) + { + using (StreamReader sr = new StreamReader(stream)) + { + string jsonString = sr.ReadToEnd(); + // Notice description is not included, camelCaseProperty starts lower case, the white space shows the indents + string expectedJsonString = "{\r\n \"id\": \"testid\",\r\n \"camelCaseProperty\": \"TestCamelCase\"\r\n}"; + Assert.AreEqual(expectedJsonString, jsonString); + } + } + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void ThrowOnSerializerOptionsWithCustomSerializer() + { + CosmosClientOptions options = new CosmosClientOptions() + { + Serializer = new CosmosJsonDotNetSerializer() + }; + + options.SerializerOptions = new CosmosSerializationOptions(); + } + + [TestMethod] + [ExpectedException(typeof(ArgumentException))] + public void ThrowOnCustomSerializerWithSerializerOptions() + { + CosmosClientOptions options = new CosmosClientOptions() + { + SerializerOptions = new CosmosSerializationOptions() + }; + + options.Serializer = new CosmosJsonDotNetSerializer(); + } + [TestMethod] [ExpectedException(typeof(ArgumentNullException))] public void ThrowOnNullConnectionString() @@ -160,19 +250,19 @@ public void ThrowOnMissingAccountEndpointInConnectionString() public void AssertJsonSerializer() { string connectionString = "AccountEndpoint=https://localtestcosmos.documents.azure.com:443/;AccountKey=425Mcv8CXQqzRNCgFNjIhT424GK99CKJvASowTnq15Vt8LeahXTcN5wt3342vQ==;"; - var cosmosClientBuilder = new CosmosClientBuilder(connectionString); - var cosmosClient = cosmosClientBuilder.Build(new MockDocumentClient()); - Assert.IsInstanceOfType(cosmosClient.ClientOptions.CosmosSerializerWithWrapperOrDefault, typeof(CosmosJsonSerializerWrapper)); - Assert.AreEqual(cosmosClient.ClientOptions.CosmosSerializerWithWrapperOrDefault, cosmosClient.ClientOptions.PropertiesSerializer); + CosmosClientBuilder cosmosClientBuilder = new CosmosClientBuilder(connectionString); + CosmosClient cosmosClient = cosmosClientBuilder.Build(new MockDocumentClient()); + Assert.IsInstanceOfType(cosmosClient.ClientOptions.GetCosmosSerializerWithWrapperOrDefault(), typeof(CosmosJsonSerializerWrapper)); + Assert.AreEqual(cosmosClient.ClientOptions.GetCosmosSerializerWithWrapperOrDefault(), cosmosClient.ClientOptions.PropertiesSerializer); CosmosSerializer defaultSerializer = cosmosClient.ClientOptions.PropertiesSerializer; CosmosSerializer mockJsonSerializer = new Mock().Object; cosmosClientBuilder.WithCustomSerializer(mockJsonSerializer); - var cosmosClientCustom = cosmosClientBuilder.Build(new MockDocumentClient()); + CosmosClient cosmosClientCustom = cosmosClientBuilder.Build(new MockDocumentClient()); Assert.AreEqual(defaultSerializer, cosmosClientCustom.ClientOptions.PropertiesSerializer); Assert.AreEqual(mockJsonSerializer, cosmosClientCustom.ClientOptions.Serializer); - Assert.IsInstanceOfType(cosmosClientCustom.ClientOptions.CosmosSerializerWithWrapperOrDefault, typeof(CosmosJsonSerializerWrapper)); - Assert.AreEqual(mockJsonSerializer, ((CosmosJsonSerializerWrapper)cosmosClientCustom.ClientOptions.CosmosSerializerWithWrapperOrDefault).InternalJsonSerializer); + Assert.IsInstanceOfType(cosmosClientCustom.ClientOptions.GetCosmosSerializerWithWrapperOrDefault(), typeof(CosmosJsonSerializerWrapper)); + Assert.AreEqual(mockJsonSerializer, ((CosmosJsonSerializerWrapper)cosmosClientCustom.ClientOptions.GetCosmosSerializerWithWrapperOrDefault()).InternalJsonSerializer); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json index 57b123c6dd..01cbe3d275 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json @@ -1246,11 +1246,19 @@ ], "MethodInfo": "Microsoft.Azure.Cosmos.ConnectionMode get_ConnectionMode()" }, - "Microsoft.Azure.Cosmos.CosmosSerializer get_Serializer()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.CosmosSerializationOptions get_SerializerOptions()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosSerializationOptions get_SerializerOptions()" + }, + "Microsoft.Azure.Cosmos.CosmosSerializationOptions SerializerOptions": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosSerializer get_Serializer()": { + "Type": "Method", + "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosSerializer get_Serializer()" }, "Microsoft.Azure.Cosmos.CosmosSerializer Serializer[Newtonsoft.Json.JsonConverterAttribute(typeof(Microsoft.Azure.Cosmos.CosmosClientOptions+ClientOptionJsonConverter))]": { @@ -1267,9 +1275,11 @@ ], "MethodInfo": null }, - "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.RequestHandler] get_CustomHandlers()": { + "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.RequestHandler] get_CustomHandlers()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", - "Attributes": [], + "Attributes": [ + "CompilerGeneratedAttribute" + ], "MethodInfo": "System.Collections.ObjectModel.Collection`1[Microsoft.Azure.Cosmos.RequestHandler] get_CustomHandlers()" }, "System.Nullable`1[Microsoft.Azure.Cosmos.ConsistencyLevel] ConsistencyLevel": { @@ -1399,12 +1409,15 @@ ], "MethodInfo": "Void set_RequestTimeout(System.TimeSpan)" }, - "Void set_Serializer(Microsoft.Azure.Cosmos.CosmosSerializer)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Void set_Serializer(Microsoft.Azure.Cosmos.CosmosSerializer)": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "Void set_Serializer(Microsoft.Azure.Cosmos.CosmosSerializer)" + }, + "Void set_SerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Void set_SerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions)" } }, "NestedTypes": {} @@ -1514,51 +1527,97 @@ }, "NestedTypes": {} }, - "CosmosJsonDotNetSerializer": { + "CosmosPropertyNamingPolicy": { "Subclasses": {}, "Members": { - "System.IO.Stream ToStream[T](T)": { + "Int32 value__": { + "Type": "Field", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy CamelCase": { + "Type": "Field", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy Default": { + "Type": "Field", + "Attributes": [], + "MethodInfo": null + } + }, + "NestedTypes": {} + }, + "CosmosSerializationOptions": { + "Subclasses": {}, + "Members": { + "Boolean get_IgnoreNullValues()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Boolean get_IgnoreNullValues()" + }, + "Boolean get_Indented()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Boolean get_Indented()" + }, + "Boolean IgnoreNullValues": { + "Type": "Property", "Attributes": [], - "MethodInfo": "System.IO.Stream ToStream[T](T)" + "MethodInfo": null }, - "T FromStream[T](System.IO.Stream)": { + "Boolean Indented": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, + "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy get_PropertyNamingPolicy()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy get_PropertyNamingPolicy()" + }, + "Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy PropertyNamingPolicy": { + "Type": "Property", "Attributes": [], - "MethodInfo": "T FromStream[T](System.IO.Stream)" + "MethodInfo": null }, - "Void .ctor(Newtonsoft.Json.JsonSerializerSettings)": { + "Void .ctor()": { "Type": "Constructor", "Attributes": [], - "MethodInfo": "Void .ctor(Newtonsoft.Json.JsonSerializerSettings)" + "MethodInfo": "Void .ctor()" + }, + "Void set_IgnoreNullValues(Boolean)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_IgnoreNullValues(Boolean)" + }, + "Void set_Indented(Boolean)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_Indented(Boolean)" + }, + "Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy)[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Type": "Method", + "Attributes": [ + "CompilerGeneratedAttribute" + ], + "MethodInfo": "Void set_PropertyNamingPolicy(Microsoft.Azure.Cosmos.CosmosPropertyNamingPolicy)" } }, "NestedTypes": {} }, "CosmosSerializer": { - "Subclasses": { - "CosmosJsonDotNetSerializer": { - "Subclasses": {}, - "Members": { - "System.IO.Stream ToStream[T](T)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "System.IO.Stream ToStream[T](T)" - }, - "T FromStream[T](System.IO.Stream)": { - "Type": "Method", - "Attributes": [], - "MethodInfo": "T FromStream[T](System.IO.Stream)" - }, - "Void .ctor(Newtonsoft.Json.JsonSerializerSettings)": { - "Type": "Constructor", - "Attributes": [], - "MethodInfo": "Void .ctor(Newtonsoft.Json.JsonSerializerSettings)" - } - }, - "NestedTypes": {} - } - }, + "Subclasses": {}, "Members": { "System.IO.Stream ToStream[T](T)": { "Type": "Method", @@ -2167,6 +2226,11 @@ "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithRequestTimeout(System.TimeSpan)" }, + "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithSerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions)": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithSerializerOptions(Microsoft.Azure.Cosmos.CosmosSerializationOptions)" + }, "Microsoft.Azure.Cosmos.Fluent.CosmosClientBuilder WithThrottlingRetryOptions(System.TimeSpan, Int32)": { "Type": "Method", "Attributes": [], diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs index 500a61a076..afda45e375 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/LocationCacheTests.cs @@ -506,12 +506,20 @@ await BackoffRetryUtility.ExecuteAsync( [Owner("atulk")] public async Task ValidateAsync() { - for (int i = 0; i < 8; i++) + bool[] boolValues = new bool[] {true, false}; + + foreach (bool useMultipleWriteEndpoints in boolValues) { - bool useMultipleWriteEndpoints = (i & 1) > 0; - bool endpointDiscoveryEnabled = (i & 2) > 0; - bool isPreferredListEmpty = (i & 4) > 0; - await this.ValidateLocationCacheAsync(useMultipleWriteEndpoints, endpointDiscoveryEnabled, isPreferredListEmpty); + foreach (bool endpointDiscoveryEnabled in boolValues) + { + foreach (bool isPreferredListEmpty in boolValues) + { + await this.ValidateLocationCacheAsync( + useMultipleWriteEndpoints, + endpointDiscoveryEnabled, + isPreferredListEmpty); + } + } } } @@ -642,15 +650,34 @@ private async Task ValidateLocationCacheAsync( preferredAvailableWriteEndpoints, preferredAvailableReadEndpoints); - // wait for TTL on unavailablity info - await Task.Delay( - int.Parse( - System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"], - NumberStyles.Integer, - CultureInfo.InvariantCulture) * 1000); - - CollectionAssert.AreEqual(currentWriteEndpoints, this.cache.WriteEndpoints); - CollectionAssert.AreEqual(currentReadEndpoints, this.cache.ReadEndpoints); + // wait for TTL on unavailability info + string expirationTime = System.Configuration.ConfigurationManager.AppSettings["UnavailableLocationsExpirationTimeInSeconds"]; + int delayInMilliSeconds = int.Parse( + expirationTime, + NumberStyles.Integer, + CultureInfo.InvariantCulture) * 1000; + await Task.Delay(delayInMilliSeconds); + + string config = $"Delay{expirationTime};" + + $"useMultipleWriteLocations:{useMultipleWriteLocations};" + + $"endpointDiscoveryEnabled:{endpointDiscoveryEnabled};" + + $"isPreferredListEmpty:{isPreferredListEmpty}"; + + CollectionAssert.AreEqual( + currentWriteEndpoints, + this.cache.WriteEndpoints, + "Write Endpoints failed;" + + $"config:{config};" + + $"Current:{string.Join(",", currentWriteEndpoints)};" + + $"Cache:{string.Join(",", this.cache.WriteEndpoints)};"); + + CollectionAssert.AreEqual( + currentReadEndpoints, + this.cache.ReadEndpoints, + "Read Endpoints failed;" + + $"config:{config};" + + $"Current:{string.Join(",", currentReadEndpoints)};" + + $"Cache:{string.Join(",", this.cache.ReadEndpoints)};"); } } } diff --git a/changelog.md b/changelog.md index b82e187cd1..e602e25936 100644 --- a/changelog.md +++ b/changelog.md @@ -5,6 +5,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- [#650](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/650) CosmosSerializerOptions to customize serialization + ### Fixed - [#612](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/612) Bug fix for ReadFeed with partition-key @@ -20,7 +24,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [#541](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/541) Added consistency level to client and query options - [#544](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/544) Added continuation token support for LINQ - [#557](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/557) Added trigger options to item request options -- [#571](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/571) Added a default JSON.net serializer with optional settings - [#572](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/572) Added partition key validation on CreateContainerIfNotExistsAsync - [#581](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/581) Added LINQ to QueryDefinition API - [#592](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/592) Added CreateIfNotExistsAsync to container builder