From d4b9c9ca1a8fe3f3b473699b2b8d4f7c1b1f8951 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Tue, 11 Feb 2020 06:23:55 -0800 Subject: [PATCH 01/25] Removed Error since it is not being used anywhere. --- .../src/Batch/TransactionalBatchOperationResult.cs | 1 - .../src/Handler/ResponseMessage.cs | 14 ++------------ .../src/Query/v3Query/QueryResponse.cs | 4 ---- .../src/Resource/CosmosException.cs | 1 - Microsoft.Azure.Cosmos/src/Util/Extensions.cs | 1 - 5 files changed, 2 insertions(+), 19 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs index c9d311b227..6cdb3142e0 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs @@ -213,7 +213,6 @@ internal ResponseMessage ToResponseMessage() statusCode: this.StatusCode, requestMessage: null, errorMessage: null, - error: null, headers: headers, diagnostics: this.DiagnosticsContext ?? CosmosDiagnosticsContext.Create()) { diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 847b8481c7..5d6b7da0b2 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -53,21 +53,18 @@ public ResponseMessage( /// The HttpStatusCode of the response /// The object /// The reason for failures if any. - /// The inner error object /// The headers for the response. /// The diagnostics for the request internal ResponseMessage( HttpStatusCode statusCode, RequestMessage requestMessage, string errorMessage, - Error error, Headers headers, CosmosDiagnosticsContext diagnostics) { this.StatusCode = statusCode; this.RequestMessage = requestMessage; this.ErrorMessage = errorMessage; - this.Error = error; this.Headers = headers ?? new Headers(); this.DiagnosticsContext = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); } @@ -120,11 +117,6 @@ public virtual Stream Content internal CosmosDiagnosticsContext DiagnosticsContext { get; } - /// - /// Gets the internal error object. - /// - internal virtual Error Error { get; set; } - private bool disposed; private Stream content; @@ -148,8 +140,7 @@ public virtual ResponseMessage EnsureSuccessStatusCode() throw new CosmosException( this, - message, - this.Error); + message); } return this; @@ -213,8 +204,7 @@ private void CheckDisposed() private void EnsureErrorMessage() { - if (this.Error != null - || !string.IsNullOrEmpty(this.ErrorMessage)) + if (!string.IsNullOrEmpty(this.ErrorMessage)) { return; } diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs index fac7773717..3fe1f3ac39 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs @@ -42,14 +42,12 @@ private QueryResponse( RequestMessage requestMessage, CosmosDiagnosticsContext diagnostics, string errorMessage, - Error error, Lazy memoryStream, CosmosSerializationFormatOptions serializationOptions) : base( statusCode: statusCode, requestMessage: requestMessage, errorMessage: errorMessage, - error: error, headers: responseHeaders, diagnostics: diagnostics) { @@ -121,7 +119,6 @@ internal static QueryResponse CreateSuccess( diagnostics: diagnostics, statusCode: HttpStatusCode.OK, errorMessage: null, - error: null, requestMessage: null, memoryStream: memoryStream, serializationOptions: serializationOptions); @@ -145,7 +142,6 @@ internal static QueryResponse CreateFailure( diagnostics: diagnostics, statusCode: statusCode, errorMessage: errorMessage, - error: error, requestMessage: requestMessage, memoryStream: null, serializationOptions: null); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs index 0f95bc120d..fe90a8d5cd 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs @@ -208,7 +208,6 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) requestMessage: request, errorMessage: this.Message, statusCode: this.StatusCode, - error: this.Error, diagnostics: request.DiagnosticsContext); } } diff --git a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs index 6045ae0e90..7021ddf90c 100644 --- a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs +++ b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs @@ -77,7 +77,6 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientExcep } responseMessage.ErrorMessage = reasonPhraseString; - responseMessage.Error = documentClientException.Error; if (documentClientException.Headers != null) { From ef9a94b41731a320aa0a15a1842f7d1eaa4bfea9 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Tue, 11 Feb 2020 06:47:03 -0800 Subject: [PATCH 02/25] Removed error since it's not being used --- .../src/Resource/ClientContextCore.cs | 2 +- .../src/Resource/Container/ContainerCore.cs | 2 +- .../src/Resource/CosmosException.cs | 12 +----------- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index e67f4fe623..1b4117bb2e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -201,7 +201,7 @@ internal override async Task GetCachedContainerPropertiesAs } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message, ex.Error); + throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index b22cbe6574..276937f7bc 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -213,7 +213,7 @@ public override Task ReplaceContainerStreamAsync( } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message, ex.Error); + throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs index fe90a8d5cd..4a8d103a18 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs @@ -19,19 +19,16 @@ public class CosmosException : Exception internal CosmosException( HttpStatusCode statusCode, string message, - Error error = null, Exception inner = null) : base(message, inner) { this.StatusCode = statusCode; - this.Error = error; this.Headers = new Headers(); } internal CosmosException( ResponseMessage cosmosResponseMessage, - string message, - Error error = null) + string message) : base(message) { if (cosmosResponseMessage != null) @@ -56,8 +53,6 @@ internal CosmosException( } } } - - this.Error = error; } /// @@ -131,11 +126,6 @@ public CosmosException( /// public virtual CosmosDiagnostics Diagnostics { get; } - /// - /// Gets the internal error object - /// - internal virtual Error Error { get; } - /// /// Try to get a header from the cosmos response message /// From 87b363cb78f3a42986432248d3049a70ff6ff8ac Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 13 Feb 2020 07:17:30 -0800 Subject: [PATCH 03/25] Refactored added new exception types --- .../src/Handler/ResponseMessage.cs | 2 + .../CosmosExceptions/BadRequestException.cs | 24 +++++ .../{ => CosmosExceptions}/CosmosException.cs | 34 ++++++- .../CosmosExceptionFactory.cs | 91 +++++++++++++++++++ .../CosmosExceptions/CosmosHttpException.cs | 25 +++++ .../CosmosRequestTimeoutException.cs | 51 +++++++++++ .../InternalServerErrorException.cs | 24 +++++ 7 files changed, 248 insertions(+), 3 deletions(-) rename Microsoft.Azure.Cosmos/src/Resource/{ => CosmosExceptions}/CosmosException.cs (86%) create mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 5d6b7da0b2..8e38b362c3 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -15,6 +15,8 @@ namespace Microsoft.Azure.Cosmos /// public class ResponseMessage : IDisposable { + private readonly CosmosException exception; + /// /// Create a /// diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs index c7646d369b..4e31945fd8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions { using System; + using System.Diagnostics; using System.Net; internal sealed class BadRequestException : CosmosHttpException @@ -23,5 +24,28 @@ public BadRequestException(string message, Exception innerException) : base(statusCode: HttpStatusCode.BadRequest, message: message, innerException: innerException) { } + + internal BadRequestException( + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + : base(HttpStatusCode.BadRequest, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs similarity index 86% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 4a8d103a18..584c44819d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -5,17 +5,19 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Diagnostics; using System.IO; using System.Net; + using System.Runtime.CompilerServices; using System.Text; - using Microsoft.Azure.Documents; /// /// The Cosmos Client exception /// public class CosmosException : Exception { - private static readonly string FullName = typeof(CosmosException).FullName; + private readonly StackTrace stackTrace; + internal CosmosException( HttpStatusCode statusCode, string message, @@ -55,6 +57,29 @@ internal CosmosException( } } + internal CosmosException( + HttpStatusCode statusCodes, + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + : base(message, innerException) + { + this.stackTrace = stackTrace; + this.ActivityId = activityId; + this.StatusCode = statusCodes; + this.SubStatusCode = subStatusCode; + this.RetryAfter = retryAfter; + this.RequestCharge = requestCharge; + this.Diagnostics = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); + this.Headers = headers; + } + /// /// Create a /// @@ -143,6 +168,9 @@ public virtual bool TryGetHeader(string headerName, out string value) return this.Headers.TryGetValue(headerName, out value); } + /// + public override string StackTrace => this.stackTrace.ToString(); + /// /// Create a custom string with all the relevant exception information /// @@ -150,7 +178,7 @@ public virtual bool TryGetHeader(string headerName, out string value) public override string ToString() { StringBuilder stringBuilder = new StringBuilder(); - stringBuilder.Append(CosmosException.FullName); + stringBuilder.Append(this.GetType().FullName); if (this.Message != null) { stringBuilder.Append(" : "); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs new file mode 100644 index 0000000000..21c99108fe --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.Diagnostics; + using System.Net; + using Microsoft.Azure.Documents; + + internal static class CosmosExceptionFactory + { + internal static CosmosException Create( + DocumentClientException dce, + CosmosDiagnosticsContext diagnosticsContext) + { + Headers headers = new Headers(); + if (dce.Headers != null) + { + foreach (string key in dce.Headers) + { + headers.Add(key, dce.Headers[key]); + } + } + + return CosmosExceptionFactory.Create( + dce.StatusCode ?? HttpStatusCode.InternalServerError, + (int)dce.GetSubStatus(), + dce.Message, + new StackTrace(dce), + dce.ActivityId, + dce.RequestCharge, + dce.RetryAfter, + headers, + diagnosticsContext, + dce.InnerException); + } + + public static CosmosException Create( + HttpStatusCode statusCode, + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + { + switch (statusCode) + { + case HttpStatusCode.InternalServerError: + return new InternalServerErrorException( + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + case HttpStatusCode.BadRequest: + return new BadRequestException( + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + default: + return new CosmosException( + statusCode, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs index 1b1a76b486..754bd9d3c8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions { using System; + using System.Diagnostics; using System.Net; internal abstract class CosmosHttpException : CosmosException @@ -23,5 +24,29 @@ protected CosmosHttpException(HttpStatusCode statusCode, string message, Excepti : base(statusCode: statusCode, message: message, inner: innerException) { } + + internal CosmosHttpException( + HttpStatusCode statusCodes, + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + : base(statusCodes, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs new file mode 100644 index 0000000000..360a0e8b80 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs @@ -0,0 +1,51 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.Diagnostics; + using System.Net; + + internal sealed class CosmosRequestTimeoutException : CosmosHttpException + { + public CosmosRequestTimeoutException() + : base(statusCode: HttpStatusCode.RequestTimeout, message: null) + { + } + + public CosmosRequestTimeoutException(string message) + : base(statusCode: HttpStatusCode.RequestTimeout, message: message) + { + } + + public CosmosRequestTimeoutException(string message, Exception innerException) + : base(statusCode: HttpStatusCode.RequestTimeout, message: message, innerException: innerException) + { + } + + internal CosmosRequestTimeoutException( + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + : base(HttpStatusCode.RequestTimeout, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs index 4d4ceb9122..3c5a373a4b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions { using System; + using System.Diagnostics; using System.Net; internal sealed class InternalServerErrorException : CosmosHttpException @@ -23,5 +24,28 @@ public InternalServerErrorException(string message, Exception innerException) : base(statusCode: HttpStatusCode.InternalServerError, message: message, innerException: innerException) { } + + internal InternalServerErrorException( + int subStatusCode, + string message, + StackTrace stackTrace, + string activityId, + double requestCharge, + TimeSpan? retryAfter, + Headers headers, + CosmosDiagnosticsContext diagnosticsContext, + Exception innerException) + : base(HttpStatusCode.InternalServerError, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } } } From d1406ecefe132a20a8529dcd22dea667fed75d24 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 14 Feb 2020 13:46:00 -0800 Subject: [PATCH 04/25] ResponseMessage.ErrorMessage will now return the full CosmosException string. Returning only the error message makes it not possible to debug. CosmosException now stores the stack trace. This fixes the issues where the error information is stored and later thrown causing the exception to show the incorrect error location. Created new CosmosExceptionFactory. This helps produce a CosmosException from the various types. --- .../TransactionalBatchOperationResult.cs | 2 +- .../src/Handler/ResponseMessage.cs | 70 ++----- ...smosCrossPartitionQueryExecutionContext.cs | 7 +- .../ItemProducers/ItemProducer.cs | 4 +- .../CosmosOrderByItemQueryExecutionContext.cs | 7 +- .../Core/QueryClient/QueryResponseCore.cs | 12 +- .../Core/QueryPlan/QueryPlanRetriever.cs | 20 +- .../src/Query/Core/QueryResponseFactory.cs | 46 +++-- .../Query/v3Query/CosmosQueryClientCore.cs | 2 +- .../src/Query/v3Query/QueryIterator.cs | 3 +- .../src/Query/v3Query/QueryResponse.cs | 11 +- .../src/Resource/ClientContextCore.cs | 15 +- .../src/Resource/Container/ContainerCore.cs | 3 +- .../CosmosExceptions/BadRequestException.cs | 52 ++--- .../CosmosExceptions/CosmosException.cs | 74 +++---- .../CosmosExceptionFactory.cs | 194 ++++++++++++++++-- .../CosmosExceptions/CosmosHttpException.cs | 53 ++--- .../CosmosRequestTimeoutException.cs | 51 ----- .../InternalServerErrorException.cs | 52 ++--- .../CosmosExceptions/NotFoundException.cs | 42 ++++ .../RequestTimeoutException.cs | 42 ++++ .../CosmosExceptions/ThrottledException.cs | 37 ++++ .../src/Resource/Offer/CosmosOffers.cs | 3 +- Microsoft.Azure.Cosmos/src/Util/Extensions.cs | 75 ++++--- .../CosmosQueryUnitTests.cs | 21 +- .../Query/ItemProducerTreeUnitTests.cs | 4 +- .../Query/QueryPipelineMockTests.cs | 4 +- .../Query/QueryResponseFactoryTests.cs | 14 +- .../Query/QueryResponseMessageFactory.cs | 17 +- 29 files changed, 555 insertions(+), 382 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs create mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs diff --git a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs index 6cdb3142e0..7631d2cfb4 100644 --- a/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs +++ b/Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs @@ -212,8 +212,8 @@ internal ResponseMessage ToResponseMessage() ResponseMessage responseMessage = new ResponseMessage( statusCode: this.StatusCode, requestMessage: null, - errorMessage: null, headers: headers, + cosmosException: null, diagnostics: this.DiagnosticsContext ?? CosmosDiagnosticsContext.Create()) { Content = this.ResourceStream diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 8e38b362c3..f848350af8 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.Cosmos using System.Diagnostics; using System.IO; using System.Net; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; /// @@ -15,8 +16,6 @@ namespace Microsoft.Azure.Cosmos /// public class ResponseMessage : IDisposable { - private readonly CosmosException exception; - /// /// Create a /// @@ -24,6 +23,7 @@ public ResponseMessage() { this.Headers = new Headers(); this.DiagnosticsContext = CosmosDiagnosticsContext.Create(); + this.CosmosException = null; } /// @@ -44,9 +44,16 @@ public ResponseMessage( this.StatusCode = statusCode; this.RequestMessage = requestMessage; - this.ErrorMessage = errorMessage; this.Headers = new Headers(); this.DiagnosticsContext = requestMessage?.DiagnosticsContext ?? CosmosDiagnosticsContext.Create(); + + if (!string.IsNullOrEmpty(errorMessage)) + { + this.CosmosException = CosmosExceptionFactory.Create( + statusCode, + requestMessage, + errorMessage); + } } /// @@ -54,19 +61,19 @@ public ResponseMessage( /// /// The HttpStatusCode of the response /// The object - /// The reason for failures if any. /// The headers for the response. + /// The exception if the response is from an error. /// The diagnostics for the request internal ResponseMessage( HttpStatusCode statusCode, RequestMessage requestMessage, - string errorMessage, Headers headers, + CosmosException cosmosException, CosmosDiagnosticsContext diagnostics) { this.StatusCode = statusCode; this.RequestMessage = requestMessage; - this.ErrorMessage = errorMessage; + this.CosmosException = cosmosException; this.Headers = headers ?? new Headers(); this.DiagnosticsContext = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics)); } @@ -92,7 +99,7 @@ public virtual Stream Content /// /// Gets the reason for a failure in the current response. /// - public virtual string ErrorMessage { get; internal set; } + public virtual string ErrorMessage => this.CosmosException?.ToString(); /// /// Gets the current HTTP headers. @@ -119,6 +126,8 @@ public virtual Stream Content internal CosmosDiagnosticsContext DiagnosticsContext { get; } + internal CosmosException CosmosException { get; } + private bool disposed; private Stream content; @@ -131,18 +140,13 @@ public virtual Stream Content /// /// Checks if the current has a successful status code, otherwise, throws. /// - /// An instance of representing the error state. + /// An instance of representing the error state. /// The current . public virtual ResponseMessage EnsureSuccessStatusCode() { if (!this.IsSuccessStatusCode) { - this.EnsureErrorMessage(); - string message = $"Response status code does not indicate success: {(int)this.StatusCode} Substatus: {(int)this.Headers.SubStatusCode} Reason: ({this.ErrorMessage})."; - - throw new CosmosException( - this, - message); + throw CosmosExceptionFactory.Create(this); } return this; @@ -203,43 +207,5 @@ private void CheckDisposed() throw new ObjectDisposedException(this.GetType().ToString()); } } - - private void EnsureErrorMessage() - { - if (!string.IsNullOrEmpty(this.ErrorMessage)) - { - return; - } - - if (this.content != null - && this.content.CanRead) - { - try - { - Error error = Documents.Resource.LoadFrom(this.content); - if (error != null) - { - // Error format is not consistent across modes - if (!string.IsNullOrEmpty(error.Message)) - { - this.ErrorMessage = error.Message; - } - else - { - this.ErrorMessage = error.ToString(); - } - } - } - catch (Newtonsoft.Json.JsonReaderException) - { - // Content is not Json - this.content.Position = 0; - using (StreamReader streamReader = new StreamReader(this.content)) - { - this.ErrorMessage = streamReader.ReadToEnd(); - } - } - } - } } } \ No newline at end of file diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs index b279395834..2bb1056365 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/CosmosCrossPartitionQueryExecutionContext.cs @@ -401,12 +401,7 @@ protected async Task TryInitializeAsync( if (failureResponse.HasValue) { return TryCatch.FromException( - new CosmosException( - statusCode: failureResponse.Value.StatusCode, - subStatusCode: (int)failureResponse.Value.SubStatusCode.GetValueOrDefault(0), - message: failureResponse.Value.ErrorMessage, - activityId: failureResponse.Value.ActivityId, - requestCharge: failureResponse.Value.RequestCharge)); + failureResponse.Value.CosmosException); } if (!movedToNextPage) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs index b9bdbcb48a..74341dc9c2 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs @@ -14,6 +14,8 @@ namespace Microsoft.Azure.Cosmos.Query.Core.ExecutionContext.ItemProducers using Microsoft.Azure.Cosmos.Query.Core.Collections; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Documents; using PartitionKeyRange = Documents.PartitionKeyRange; using PartitionKeyRangeIdentity = Documents.PartitionKeyRangeIdentity; @@ -289,7 +291,7 @@ public async Task BufferMoreDocumentsAsync(CancellationToken token) feedResponse = QueryResponseCore.CreateFailure( statusCode: (System.Net.HttpStatusCode)429, subStatusCodes: null, - errorMessage: "Request Rate Too Large", + cosmosException: new ThrottledException("Request Rate Too Large"), requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs index b705e575be..64d859262f 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/OrderBy/CosmosOrderByItemQueryExecutionContext.cs @@ -641,12 +641,7 @@ private async Task TryFilterAsync( if (failureResponse.HasValue) { return TryCatch.FromException( - new CosmosException( - statusCode: failureResponse.Value.StatusCode, - subStatusCode: (int)failureResponse.Value.SubStatusCode.GetValueOrDefault(0), - message: failureResponse.Value.ErrorMessage, - activityId: failureResponse.Value.ActivityId, - requestCharge: 0)); + failureResponse.Value.CosmosException); } break; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs index 061153859f..368c79b432 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryClient/QueryResponseCore.cs @@ -35,7 +35,7 @@ private QueryResponseCore( long responseLengthBytes, string disallowContinuationTokenMessage, string continuationToken, - string errorMessage, + CosmosException cosmosException, SubStatusCodes? subStatusCode) { this.IsSuccess = isSuccess; @@ -47,13 +47,13 @@ private QueryResponseCore( this.RequestCharge = requestCharge; this.DisallowContinuationTokenMessage = disallowContinuationTokenMessage; this.ContinuationToken = continuationToken; - this.ErrorMessage = errorMessage; + this.CosmosException = cosmosException; this.SubStatusCode = subStatusCode; } internal IReadOnlyList CosmosElements { get; } - internal string ErrorMessage { get; } + internal CosmosException CosmosException { get; } internal SubStatusCodes? SubStatusCode { get; } @@ -92,7 +92,7 @@ internal static QueryResponseCore CreateSuccess( responseLengthBytes: responseLengthBytes, disallowContinuationTokenMessage: disallowContinuationTokenMessage, continuationToken: continuationToken, - errorMessage: null, + cosmosException: null, subStatusCode: null); return cosmosQueryResponse; @@ -101,7 +101,7 @@ internal static QueryResponseCore CreateSuccess( internal static QueryResponseCore CreateFailure( HttpStatusCode statusCode, SubStatusCodes? subStatusCodes, - string errorMessage, + CosmosException cosmosException, double requestCharge, string activityId, IReadOnlyCollection diagnostics) @@ -116,7 +116,7 @@ internal static QueryResponseCore CreateFailure( responseLengthBytes: 0, disallowContinuationTokenMessage: null, continuationToken: null, - errorMessage: errorMessage, + cosmosException: cosmosException, subStatusCode: subStatusCodes); return cosmosQueryResponse; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs index a5b8a29827..f107b4bd97 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos.Query.Core.QueryPlan using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using OperationType = Documents.OperationType; using PartitionKeyDefinition = Documents.PartitionKeyDefinition; using ResourceType = Documents.ResourceType; @@ -62,9 +63,22 @@ public static async Task GetQueryPlanWithServiceI if (!tryGetQueryPlan.Succeeded) { - throw new CosmosException( - System.Net.HttpStatusCode.BadRequest, - tryGetQueryPlan.Exception.ToString()); + if (tryGetQueryPlan.Exception is CosmosException) + { + throw tryGetQueryPlan.Exception; + } + + throw CosmosExceptionFactory.Create( + statusCode: System.Net.HttpStatusCode.BadRequest, + subStatusCode: default, + message: tryGetQueryPlan.Exception.Message, + stackTrace: default, + activityId: default, + requestCharge: default, + retryAfter: default, + headers: default, + diagnosticsContext: default, + innerException: tryGetQueryPlan.Exception); } return tryGetQueryPlan.Result; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 5859b1dfd5..6df8e743cb 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos.Query.Core { using System; + using System.Diagnostics; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; @@ -32,14 +33,7 @@ public static QueryResponseCore CreateFromException(Exception exception) } else if (exception is ExceptionWithStackTraceException exceptionWithStackTrace) { - QueryResponseCore innerExceptionResponse = QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); - queryResponseCore = QueryResponseCore.CreateFailure( - statusCode: innerExceptionResponse.StatusCode, - subStatusCodes: innerExceptionResponse.SubStatusCode, - errorMessage: exceptionWithStackTrace.ToString(), - requestCharge: innerExceptionResponse.RequestCharge, - activityId: innerExceptionResponse.ActivityId, - diagnostics: innerExceptionResponse.Diagnostics); + return QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); } else { @@ -50,11 +44,23 @@ public static QueryResponseCore CreateFromException(Exception exception) } else { + CosmosException unkownCosmosException = CosmosExceptionFactory.Create( + statusCode: System.Net.HttpStatusCode.InternalServerError, + subStatusCode: default, + message: exception.Message, + stackTrace: new System.Diagnostics.StackTrace(exception), + activityId: QueryResponseCore.EmptyGuidString, + requestCharge: 0, + retryAfter: null, + headers: null, + diagnosticsContext: null, + innerException: exception); + // Unknown exception type should become a 500 queryResponseCore = QueryResponseCore.CreateFailure( statusCode: System.Net.HttpStatusCode.InternalServerError, subStatusCodes: null, - errorMessage: exception?.ToString(), + cosmosException: unkownCosmosException, requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -69,7 +75,7 @@ private static QueryResponseCore CreateFromCosmosException(CosmosException cosmo QueryResponseCore queryResponseCore = QueryResponseCore.CreateFailure( statusCode: cosmosException.StatusCode, subStatusCodes: (Microsoft.Azure.Documents.SubStatusCodes)cosmosException.SubStatusCode, - errorMessage: cosmosException.ToString(), + cosmosException: cosmosException, requestCharge: 0, activityId: cosmosException.ActivityId, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -79,10 +85,14 @@ private static QueryResponseCore CreateFromCosmosException(CosmosException cosmo private static QueryResponseCore CreateFromDocumentClientException(Microsoft.Azure.Documents.DocumentClientException documentClientException) { + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentClientException, + null); + QueryResponseCore queryResponseCore = QueryResponseCore.CreateFailure( statusCode: documentClientException.StatusCode.GetValueOrDefault(System.Net.HttpStatusCode.InternalServerError), subStatusCodes: null, - errorMessage: documentClientException.ToString(), + cosmosException: cosmosException, requestCharge: 0, activityId: documentClientException.ActivityId, diagnostics: QueryResponseCore.EmptyDiagnostics); @@ -101,22 +111,24 @@ private QueryExceptionConverter() public override CosmosException Visit(MalformedContinuationTokenException malformedContinuationTokenException) { return new BadRequestException( - $"{nameof(BadRequestException)} due to {nameof(MalformedContinuationTokenException)}", - malformedContinuationTokenException); + message: malformedContinuationTokenException.Message, + stackTrace: new StackTrace(malformedContinuationTokenException), + innerException: malformedContinuationTokenException); } public override CosmosException Visit(UnexpectedQueryPartitionProviderException unexpectedQueryPartitionProviderException) { return new InternalServerErrorException( - $"{nameof(InternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", - unexpectedQueryPartitionProviderException); + message: $"{nameof(InternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", + innerException: unexpectedQueryPartitionProviderException); } public override CosmosException Visit(ExpectedQueryPartitionProviderException expectedQueryPartitionProviderException) { return new BadRequestException( - $"{nameof(BadRequestException)} due to {nameof(ExpectedQueryPartitionProviderException)}", - expectedQueryPartitionProviderException); + message: expectedQueryPartitionProviderException.Message, + stackTrace: new StackTrace(expectedQueryPartitionProviderException), + innerException: expectedQueryPartitionProviderException); } } } diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs index 106c2cc472..24098c24ba 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/CosmosQueryClientCore.cs @@ -289,7 +289,7 @@ private QueryResponseCore GetCosmosElementResponse( return QueryResponseCore.CreateFailure( statusCode: cosmosResponseMessage.StatusCode, subStatusCodes: cosmosResponseMessage.Headers.SubStatusCode, - errorMessage: cosmosResponseMessage.ErrorMessage, + cosmosException: cosmosResponseMessage.CosmosException, requestCharge: cosmosResponseMessage.Headers.RequestCharge, activityId: cosmosResponseMessage.Headers.ActivityId, diagnostics: pageDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs index 2840db61d2..9b04d2f08b 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryIterator.cs @@ -119,8 +119,7 @@ public override async Task ReadNextAsync(CancellationToken canc { queryResponse = QueryResponse.CreateFailure( statusCode: responseCore.StatusCode, - error: null, - errorMessage: responseCore.ErrorMessage, + cosmosException: responseCore.CosmosException, requestMessage: null, diagnostics: diagnostics, responseHeaders: new CosmosQueryResponseMessageHeaders( diff --git a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs index 3fe1f3ac39..5cbf7597cc 100644 --- a/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs +++ b/Microsoft.Azure.Cosmos/src/Query/v3Query/QueryResponse.cs @@ -41,13 +41,13 @@ private QueryResponse( HttpStatusCode statusCode, RequestMessage requestMessage, CosmosDiagnosticsContext diagnostics, - string errorMessage, + CosmosException cosmosException, Lazy memoryStream, CosmosSerializationFormatOptions serializationOptions) : base( statusCode: statusCode, requestMessage: requestMessage, - errorMessage: errorMessage, + cosmosException: cosmosException, headers: responseHeaders, diagnostics: diagnostics) { @@ -118,7 +118,7 @@ internal static QueryResponse CreateSuccess( responseHeaders: responseHeaders, diagnostics: diagnostics, statusCode: HttpStatusCode.OK, - errorMessage: null, + cosmosException: null, requestMessage: null, memoryStream: memoryStream, serializationOptions: serializationOptions); @@ -130,8 +130,7 @@ internal static QueryResponse CreateFailure( CosmosQueryResponseMessageHeaders responseHeaders, HttpStatusCode statusCode, RequestMessage requestMessage, - string errorMessage, - Error error, + CosmosException cosmosException, CosmosDiagnosticsContext diagnostics) { QueryResponse cosmosQueryResponse = new QueryResponse( @@ -141,7 +140,7 @@ internal static QueryResponse CreateFailure( responseHeaders: responseHeaders, diagnostics: diagnostics, statusCode: statusCode, - errorMessage: errorMessage, + cosmosException: cosmosException, requestMessage: requestMessage, memoryStream: null, serializationOptions: null); diff --git a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs index 1b4117bb2e..dcbfd61ac7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/ClientContextCore.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Handlers; using Microsoft.Azure.Cosmos.Query; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; @@ -191,17 +192,21 @@ internal override async Task GetCachedContainerPropertiesAs string containerUri, CancellationToken cancellationToken = default(CancellationToken)) { + CosmosDiagnosticsContextCore diagnosticsContext = new CosmosDiagnosticsContextCore(); ClientCollectionCache collectionCache = await this.DocumentClient.GetCollectionCacheAsync(); try { - return await collectionCache.ResolveByNameAsync( - HttpConstants.Versions.CurrentVersion, - containerUri, - cancellationToken); + using (diagnosticsContext.CreateScope("ContainerCache.ResolveByNameAsync")) + { + return await collectionCache.ResolveByNameAsync( + HttpConstants.Versions.CurrentVersion, + containerUri, + cancellationToken); + } } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message); + throw CosmosExceptionFactory.Create(ex, diagnosticsContext); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index 276937f7bc..198a272b38 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -9,6 +9,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Cosmos.Scripts; using Microsoft.Azure.Documents; @@ -213,7 +214,7 @@ public override Task ReplaceContainerStreamAsync( } catch (DocumentClientException ex) { - throw new CosmosException(ex.ToCosmosResponseMessage(null), ex.Message); + throw CosmosExceptionFactory.Create(ex, null); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs index 4e31945fd8..a78137ad16 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs @@ -10,41 +10,27 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions internal sealed class BadRequestException : CosmosHttpException { - public BadRequestException() - : base(statusCode: HttpStatusCode.BadRequest, message: null) - { - } - - public BadRequestException(string message) - : base(statusCode: HttpStatusCode.BadRequest, message: message) - { - } - - public BadRequestException(string message, Exception innerException) - : base(statusCode: HttpStatusCode.BadRequest, message: message, innerException: innerException) - { - } - internal BadRequestException( - int subStatusCode, string message, - StackTrace stackTrace, - string activityId, - double requestCharge, - TimeSpan? retryAfter, - Headers headers, - CosmosDiagnosticsContext diagnosticsContext, - Exception innerException) - : base(HttpStatusCode.BadRequest, - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base( + HttpStatusCode.BadRequest, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) { } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 584c44819d..49de65e0e3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -6,9 +6,7 @@ namespace Microsoft.Azure.Cosmos { using System; using System.Diagnostics; - using System.IO; using System.Net; - using System.Runtime.CompilerServices; using System.Text; /// @@ -17,50 +15,12 @@ namespace Microsoft.Azure.Cosmos public class CosmosException : Exception { private readonly StackTrace stackTrace; - - internal CosmosException( - HttpStatusCode statusCode, - string message, - Exception inner = null) - : base(message, inner) - { - this.StatusCode = statusCode; - this.Headers = new Headers(); - } - - internal CosmosException( - ResponseMessage cosmosResponseMessage, - string message) - : base(message) - { - if (cosmosResponseMessage != null) - { - this.StatusCode = cosmosResponseMessage.StatusCode; - this.Headers = cosmosResponseMessage.Headers; - if (this.Headers == null) - { - this.Headers = new Headers(); - } - - this.ActivityId = this.Headers.ActivityId; - this.RequestCharge = this.Headers.RequestCharge; - this.RetryAfter = this.Headers.RetryAfter; - this.SubStatusCode = (int)this.Headers.SubStatusCode; - this.Diagnostics = cosmosResponseMessage.Diagnostics; - if (this.Headers.ContentLengthAsLong > 0) - { - using (StreamReader responseReader = new StreamReader(cosmosResponseMessage.Content)) - { - this.ResponseBody = responseReader.ReadToEnd(); - } - } - } - } + private readonly CosmosDiagnosticsContext diagnosticsContext; internal CosmosException( HttpStatusCode statusCodes, - int subStatusCode, string message, + int subStatusCode, StackTrace stackTrace, string activityId, double requestCharge, @@ -76,8 +36,10 @@ internal CosmosException( this.SubStatusCode = subStatusCode; this.RetryAfter = retryAfter; this.RequestCharge = requestCharge; - this.Diagnostics = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); this.Headers = headers; + + // Always have a diagnostic context. A new diagnostic will have useful info like user agent + this.diagnosticsContext = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); } /// @@ -96,6 +58,7 @@ public CosmosException( double requestCharge) : base(message) { + this.stackTrace = new StackTrace(-1); this.SubStatusCode = subStatusCode; this.StatusCode = statusCode; this.RequestCharge = requestCharge; @@ -149,7 +112,23 @@ public CosmosException( /// /// Gets the diagnostics for the request /// - public virtual CosmosDiagnostics Diagnostics { get; } + public virtual CosmosDiagnostics Diagnostics => this.diagnosticsContext; + + /// + public override string StackTrace + { + get + { + if (this.stackTrace != null) + { + return this.stackTrace.ToString(); + } + else + { + return base.StackTrace; + } + } + } /// /// Try to get a header from the cosmos response message @@ -168,9 +147,6 @@ public virtual bool TryGetHeader(string headerName, out string value) return this.Headers.TryGetValue(headerName, out value); } - /// - public override string StackTrace => this.stackTrace.ToString(); - /// /// Create a custom string with all the relevant exception information /// @@ -224,9 +200,9 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) return new ResponseMessage( headers: this.Headers, requestMessage: request, - errorMessage: this.Message, + cosmosException: this, statusCode: this.StatusCode, - diagnostics: request.DiagnosticsContext); + diagnostics: this.diagnosticsContext); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 21c99108fe..4bbcc9d12d 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions { using System; using System.Diagnostics; + using System.IO; using System.Net; using Microsoft.Azure.Documents; @@ -24,8 +25,22 @@ internal static CosmosException Create( } } + HttpStatusCode httpStatusCode; + if (dce.StatusCode.HasValue) + { + httpStatusCode = dce.StatusCode.Value; + } + else if (dce.InnerException != null && dce.InnerException is TransportException) + { + httpStatusCode = HttpStatusCode.RequestTimeout; + } + else + { + httpStatusCode = HttpStatusCode.InternalServerError; + } + return CosmosExceptionFactory.Create( - dce.StatusCode ?? HttpStatusCode.InternalServerError, + httpStatusCode, (int)dce.GetSubStatus(), dce.Message, new StackTrace(dce), @@ -37,7 +52,122 @@ internal static CosmosException Create( dce.InnerException); } - public static CosmosException Create( + internal static CosmosException Create( + HttpStatusCode statusCode, + RequestMessage requestMessage, + string errorMessage) + { + return CosmosExceptionFactory.Create( + statusCode: statusCode, + subStatusCode: default, + message: errorMessage, + stackTrace: new StackTrace(1), + activityId: requestMessage?.Headers?.ActivityId, + requestCharge: 0, + retryAfter: default, + headers: requestMessage?.Headers, + diagnosticsContext: default, + innerException: default); + } + + internal static CosmosException Create( + ResponseMessage responseMessage) + { + string errorMessage = responseMessage.ErrorMessage; + if (string.IsNullOrEmpty(errorMessage)) + { + if (responseMessage.Headers.ContentLengthAsLong > 0) + { + using (StreamReader responseReader = new StreamReader(responseMessage.Content)) + { + errorMessage = responseReader.ReadToEnd(); + } + } + } + + return CosmosExceptionFactory.Create( + responseMessage.StatusCode, + (int)responseMessage.Headers.SubStatusCode, + errorMessage, + new StackTrace(1), + responseMessage.Headers.ActivityId, + responseMessage.Headers.RequestCharge, + responseMessage.Headers.RetryAfter, + responseMessage.Headers, + responseMessage.DiagnosticsContext, + null); + } + + internal static CosmosException Create( + StoreResponse storeResponse, + RequestMessage requestMessage) + { + if (storeResponse == null) + { + throw new ArgumentNullException(nameof(storeResponse)); + } + + if (requestMessage == null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + string errorMessage = CosmosExceptionFactory.GetErrorMessageFromStream(storeResponse.ResponseBody); + Headers headers = storeResponse.ToCosmosHeaders(); + + return CosmosExceptionFactory.Create( + storeResponse.StatusCode, + (int)headers.SubStatusCode, + errorMessage, + new StackTrace(-1), + headers.ActivityId, + headers.RequestCharge, + headers.RetryAfter, + headers, + requestMessage.DiagnosticsContext, + null); + } + + internal static string GetErrorMessageFromStream( + Stream content) + { + using (content) + { + if (content != null + && content.CanRead) + { + try + { + Error error = Documents.Resource.LoadFrom(content); + if (error != null) + { + // Error format is not consistent across modes + if (!string.IsNullOrEmpty(error.Message)) + { + return error.Message; + } + else + { + return error.ToString(); + } + } + } + catch (Newtonsoft.Json.JsonReaderException) + { + // Content is not Json + content.Position = 0; + using (StreamReader streamReader = new StreamReader(content)) + { + return streamReader.ReadToEnd(); + } + } + } + + return null; + } + } + + internal static CosmosException Create( HttpStatusCode statusCode, int subStatusCode, string message, @@ -53,31 +183,53 @@ public static CosmosException Create( { case HttpStatusCode.InternalServerError: return new InternalServerErrorException( - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); case HttpStatusCode.BadRequest: return new BadRequestException( - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + case HttpStatusCode.RequestTimeout: + return new RequestTimeoutException( + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + case (HttpStatusCode)429: + return new ThrottledException( + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); default: return new CosmosException( statusCode, - subStatusCode, message, + subStatusCode, stackTrace, activityId, requestCharge, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs index 754bd9d3c8..92dd1da418 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs @@ -10,42 +10,27 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions internal abstract class CosmosHttpException : CosmosException { - protected CosmosHttpException(HttpStatusCode statusCode) - : this(statusCode, message: null, innerException: null) - { - } - - protected CosmosHttpException(HttpStatusCode statusCode, string message) - : this(statusCode, message: message, innerException: null) - { - } - - protected CosmosHttpException(HttpStatusCode statusCode, string message, Exception innerException) - : base(statusCode: statusCode, message: message, inner: innerException) - { - } - internal CosmosHttpException( - HttpStatusCode statusCodes, - int subStatusCode, + HttpStatusCode statusCode, string message, - StackTrace stackTrace, - string activityId, - double requestCharge, - TimeSpan? retryAfter, - Headers headers, - CosmosDiagnosticsContext diagnosticsContext, - Exception innerException) - : base(statusCodes, - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base(statusCode, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) { } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs deleted file mode 100644 index 360a0e8b80..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs +++ /dev/null @@ -1,51 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosRequestTimeoutException : CosmosHttpException - { - public CosmosRequestTimeoutException() - : base(statusCode: HttpStatusCode.RequestTimeout, message: null) - { - } - - public CosmosRequestTimeoutException(string message) - : base(statusCode: HttpStatusCode.RequestTimeout, message: message) - { - } - - public CosmosRequestTimeoutException(string message, Exception innerException) - : base(statusCode: HttpStatusCode.RequestTimeout, message: message, innerException: innerException) - { - } - - internal CosmosRequestTimeoutException( - int subStatusCode, - string message, - StackTrace stackTrace, - string activityId, - double requestCharge, - TimeSpan? retryAfter, - Headers headers, - CosmosDiagnosticsContext diagnosticsContext, - Exception innerException) - : base(HttpStatusCode.RequestTimeout, - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs index 3c5a373a4b..f4c53b533e 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs @@ -10,41 +10,27 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions internal sealed class InternalServerErrorException : CosmosHttpException { - public InternalServerErrorException() - : base(statusCode: HttpStatusCode.InternalServerError, message: null) - { - } - - public InternalServerErrorException(string message) - : base(statusCode: HttpStatusCode.InternalServerError, message: message) - { - } - - public InternalServerErrorException(string message, Exception innerException) - : base(statusCode: HttpStatusCode.InternalServerError, message: message, innerException: innerException) - { - } - internal InternalServerErrorException( - int subStatusCode, string message, - StackTrace stackTrace, - string activityId, - double requestCharge, - TimeSpan? retryAfter, - Headers headers, - CosmosDiagnosticsContext diagnosticsContext, - Exception innerException) - : base(HttpStatusCode.InternalServerError, - subStatusCode, - message, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base( + HttpStatusCode.InternalServerError, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) { } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs new file mode 100644 index 0000000000..6fbc8aae17 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.Diagnostics; + using System.Net; + + internal sealed class NotFoundException : CosmosHttpException + { + public NotFoundException(string message) + : base(statusCode: HttpStatusCode.NotFound, message: message) + { + } + + internal NotFoundException( + string message, + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base( + HttpStatusCode.NotFound, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs new file mode 100644 index 0000000000..a990834904 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs @@ -0,0 +1,42 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.Diagnostics; + using System.Net; + + internal sealed class RequestTimeoutException : CosmosHttpException + { + public RequestTimeoutException(string message) + : base(statusCode: HttpStatusCode.RequestTimeout, message: message) + { + } + + internal RequestTimeoutException( + string message, + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base( + HttpStatusCode.RequestTimeout, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs new file mode 100644 index 0000000000..afc58684c7 --- /dev/null +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs @@ -0,0 +1,37 @@ +// ------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +// ------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions +{ + using System; + using System.Diagnostics; + using System.Net; + + internal sealed class ThrottledException : CosmosHttpException + { + internal ThrottledException( + string message, + int subStatusCode = default, + StackTrace stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + : base( + (HttpStatusCode)429, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException) + { + } + } +} diff --git a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs index 34bc33629a..95a080e904 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs @@ -145,7 +145,8 @@ private async Task GetOfferV2Async( if (offerV2 == null && failIfNotConfigured) { - throw new CosmosException(HttpStatusCode.NotFound, $"Throughput is not configured for {targetRID}"); + throw new NotFoundException( + $"Throughput is not configured for {targetRID}"); } return offerV2; diff --git a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs index 7021ddf90c..d45d34c2ba 100644 --- a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs +++ b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Core.Trace; using Microsoft.Azure.Cosmos.Diagnostics; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; internal static class Extensions @@ -56,49 +57,39 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentServiceResp internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientException documentClientException, RequestMessage requestMessage) { - // if StatusCode is null it is a client business logic error and it never hit the backend, so throw - if (documentClientException.StatusCode == null) + CosmosDiagnosticsContext diagnosticsContext = requestMessage?.DiagnosticsContext; + if (diagnosticsContext == null) { - throw documentClientException; + diagnosticsContext = CosmosDiagnosticsContextCore.Create(); } - // if there is a status code then it came from the backend, return error as http error instead of throwing the exception - ResponseMessage responseMessage = new ResponseMessage(documentClientException.StatusCode ?? HttpStatusCode.InternalServerError, requestMessage); - string reasonPhraseString = documentClientException.ToString(); - if (!string.IsNullOrEmpty(reasonPhraseString)) - { - if (documentClientException.Message.IndexOfAny(Extensions.NewLineCharacters) >= 0) - { - StringBuilder sb = new StringBuilder(reasonPhraseString); - sb = sb.Replace("\r", string.Empty); - sb = sb.Replace("\n", string.Empty); - reasonPhraseString = sb.ToString(); - } - } - - responseMessage.ErrorMessage = reasonPhraseString; - - if (documentClientException.Headers != null) - { - foreach (string header in documentClientException.Headers.AllKeys()) - { - responseMessage.Headers.Add(header, documentClientException.Headers[header]); - } - } + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentClientException, + diagnosticsContext); PointOperationStatistics pointOperationStatistics = new PointOperationStatistics( - activityId: responseMessage.Headers.ActivityId, - statusCode: documentClientException.StatusCode.Value, + activityId: cosmosException.Headers.ActivityId, + statusCode: cosmosException.StatusCode, subStatusCode: (int)SubStatusCodes.Unknown, - requestCharge: responseMessage.Headers.RequestCharge, - errorMessage: responseMessage.ErrorMessage, + requestCharge: cosmosException.Headers.RequestCharge, + errorMessage: cosmosException.Message, method: requestMessage?.Method, requestUri: requestMessage?.RequestUri, requestSessionToken: requestMessage?.Headers?.Session, - responseSessionToken: responseMessage.Headers.Session, + responseSessionToken: cosmosException.Headers.Session, clientSideRequestStatistics: documentClientException.RequestStatistics as CosmosClientSideRequestStatistics); - responseMessage.DiagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); + diagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); + + // if StatusCode is null it is a client business logic error and it never hit the backend, so throw + if (documentClientException.StatusCode == null) + { + throw cosmosException; + } + + // if there is a status code then it came from the backend, return error as http error instead of throwing the exception + ResponseMessage responseMessage = cosmosException.ToCosmosResponseMessage(requestMessage); + if (requestMessage != null) { requestMessage.Properties.Remove(nameof(DocumentClientException)); @@ -110,6 +101,16 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientExcep internal static ResponseMessage ToCosmosResponseMessage(this StoreResponse storeResponse, RequestMessage requestMessage) { + // If it's considered a failure create the corresponding CosmosException + if (((int)storeResponse.StatusCode >= 200) && ((int)storeResponse.StatusCode <= 299)) + { + CosmosException cosmosException = CosmosExceptionFactory.Create( + storeResponse, + requestMessage); + + return cosmosException.ToCosmosResponseMessage(requestMessage); + } + // Is status code conversion lossy? ResponseMessage responseMessage = new ResponseMessage((HttpStatusCode)storeResponse.Status, requestMessage); if (storeResponse.ResponseBody != null) @@ -117,12 +118,18 @@ internal static ResponseMessage ToCosmosResponseMessage(this StoreResponse store responseMessage.Content = storeResponse.ResponseBody; } + return responseMessage; + } + + internal static Headers ToCosmosHeaders(this StoreResponse storeResponse) + { + Headers headers = new Headers(); for (int i = 0; i < storeResponse.ResponseHeaderNames.Length; i++) { - responseMessage.Headers.Add(storeResponse.ResponseHeaderNames[i], storeResponse.ResponseHeaderValues[i]); + headers.Add(storeResponse.ResponseHeaderNames[i], storeResponse.ResponseHeaderValues[i]); } - return responseMessage; + return headers; } internal static void TraceException(Exception exception) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index c0502d1f30..8d4cee1a2a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -38,13 +38,12 @@ public void VerifyNegativeCosmosQueryResponseStream() string errorMessage = "TestErrorMessage"; string activityId = "TestActivityId"; double requestCharge = 42.42; - + CosmosException cosmosException = new Cosmos.Resource.CosmosExceptions.BadRequestException(errorMessage); CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(); QueryResponse queryResponse = QueryResponse.CreateFailure( statusCode: HttpStatusCode.NotFound, - errorMessage: errorMessage, + cosmosException: cosmosException, requestMessage: null, - error: null, responseHeaders: new CosmosQueryResponseMessageHeaders( null, null, @@ -57,7 +56,7 @@ public void VerifyNegativeCosmosQueryResponseStream() diagnostics: diagnostics); Assert.AreEqual(HttpStatusCode.NotFound, queryResponse.StatusCode); - Assert.AreEqual(errorMessage, queryResponse.ErrorMessage); + Assert.AreEqual(cosmosException.ToString(), queryResponse.ErrorMessage); Assert.AreEqual(requestCharge, queryResponse.Headers.RequestCharge); Assert.AreEqual(activityId, queryResponse.Headers.ActivityId); Assert.AreEqual(diagnostics, queryResponse.Diagnostics); @@ -283,7 +282,7 @@ public async Task TestCosmosQueryPartitionKeyDefinition() QueryResponseCore queryResponse = await context.ExecuteNextAsync(cancellationtoken); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsTrue(queryResponse.ErrorMessage.Contains(exceptionMessage), "response error message did not contain the proper substring."); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(exceptionMessage), "response error message did not contain the proper substring."); } private async Task<(IList components, QueryResponseCore response)> GetAllExecutionComponents() @@ -363,7 +362,17 @@ public async Task TestCosmosQueryPartitionKeyDefinition() QueryResponseCore failure = QueryResponseCore.CreateFailure( System.Net.HttpStatusCode.Unauthorized, SubStatusCodes.PartitionKeyMismatch, - "Random error message", + new CosmosException( + HttpStatusCode.Unauthorized, + "Random error message", + default, + default, + "TestActivityId", + 42.89, + default, + default, + default, + default), 42.89, "TestActivityId", diagnostics); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs index c945786868..a61930732d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs @@ -19,6 +19,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Query.Core.ExecutionContext.Parallel; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -269,7 +270,8 @@ void produceAsyncCompleteCallback( Task.FromResult(QueryResponseCore.CreateFailure( statusCode: HttpStatusCode.InternalServerError, subStatusCodes: null, - errorMessage: "Error message", + cosmosException: new Cosmos.Resource.CosmosExceptions.InternalServerErrorException( + "Error message"), requestCharge: 10.2, activityId: Guid.NewGuid().ToString(), diagnostics: pageDiagnostics))); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs index 95f0be3908..be8742417f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs @@ -212,7 +212,7 @@ public async Task TestCosmosCrossPartitionQueryExecutionContextWithFailuresAsync Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.ErrorMessage); + Assert.IsNull(failure.Value.CosmosException); Assert.AreEqual(allItems.Count, itemsRead.Count); List exepected = allItems.OrderBy(x => x.id).ToList(); @@ -478,7 +478,7 @@ public async Task TestCosmosOrderByQueryExecutionContextWithFailurePageAsync(boo Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.ErrorMessage); + Assert.IsNull(failure.Value.CosmosException); Assert.AreEqual(0 /*We don't get any items, since we don't buffer the failure anymore*/, itemsRead.Count); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index 9a64b5cd68..17823ce0d3 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.Query using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using VisualStudio.TestTools.UnitTesting; [TestClass] @@ -18,12 +19,11 @@ public class QueryResponseFactoryTests [TestMethod] public void CosmosException() { - CosmosException cosmosException = new CosmosException( - statusCode: HttpStatusCode.BadRequest, + CosmosException cosmosException = new BadRequestException( message: "asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(cosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] @@ -32,7 +32,7 @@ public void DocumentClientException() Documents.DocumentClientException documentClientException = new Documents.RequestRateTooLargeException("asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(documentClientException); Assert.AreEqual((HttpStatusCode)429, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] @@ -48,7 +48,7 @@ public void QueryException() QueryException queryException = new MalformedContinuationTokenException(); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(queryException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); + Assert.IsNotNull(queryResponse.CosmosException); } [TestMethod] @@ -58,8 +58,8 @@ public void ExceptionFromTryCatch() TryCatch tryCatch = TryCatch.FromException(queryException); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); - Assert.IsNotNull(queryResponse.ErrorMessage); - Assert.IsTrue(queryResponse.ErrorMessage.Contains(nameof(ExceptionFromTryCatch))); + Assert.IsNotNull(queryResponse.CosmosException); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(ExceptionFromTryCatch))); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs index 06ca77ca80..1fd97630f0 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs @@ -146,9 +146,10 @@ public static QueryResponseCore CreateFailureResponse( SubStatusCodes subStatusCodes, string errorMessage) { + string acitivityId = Guid.NewGuid().ToString(); CosmosDiagnosticsContext diagnosticsContext = CosmosDiagnosticsContext.Create(); diagnosticsContext.AddDiagnosticsInternal(new PointOperationStatistics( - Guid.NewGuid().ToString(), + acitivityId, System.Net.HttpStatusCode.Gone, subStatusCode: SubStatusCodes.PartitionKeyRangeGone, requestCharge: 10.4, @@ -170,9 +171,19 @@ public static QueryResponseCore CreateFailureResponse( QueryResponseCore splitResponse = QueryResponseCore.CreateFailure( statusCode: httpStatusCode, subStatusCodes: subStatusCodes, - errorMessage: errorMessage, + cosmosException: new CosmosException( + httpStatusCode, + errorMessage, + (int)subStatusCodes, + new System.Diagnostics.StackTrace(), + acitivityId, + 10.4, + default, + default, + diagnosticsContext: diagnosticsContext, + default), requestCharge: 10.4, - activityId: Guid.NewGuid().ToString(), + activityId: acitivityId, diagnostics: diagnostics); return splitResponse; From eb371215c1ff0efd880c7599a7f05cf0d73a67b9 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 14 Feb 2020 14:35:31 -0800 Subject: [PATCH 05/25] Fixed build issue --- .../ChangeFeedResultSetIteratorTests.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 87680a5061..9686c3cd05 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Cosmos.Tests using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query; using Microsoft.Azure.Cosmos.Query.Core.ContinuationTokens; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; using Newtonsoft.Json; @@ -34,9 +35,15 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() ResponseMessage firstResponse = new ResponseMessage(HttpStatusCode.NotModified); firstResponse.Headers.ETag = "FirstContinuation"; - ResponseMessage secondResponse = new ResponseMessage(HttpStatusCode.NotFound); - secondResponse.Headers.ETag = "ShouldNotContainThis"; - secondResponse.ErrorMessage = "something"; + ResponseMessage secondResponse = new ResponseMessage( + statusCode: HttpStatusCode.NotFound, + requestMessage: null, + headers: new Headers() + { + ETag = "ShouldNotContainThis" + }, + cosmosException: new NotFoundException("something"), + diagnostics: CosmosDiagnosticsContext.Create()); mockContext.SetupSequence(x => x.ProcessResourceOperationAsync( It.IsAny(), From e8422e30a74c16f2effac1997e3dfe67df552105 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Tue, 18 Feb 2020 14:53:40 -0800 Subject: [PATCH 06/25] Renamed exception to add Cosmos to avoid confusion with Document name space versions. --- .../Core/ExecutionContext/ItemProducers/ItemProducer.cs | 2 +- .../src/Query/Core/QueryResponseFactory.cs | 8 ++++---- ...adRequestException.cs => CosmosBadRequestException.cs} | 4 ++-- .../src/Resource/CosmosExceptions/CosmosException.cs | 2 +- .../Resource/CosmosExceptions/CosmosExceptionFactory.cs | 8 ++++---- ...Exception.cs => CosmosInternalServerErrorException.cs} | 4 ++-- .../{NotFoundException.cs => CosmosNotFoundException.cs} | 6 +++--- ...meoutException.cs => CosmosRequestTimeoutException.cs} | 6 +++--- ...{ThrottledException.cs => CosmosThrottledException.cs} | 4 ++-- Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs | 3 ++- .../CosmosContainerTests.cs | 3 ++- .../ChangeFeedResultSetIteratorTests.cs | 2 +- .../Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs | 2 +- .../Query/ItemProducerTreeUnitTests.cs | 2 +- .../Query/QueryPipelineMockTests.cs | 4 ++-- .../Query/QueryResponseFactoryTests.cs | 2 +- 16 files changed, 32 insertions(+), 30 deletions(-) rename Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/{BadRequestException.cs => CosmosBadRequestException.cs} (90%) rename Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/{InternalServerErrorException.cs => CosmosInternalServerErrorException.cs} (88%) rename Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/{NotFoundException.cs => CosmosNotFoundException.cs} (87%) rename Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/{RequestTimeoutException.cs => CosmosRequestTimeoutException.cs} (86%) rename Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/{ThrottledException.cs => CosmosThrottledException.cs} (90%) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs index 74341dc9c2..aad0449227 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs @@ -291,7 +291,7 @@ public async Task BufferMoreDocumentsAsync(CancellationToken token) feedResponse = QueryResponseCore.CreateFailure( statusCode: (System.Net.HttpStatusCode)429, subStatusCodes: null, - cosmosException: new ThrottledException("Request Rate Too Large"), + cosmosException: new CosmosThrottledException("Request Rate Too Large"), requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 6df8e743cb..4d3cc6a754 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -110,7 +110,7 @@ private QueryExceptionConverter() public override CosmosException Visit(MalformedContinuationTokenException malformedContinuationTokenException) { - return new BadRequestException( + return new CosmosBadRequestException( message: malformedContinuationTokenException.Message, stackTrace: new StackTrace(malformedContinuationTokenException), innerException: malformedContinuationTokenException); @@ -118,14 +118,14 @@ public override CosmosException Visit(MalformedContinuationTokenException malfor public override CosmosException Visit(UnexpectedQueryPartitionProviderException unexpectedQueryPartitionProviderException) { - return new InternalServerErrorException( - message: $"{nameof(InternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", + return new CosmosInternalServerErrorException( + message: $"{nameof(CosmosInternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", innerException: unexpectedQueryPartitionProviderException); } public override CosmosException Visit(ExpectedQueryPartitionProviderException expectedQueryPartitionProviderException) { - return new BadRequestException( + return new CosmosBadRequestException( message: expectedQueryPartitionProviderException.Message, stackTrace: new StackTrace(expectedQueryPartitionProviderException), innerException: expectedQueryPartitionProviderException); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs similarity index 90% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs index a78137ad16..184fb816c5 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/BadRequestException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs @@ -8,9 +8,9 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions using System.Diagnostics; using System.Net; - internal sealed class BadRequestException : CosmosHttpException + internal sealed class CosmosBadRequestException : CosmosHttpException { - internal BadRequestException( + internal CosmosBadRequestException( string message, int subStatusCode = default, StackTrace stackTrace = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 49de65e0e3..eb4d9b2b9a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -28,7 +28,7 @@ internal CosmosException( Headers headers, CosmosDiagnosticsContext diagnosticsContext, Exception innerException) - : base(message, innerException) + : base(message) { this.stackTrace = stackTrace; this.ActivityId = activityId; diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 4bbcc9d12d..656614acff 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -182,7 +182,7 @@ internal static CosmosException Create( switch (statusCode) { case HttpStatusCode.InternalServerError: - return new InternalServerErrorException( + return new CosmosInternalServerErrorException( message, subStatusCode, stackTrace, @@ -193,7 +193,7 @@ internal static CosmosException Create( diagnosticsContext, innerException); case HttpStatusCode.BadRequest: - return new BadRequestException( + return new CosmosBadRequestException( message, subStatusCode, stackTrace, @@ -204,7 +204,7 @@ internal static CosmosException Create( diagnosticsContext, innerException); case HttpStatusCode.RequestTimeout: - return new RequestTimeoutException( + return new CosmosRequestTimeoutException( message, subStatusCode, stackTrace, @@ -215,7 +215,7 @@ internal static CosmosException Create( diagnosticsContext, innerException); case (HttpStatusCode)429: - return new ThrottledException( + return new CosmosThrottledException( message, subStatusCode, stackTrace, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs similarity index 88% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs index f4c53b533e..83d07f7119 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/InternalServerErrorException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs @@ -8,9 +8,9 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions using System.Diagnostics; using System.Net; - internal sealed class InternalServerErrorException : CosmosHttpException + internal sealed class CosmosInternalServerErrorException : CosmosHttpException { - internal InternalServerErrorException( + internal CosmosInternalServerErrorException( string message, int subStatusCode = default, StackTrace stackTrace = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs similarity index 87% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs index 6fbc8aae17..b1bde3047f 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/NotFoundException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs @@ -8,14 +8,14 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions using System.Diagnostics; using System.Net; - internal sealed class NotFoundException : CosmosHttpException + internal sealed class CosmosNotFoundException : CosmosHttpException { - public NotFoundException(string message) + public CosmosNotFoundException(string message) : base(statusCode: HttpStatusCode.NotFound, message: message) { } - internal NotFoundException( + internal CosmosNotFoundException( string message, int subStatusCode = default, StackTrace stackTrace = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs similarity index 86% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs index a990834904..76d5310838 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/RequestTimeoutException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs @@ -8,14 +8,14 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions using System.Diagnostics; using System.Net; - internal sealed class RequestTimeoutException : CosmosHttpException + internal sealed class CosmosRequestTimeoutException : CosmosHttpException { - public RequestTimeoutException(string message) + public CosmosRequestTimeoutException(string message) : base(statusCode: HttpStatusCode.RequestTimeout, message: message) { } - internal RequestTimeoutException( + internal CosmosRequestTimeoutException( string message, int subStatusCode = default, StackTrace stackTrace = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs similarity index 90% rename from Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs rename to Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs index afc58684c7..78205d3d47 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/ThrottledException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs @@ -8,9 +8,9 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions using System.Diagnostics; using System.Net; - internal sealed class ThrottledException : CosmosHttpException + internal sealed class CosmosThrottledException : CosmosHttpException { - internal ThrottledException( + internal CosmosThrottledException( string message, int subStatusCode = default, StackTrace stackTrace = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs index 95a080e904..7893c7d265 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Handlers; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; internal class CosmosOffers @@ -145,7 +146,7 @@ private async Task GetOfferV2Async( if (offerV2 == null && failIfNotConfigured) { - throw new NotFoundException( + throw (CosmosException)new CosmosNotFoundException( $"Throughput is not configured for {targetRID}"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 1dbdb6dbef..52d42af423 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -10,6 +10,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using System.Linq; using System.Net; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Newtonsoft.Json.Linq; @@ -567,7 +568,7 @@ public async Task StreamPartitionedCreateWithPathDelete() } [TestMethod] - [ExpectedException(typeof(CosmosException))] + [ExpectedException(typeof(CosmosBadRequestException))] public async Task NegativePartitionedCreateDelete() { string containerName = Guid.NewGuid().ToString(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 9686c3cd05..717546a838 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -42,7 +42,7 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() { ETag = "ShouldNotContainThis" }, - cosmosException: new NotFoundException("something"), + cosmosException: new CosmosNotFoundException("something"), diagnostics: CosmosDiagnosticsContext.Create()); mockContext.SetupSequence(x => x.ProcessResourceOperationAsync( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index 8d4cee1a2a..dfa43071be 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -38,7 +38,7 @@ public void VerifyNegativeCosmosQueryResponseStream() string errorMessage = "TestErrorMessage"; string activityId = "TestActivityId"; double requestCharge = 42.42; - CosmosException cosmosException = new Cosmos.Resource.CosmosExceptions.BadRequestException(errorMessage); + CosmosException cosmosException = new Cosmos.Resource.CosmosExceptions.CosmosBadRequestException(errorMessage); CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(); QueryResponse queryResponse = QueryResponse.CreateFailure( statusCode: HttpStatusCode.NotFound, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs index a61930732d..5f217e0a8e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs @@ -270,7 +270,7 @@ void produceAsyncCompleteCallback( Task.FromResult(QueryResponseCore.CreateFailure( statusCode: HttpStatusCode.InternalServerError, subStatusCodes: null, - cosmosException: new Cosmos.Resource.CosmosExceptions.InternalServerErrorException( + cosmosException: new Cosmos.Resource.CosmosExceptions.CosmosInternalServerErrorException( "Error message"), requestCharge: 10.2, activityId: Guid.NewGuid().ToString(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs index be8742417f..06e7ead29d 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryPipelineMockTests.cs @@ -212,7 +212,7 @@ public async Task TestCosmosCrossPartitionQueryExecutionContextWithFailuresAsync Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.CosmosException); + Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(allItems.Count, itemsRead.Count); List exepected = allItems.OrderBy(x => x.id).ToList(); @@ -478,7 +478,7 @@ public async Task TestCosmosOrderByQueryExecutionContextWithFailurePageAsync(boo Assert.IsNotNull(failure); Assert.AreEqual((HttpStatusCode)429, failure.Value.StatusCode); - Assert.IsNull(failure.Value.CosmosException); + Assert.IsNotNull(failure.Value.CosmosException.ToString()); Assert.AreEqual(0 /*We don't get any items, since we don't buffer the failure anymore*/, itemsRead.Count); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index 17823ce0d3..f585a42e7a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -19,7 +19,7 @@ public class QueryResponseFactoryTests [TestMethod] public void CosmosException() { - CosmosException cosmosException = new BadRequestException( + CosmosException cosmosException = new CosmosBadRequestException( message: "asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(cosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); From f783e547dee913444a43a0f2b0d47e4537e7300b Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Wed, 19 Feb 2020 12:54:34 -0800 Subject: [PATCH 07/25] Fixed exception handling and updated tests. --- .../Core/QueryPlan/QueryPlanRetriever.cs | 6 +-- .../CosmosExceptions/CosmosException.cs | 2 +- .../CosmosExceptionFactory.cs | 49 ++++++++++++++++--- .../CrossPartitionQueryTests.cs | 5 +- .../CosmosExceptionTests.cs | 36 +++++++++++++- 5 files changed, 83 insertions(+), 15 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs index f107b4bd97..1ff110fa39 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs @@ -71,14 +71,14 @@ public static async Task GetQueryPlanWithServiceI throw CosmosExceptionFactory.Create( statusCode: System.Net.HttpStatusCode.BadRequest, subStatusCode: default, - message: tryGetQueryPlan.Exception.Message, - stackTrace: default, + message: tryGetQueryPlan.Exception.ToString(), + stackTrace: new System.Diagnostics.StackTrace(tryGetQueryPlan.Exception), activityId: default, requestCharge: default, retryAfter: default, headers: default, diagnosticsContext: default, - innerException: tryGetQueryPlan.Exception); + innerException: default); } return tryGetQueryPlan.Result; diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index eb4d9b2b9a..49de65e0e3 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -28,7 +28,7 @@ internal CosmosException( Headers headers, CosmosDiagnosticsContext diagnosticsContext, Exception innerException) - : base(message) + : base(message, innerException) { this.stackTrace = stackTrace; this.ActivityId = activityId; diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 656614acff..13fb244720 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -73,29 +73,51 @@ internal static CosmosException Create( internal static CosmosException Create( ResponseMessage responseMessage) { + // If there is no content and there is cosmos exception + // then use the existing exception + if (responseMessage.Content == null + && responseMessage.CosmosException != null) + { + return responseMessage.CosmosException; + } + + // If content was added after the response message + // creation the exception should be updated. string errorMessage = responseMessage.ErrorMessage; - if (string.IsNullOrEmpty(errorMessage)) + string contentMessage = GetErrorMessageFromStream(responseMessage.Content); + if (!string.IsNullOrEmpty(contentMessage)) { - if (responseMessage.Headers.ContentLengthAsLong > 0) + if (string.IsNullOrEmpty(errorMessage)) { - using (StreamReader responseReader = new StreamReader(responseMessage.Content)) - { - errorMessage = responseReader.ReadToEnd(); - } + errorMessage = contentMessage; + } + else + { + errorMessage = $"Error Message: {errorMessage}; Content {contentMessage};"; } } + StackTrace stackTrace; + if (responseMessage.CosmosException != null) + { + stackTrace = new StackTrace(responseMessage.CosmosException); + } + else + { + stackTrace = new StackTrace(1); + } + return CosmosExceptionFactory.Create( responseMessage.StatusCode, (int)responseMessage.Headers.SubStatusCode, errorMessage, - new StackTrace(1), + stackTrace, responseMessage.Headers.ActivityId, responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers, responseMessage.DiagnosticsContext, - null); + responseMessage.CosmosException?.InnerException); } internal static CosmosException Create( @@ -181,6 +203,17 @@ internal static CosmosException Create( { switch (statusCode) { + case HttpStatusCode.NotFound: + return new CosmosNotFoundException( + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); case HttpStatusCode.InternalServerError: return new CosmosInternalServerErrorException( message, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs index b2b1989397..62d6d578ba 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs @@ -27,6 +27,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.Azure.Documents.Routing; @@ -794,9 +795,9 @@ private async Task TestBadQueriesOverMultiplePartitionsHelper(Container containe Assert.Fail($"Expected {nameof(CosmosException)}"); } - catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) + catch (CosmosBadRequestException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) { - Assert.IsTrue(exception.Message.Contains(@"{""errors"":[{""severity"":""Error"",""location"":{""start"":27,""end"":28},""code"":""SC2001"",""message"":""Identifier 'a' could not be resolved.""}]}"), + Assert.IsTrue(exception.Message.Contains(@"Identifier 'a' could not be resolved."), exception.Message); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs index 7b0b58af51..12ed439f00 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -12,6 +12,7 @@ namespace Microsoft.Azure.Cosmos using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Common; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Cosmos.Routing; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -32,7 +33,7 @@ public void EnsureSuccessStatusCode_DontThrowOnSuccess() public void EnsureSuccessStatusCode_ThrowsOnFailure() { ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.NotFound); - Assert.ThrowsException(() => responseMessage.EnsureSuccessStatusCode()); + Assert.ThrowsException(() => responseMessage.EnsureSuccessStatusCode()); } [TestMethod] @@ -111,5 +112,38 @@ public void VerifyDocumentClientExceptionToResponseMessage() Assert.IsTrue(responseMessage.ErrorMessage.Contains(errorMessage)); Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyDocumentClientExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); } + + [TestMethod] + public void VerifyTransportExceptionToResponseMessage() + { + string errorMessage = "Test Exception!"; + DocumentClientException dce = null; + TransportException transportException = new TransportException( + errorCode: TransportErrorCode.ConnectionBroken, + innerException: null, + activityId: Guid.NewGuid(), + requestUri: new Uri("https://localhost"), + sourceDescription: "The SourceDescription", + userPayload: true, + payloadSent: true); + + try + { + throw new ServiceUnavailableException( + message: errorMessage, + innerException: transportException); + } + catch (DocumentClientException exception) + { + dce = exception; + } + + ResponseMessage responseMessage = dce.ToCosmosResponseMessage(null); + Assert.IsFalse(responseMessage.IsSuccessStatusCode); + Assert.AreEqual(HttpStatusCode.ServiceUnavailable, responseMessage.StatusCode); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(errorMessage)); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(transportException.ToString())); + Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyTransportExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); + } } } From 61905b19bd6216a0fa80481765c863359da85893 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 20 Feb 2020 07:08:57 -0800 Subject: [PATCH 08/25] Fixed tests --- .../ChangeFeed/SmokeTests.cs | 3 ++- .../DotNetSDKAPI.json | 22 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs index b15023554a..8777c4e365 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests.ChangeFeed using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Query.Core; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] @@ -75,7 +76,7 @@ public async Task NotExistentLeaseContainer() .WithInstanceName("random") .WithLeaseContainer(notFoundContainer).Build(); - CosmosException exception = await Assert.ThrowsExceptionAsync(() => processor.StartAsync()); + CosmosException exception = await Assert.ThrowsExceptionAsync(() => processor.StartAsync()); Assert.AreEqual(HttpStatusCode.NotFound, exception.StatusCode); } 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 c455c8fbf1..88b93e2a55 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DotNetSDKAPI.json @@ -1756,11 +1756,9 @@ "Attributes": [], "MethodInfo": null }, - "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "Microsoft.Azure.Cosmos.CosmosDiagnostics get_Diagnostics()" }, "Microsoft.Azure.Cosmos.Headers get_Headers()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { @@ -1818,11 +1816,21 @@ ], "MethodInfo": "System.String get_ResponseBody()" }, + "System.String get_StackTrace()": { + "Type": "Method", + "Attributes": [], + "MethodInfo": "System.String get_StackTrace()" + }, "System.String ResponseBody": { "Type": "Property", "Attributes": [], "MethodInfo": null }, + "System.String StackTrace": { + "Type": "Property", + "Attributes": [], + "MethodInfo": null + }, "System.String ToString()": { "Type": "Method", "Attributes": [], @@ -5231,11 +5239,9 @@ "Attributes": [], "MethodInfo": "System.String get_ContinuationToken()" }, - "System.String get_ErrorMessage()[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]": { + "System.String get_ErrorMessage()": { "Type": "Method", - "Attributes": [ - "CompilerGeneratedAttribute" - ], + "Attributes": [], "MethodInfo": "System.String get_ErrorMessage()" }, "Void .ctor()": { From 23a7a99f925ad95cf51f8238b67f5e6ddc9cf2fe Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 20 Feb 2020 14:00:43 -0800 Subject: [PATCH 09/25] Fixed ExceptionWithStackTraceException to use original exception stack trace. --- .../ExceptionWithStackTraceException.cs | 5 +++ .../src/Query/Core/QueryResponseFactory.cs | 37 ++++++++++++++++++- .../CosmosExceptions/CosmosException.cs | 10 +++-- .../Query/QueryResponseFactoryTests.cs | 32 ++++++++++++++-- 4 files changed, 76 insertions(+), 8 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs index be9b12bce6..98dd77e08e 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs @@ -40,6 +40,11 @@ public ExceptionWithStackTraceException( public override string StackTrace => this.stackTrace.ToString(); + public StackTrace GetStackTrace() + { + return this.stackTrace; + } + public override string ToString() { // core2.x does not honor the StackTrace property in .ToString() (it uses the private internal stack trace). diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 4d3cc6a754..d98461d0f4 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -6,10 +6,12 @@ namespace Microsoft.Azure.Cosmos.Query.Core { using System; using System.Diagnostics; + using System.Runtime.CompilerServices; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; + using Microsoft.Azure.Documents; internal static class QueryResponseFactory { @@ -33,7 +35,7 @@ public static QueryResponseCore CreateFromException(Exception exception) } else if (exception is ExceptionWithStackTraceException exceptionWithStackTrace) { - return QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); + queryResponseCore = QueryResponseFactory.CreateFromExceptionWithStackTrace(exceptionWithStackTrace); } else { @@ -100,6 +102,39 @@ private static QueryResponseCore CreateFromDocumentClientException(Microsoft.Azu return queryResponseCore; } + private static QueryResponseCore CreateFromExceptionWithStackTrace(ExceptionWithStackTraceException exceptionWithStackTrace) + { + // Use the original stack trace from the inner exception. + if (exceptionWithStackTrace.InnerException is DocumentClientException + || exceptionWithStackTrace.InnerException is CosmosException) + { + return QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); + } + + QueryResponseCore queryResponseCore = QueryResponseFactory.CreateFromException(exceptionWithStackTrace.InnerException); + CosmosException cosmosException = queryResponseCore.CosmosException; + + queryResponseCore = QueryResponseCore.CreateFailure( + statusCode: queryResponseCore.StatusCode, + subStatusCodes: queryResponseCore.SubStatusCode, + cosmosException: CosmosExceptionFactory.Create( + cosmosException.StatusCode, + cosmosException.SubStatusCode, + cosmosException.Message, + exceptionWithStackTrace.GetStackTrace(), + cosmosException.ActivityId, + cosmosException.RequestCharge, + cosmosException.RetryAfter, + cosmosException.Headers, + cosmosException.DiagnosticsContext, + cosmosException.InnerException), + requestCharge: queryResponseCore.RequestCharge, + activityId: queryResponseCore.ActivityId, + diagnostics: queryResponseCore.Diagnostics); + + return queryResponseCore; + } + private sealed class QueryExceptionConverter : QueryExceptionVisitor { public static readonly QueryExceptionConverter Singleton = new QueryExceptionConverter(); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 49de65e0e3..285f8b0977 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -15,7 +15,6 @@ namespace Microsoft.Azure.Cosmos public class CosmosException : Exception { private readonly StackTrace stackTrace; - private readonly CosmosDiagnosticsContext diagnosticsContext; internal CosmosException( HttpStatusCode statusCodes, @@ -39,7 +38,7 @@ internal CosmosException( this.Headers = headers; // Always have a diagnostic context. A new diagnostic will have useful info like user agent - this.diagnosticsContext = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); + this.DiagnosticsContext = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); } /// @@ -64,6 +63,7 @@ public CosmosException( this.RequestCharge = requestCharge; this.ActivityId = activityId; this.Headers = new Headers(); + this.DiagnosticsContext = CosmosDiagnosticsContext.Create(); } /// @@ -112,7 +112,7 @@ public CosmosException( /// /// Gets the diagnostics for the request /// - public virtual CosmosDiagnostics Diagnostics => this.diagnosticsContext; + public virtual CosmosDiagnostics Diagnostics => this.DiagnosticsContext; /// public override string StackTrace @@ -130,6 +130,8 @@ public override string StackTrace } } + internal virtual CosmosDiagnosticsContext DiagnosticsContext { get; } + /// /// Try to get a header from the cosmos response message /// @@ -202,7 +204,7 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) requestMessage: request, cosmosException: this, statusCode: this.StatusCode, - diagnostics: this.diagnosticsContext); + diagnostics: this.DiagnosticsContext); } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index f585a42e7a..5cf58d4bd2 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -54,12 +54,38 @@ public void QueryException() [TestMethod] public void ExceptionFromTryCatch() { - QueryException queryException = new MalformedContinuationTokenException(); - TryCatch tryCatch = TryCatch.FromException(queryException); + TryCatch tryCatch = this.QueryExceptionHelper(new MalformedContinuationTokenException()); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); Assert.IsNotNull(queryResponse.CosmosException); - Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(ExceptionFromTryCatch))); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper))); + } + + [TestMethod] + public void ExceptionFromTryCatchWithCosmosException() + { + CosmosException cosmosException; + try + { + throw new CosmosBadRequestException("InternalServerTestMessage"); + } + catch (CosmosException ce) + { + cosmosException = ce; + } + + TryCatch tryCatch = this.QueryExceptionHelper(cosmosException); + QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); + Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); + Assert.IsNotNull(queryResponse.CosmosException); + + // Should preserve the original stack trace. + Assert.IsFalse(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper))); + } + + private TryCatch QueryExceptionHelper(Exception exception) + { + return TryCatch.FromException(exception); } } } From 97fae6353639f4e26e1fa6ebfdfbfa5cb684c41d Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 20 Feb 2020 14:22:51 -0800 Subject: [PATCH 10/25] Fixed merge conflicts with latest --- .../src/Encryption/EncryptionProcessor.cs | 3 ++- .../Resource/DataEncryptionKey/DataEncryptionKeyCore.cs | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs index 31b74e612f..ae5f82b77d 100644 --- a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Text; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -134,7 +135,7 @@ public async Task DecryptAsync( EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject(); if (encryptionProperties.EncryptionFormatVersion != 1) { - throw new CosmosException(HttpStatusCode.InternalServerError, $"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); + throw new CosmosInternalServerErrorException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); } DataEncryptionKeyCore tempDek = (DataEncryptionKeyInlineCore)database.GetDataEncryptionKey(id: "unknown"); diff --git a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs index dfc71ad6c1..7e13e9eb5b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs @@ -11,6 +11,7 @@ namespace Microsoft.Azure.Cosmos using System.Net; using System.Threading; using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; /// @@ -135,7 +136,7 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosException(HttpStatusCode.NotFound, ClientResources.DataEncryptionKeyNotFound, inner: ex); + throw new CosmosNotFoundException(ClientResources.DataEncryptionKeyNotFound, innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -172,7 +173,7 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosException(HttpStatusCode.NotFound, ClientResources.DataEncryptionKeyNotFound, inner: ex); + throw new CosmosNotFoundException(ClientResources.DataEncryptionKeyNotFound, innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -223,7 +224,7 @@ internal virtual byte[] GenerateKey(CosmosEncryptionAlgorithm encryptionAlgorith InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken); if (!roundTripResponse.RawDek.SequenceEqual(key)) { - throw new CosmosException(HttpStatusCode.BadRequest, ClientResources.KeyWrappingDidNotRoundtrip); + throw new CosmosBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip); } return (keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse); From 968fb9725e38382c7f72aef2afb629f2256556a4 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 20 Feb 2020 14:28:19 -0800 Subject: [PATCH 11/25] Added diagnostic context to exceptions. --- .../DataEncryptionKey/DataEncryptionKeyCore.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs index 7e13e9eb5b..852f616ef0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs @@ -136,7 +136,10 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosNotFoundException(ClientResources.DataEncryptionKeyNotFound, innerException: ex); + throw new CosmosNotFoundException( + ClientResources.DataEncryptionKeyNotFound, + diagnosticsContext: diagnosticsContext, + innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -173,7 +176,10 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosNotFoundException(ClientResources.DataEncryptionKeyNotFound, innerException: ex); + throw new CosmosNotFoundException( + ClientResources.DataEncryptionKeyNotFound, + diagnosticsContext: diagnosticsContext, + innerException: ex); } InMemoryRawDek inMemoryRawDek = await this.ClientContext.DekCache.GetOrAddRawDekAsync( @@ -224,7 +230,8 @@ internal virtual byte[] GenerateKey(CosmosEncryptionAlgorithm encryptionAlgorith InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken); if (!roundTripResponse.RawDek.SequenceEqual(key)) { - throw new CosmosBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip); + throw new CosmosBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip, + diagnosticsContext: diagnosticsContext); } return (keyWrapResponse.WrappedDataEncryptionKey, keyWrapResponse.EncryptionKeyWrapMetadata, roundTripResponse); From 1162ce12776cc4a3b06c7f76b2735e1d6f1be762 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 20 Feb 2020 14:51:00 -0800 Subject: [PATCH 12/25] Fixed test for retail build --- .../Query/QueryResponseFactoryTests.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index 5cf58d4bd2..d4892833d4 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.Query { using System; using System.Net; + using System.Runtime.CompilerServices; using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.Azure.Cosmos.Query.Core.Exceptions; using Microsoft.Azure.Cosmos.Query.Core.Monads; @@ -54,11 +55,12 @@ public void QueryException() [TestMethod] public void ExceptionFromTryCatch() { - TryCatch tryCatch = this.QueryExceptionHelper(new MalformedContinuationTokenException()); + TryCatch tryCatch = this.QueryExceptionHelper(new MalformedContinuationTokenException("TestMessage")); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(tryCatch.Exception); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); Assert.IsNotNull(queryResponse.CosmosException); - Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper))); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains("TestMessage")); + Assert.IsTrue(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper)), queryResponse.CosmosException.ToString()); } [TestMethod] @@ -80,9 +82,12 @@ public void ExceptionFromTryCatchWithCosmosException() Assert.IsNotNull(queryResponse.CosmosException); // Should preserve the original stack trace. - Assert.IsFalse(queryResponse.CosmosException.ToString().Contains(nameof(QueryExceptionHelper))); + string exceptionMessage = queryResponse.CosmosException.ToString(); + Assert.IsTrue(exceptionMessage.Contains("InternalServerTestMessage")); + Assert.IsFalse(exceptionMessage.Contains(nameof(QueryExceptionHelper))); } + [MethodImpl(MethodImplOptions.NoInlining)] private TryCatch QueryExceptionHelper(Exception exception) { return TryCatch.FromException(exception); From 32343f2af38bd14c7052147c36fde0a43d4201c4 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 21 Feb 2020 04:41:49 -0800 Subject: [PATCH 13/25] Updated error message field in linq tests --- .../BaselineTest/BaselineTests.cs | 4 +++- .../LinqGeneralBaselineTests.TestThenByTranslation.xml | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs index c41c2de489..e94c04fca7 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/BaselineTests.cs @@ -139,7 +139,9 @@ public void ExecuteTestSuite(IEnumerable inputs, [CallerMemberName] stri matched, $@" Expected: {baselineTextSuffix}, - Actual: {outputTextSuffix}"); + Actual: {outputTextSuffix}, + OutputPath: {outputPath}, + BaselinePath: {baselinePath}"); } /// diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml index 1705c5db5b..7b5f11cb11 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/BaselineTest/TestBaseline/LinqGeneralBaselineTests.TestThenByTranslation.xml @@ -415,7 +415,7 @@ FROM ( ) AS r1 ORDER BY r1["FamilyId"] ASC, r1["FamilyNumber"] ASC ]]> - + @@ -467,7 +467,7 @@ FROM ( ) AS r0 ORDER BY r0["FamilyId"] ASC, r0["FamilyNumber"] ASC ]]> - + From 064c1f523a605577244996a7e2f6a860467f4024 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 21 Feb 2020 05:49:30 -0800 Subject: [PATCH 14/25] Converted the stack trace to a string. Removed creating the stack trace for exception less path. --- .../ExceptionWithStackTraceException.cs | 5 -- .../Core/QueryPlan/QueryPlanRetriever.cs | 2 +- .../src/Query/Core/QueryResponseFactory.cs | 8 +-- .../CosmosBadRequestException.cs | 2 +- .../CosmosExceptions/CosmosException.cs | 9 ++- .../CosmosExceptionFactory.cs | 14 ++--- .../CosmosExceptions/CosmosHttpException.cs | 2 +- .../CosmosInternalServerErrorException.cs | 2 +- .../CosmosNotFoundException.cs | 2 +- .../CosmosRequestTimeoutException.cs | 2 +- .../CosmosThrottledException.cs | 2 +- .../CosmosExceptionTests.cs | 56 +++++++++++++++++++ .../Query/QueryResponseMessageFactory.cs | 4 +- 13 files changed, 80 insertions(+), 30 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs index 98dd77e08e..be9b12bce6 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/Monads/ExceptionWithStackTraceException.cs @@ -40,11 +40,6 @@ public ExceptionWithStackTraceException( public override string StackTrace => this.stackTrace.ToString(); - public StackTrace GetStackTrace() - { - return this.stackTrace; - } - public override string ToString() { // core2.x does not honor the StackTrace property in .ToString() (it uses the private internal stack trace). diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs index 1ff110fa39..b2d4637107 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs @@ -72,7 +72,7 @@ public static async Task GetQueryPlanWithServiceI statusCode: System.Net.HttpStatusCode.BadRequest, subStatusCode: default, message: tryGetQueryPlan.Exception.ToString(), - stackTrace: new System.Diagnostics.StackTrace(tryGetQueryPlan.Exception), + stackTrace: tryGetQueryPlan.Exception.StackTrace, activityId: default, requestCharge: default, retryAfter: default, diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index d98461d0f4..167d137cc6 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -50,7 +50,7 @@ public static QueryResponseCore CreateFromException(Exception exception) statusCode: System.Net.HttpStatusCode.InternalServerError, subStatusCode: default, message: exception.Message, - stackTrace: new System.Diagnostics.StackTrace(exception), + stackTrace: exception.StackTrace, activityId: QueryResponseCore.EmptyGuidString, requestCharge: 0, retryAfter: null, @@ -121,7 +121,7 @@ private static QueryResponseCore CreateFromExceptionWithStackTrace(ExceptionWith cosmosException.StatusCode, cosmosException.SubStatusCode, cosmosException.Message, - exceptionWithStackTrace.GetStackTrace(), + exceptionWithStackTrace.StackTrace, cosmosException.ActivityId, cosmosException.RequestCharge, cosmosException.RetryAfter, @@ -147,7 +147,7 @@ public override CosmosException Visit(MalformedContinuationTokenException malfor { return new CosmosBadRequestException( message: malformedContinuationTokenException.Message, - stackTrace: new StackTrace(malformedContinuationTokenException), + stackTrace: malformedContinuationTokenException.StackTrace, innerException: malformedContinuationTokenException); } @@ -162,7 +162,7 @@ public override CosmosException Visit(ExpectedQueryPartitionProviderException ex { return new CosmosBadRequestException( message: expectedQueryPartitionProviderException.Message, - stackTrace: new StackTrace(expectedQueryPartitionProviderException), + stackTrace: expectedQueryPartitionProviderException.StackTrace, innerException: expectedQueryPartitionProviderException); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs index 184fb816c5..c84ad2275a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs @@ -13,7 +13,7 @@ internal sealed class CosmosBadRequestException : CosmosHttpException internal CosmosBadRequestException( string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 285f8b0977..8e0c8915fd 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos { using System; - using System.Diagnostics; using System.Net; using System.Text; @@ -14,13 +13,13 @@ namespace Microsoft.Azure.Cosmos /// public class CosmosException : Exception { - private readonly StackTrace stackTrace; + private readonly string stackTrace; internal CosmosException( HttpStatusCode statusCodes, string message, int subStatusCode, - StackTrace stackTrace, + string stackTrace, string activityId, double requestCharge, TimeSpan? retryAfter, @@ -57,7 +56,7 @@ public CosmosException( double requestCharge) : base(message) { - this.stackTrace = new StackTrace(-1); + this.stackTrace = null; this.SubStatusCode = subStatusCode; this.StatusCode = statusCode; this.RequestCharge = requestCharge; @@ -121,7 +120,7 @@ public override string StackTrace { if (this.stackTrace != null) { - return this.stackTrace.ToString(); + return this.stackTrace; } else { diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 13fb244720..c3099bf279 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -43,7 +43,7 @@ internal static CosmosException Create( httpStatusCode, (int)dce.GetSubStatus(), dce.Message, - new StackTrace(dce), + dce.StackTrace, dce.ActivityId, dce.RequestCharge, dce.RetryAfter, @@ -61,7 +61,7 @@ internal static CosmosException Create( statusCode: statusCode, subStatusCode: default, message: errorMessage, - stackTrace: new StackTrace(1), + stackTrace: null, activityId: requestMessage?.Headers?.ActivityId, requestCharge: 0, retryAfter: default, @@ -97,14 +97,14 @@ internal static CosmosException Create( } } - StackTrace stackTrace; + string stackTrace; if (responseMessage.CosmosException != null) { - stackTrace = new StackTrace(responseMessage.CosmosException); + stackTrace = responseMessage.CosmosException.StackTrace; } else { - stackTrace = new StackTrace(1); + stackTrace = null; } return CosmosExceptionFactory.Create( @@ -141,7 +141,7 @@ internal static CosmosException Create( storeResponse.StatusCode, (int)headers.SubStatusCode, errorMessage, - new StackTrace(-1), + null, headers.ActivityId, headers.RequestCharge, headers.RetryAfter, @@ -193,7 +193,7 @@ internal static CosmosException Create( HttpStatusCode statusCode, int subStatusCode, string message, - StackTrace stackTrace, + string stackTrace, string activityId, double requestCharge, TimeSpan? retryAfter, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs index 92dd1da418..ee33416ad1 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs @@ -14,7 +14,7 @@ internal CosmosHttpException( HttpStatusCode statusCode, string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs index 83d07f7119..a13a14625b 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs @@ -13,7 +13,7 @@ internal sealed class CosmosInternalServerErrorException : CosmosHttpException internal CosmosInternalServerErrorException( string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs index b1bde3047f..854e3d8dda 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs @@ -18,7 +18,7 @@ public CosmosNotFoundException(string message) internal CosmosNotFoundException( string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs index 76d5310838..ad50b7f415 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs @@ -18,7 +18,7 @@ public CosmosRequestTimeoutException(string message) internal CosmosRequestTimeoutException( string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs index 78205d3d47..2e408c69cf 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs @@ -13,7 +13,7 @@ internal sealed class CosmosThrottledException : CosmosHttpException internal CosmosThrottledException( string message, int subStatusCode = default, - StackTrace stackTrace = default, + string stackTrace = default, string activityId = default, double requestCharge = default, TimeSpan? retryAfter = default, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs index 12ed439f00..d72a8eef0f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.Cosmos { using System; + using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; @@ -145,5 +146,60 @@ public void VerifyTransportExceptionToResponseMessage() Assert.IsTrue(responseMessage.ErrorMessage.Contains(transportException.ToString())); Assert.IsTrue(responseMessage.ErrorMessage.Contains("VerifyTransportExceptionToResponseMessage"), $"Message should have method name for the stack trace {responseMessage.ErrorMessage}"); } + + [TestMethod] + public void EnsureCorrectStatusCode() + { + string testMessage = "Test" + Guid.NewGuid().ToString(); + + List<(HttpStatusCode statusCode, CosmosException exception)> exceptionsToStatusCodes = new List<(HttpStatusCode, CosmosException)>() + { + (HttpStatusCode.NotFound, new CosmosNotFoundException(testMessage)), + (HttpStatusCode.InternalServerError, new CosmosInternalServerErrorException(testMessage)), + (HttpStatusCode.BadRequest, new CosmosBadRequestException(testMessage)), + (HttpStatusCode.RequestTimeout, new CosmosRequestTimeoutException(testMessage)), + ((HttpStatusCode)429, new CosmosThrottledException(testMessage)), + }; + + foreach((HttpStatusCode statusCode, CosmosException exception) item in exceptionsToStatusCodes) + { + this.ValidateExceptionInfo(item.exception, item.statusCode, testMessage); + } + } + + [TestMethod] + public void ValidateExceptionStackTraceHandling() + { + CosmosException cosmosException = new CosmosNotFoundException("TestMessage"); + Assert.AreEqual(null, cosmosException.StackTrace); + Assert.IsFalse(cosmosException.ToString().Contains(nameof(ValidateExceptionStackTraceHandling))); + try + { + throw cosmosException; + } + catch(CosmosException ce) + { + Assert.IsTrue(ce.StackTrace.Contains(nameof(ValidateExceptionStackTraceHandling)), ce.StackTrace); + } + + string stackTrace = "OriginalDocumentClientExceptionStackTrace"; + try + { + throw new CosmosNotFoundException("TestMessage", stackTrace: stackTrace); + } + catch (CosmosException ce) + { + Assert.AreEqual(stackTrace, ce.StackTrace); + } + } + + private void ValidateExceptionInfo( + CosmosException exception, + HttpStatusCode httpStatusCode, + string message) + { + Assert.AreEqual(httpStatusCode, exception.StatusCode); + Assert.IsTrue(exception.ToString().Contains(message)); + } } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs index 1fd97630f0..80d076b356 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs @@ -87,7 +87,7 @@ public static QueryResponseCore CreateQueryResponse( if (isOrderByQuery) { memoryStream = SerializeForOrderByQuery(items); - using(StreamReader sr = new StreamReader(SerializeForOrderByQuery(items))) + using (StreamReader sr = new StreamReader(SerializeForOrderByQuery(items))) { json = sr.ReadToEnd(); } @@ -175,7 +175,7 @@ public static QueryResponseCore CreateFailureResponse( httpStatusCode, errorMessage, (int)subStatusCodes, - new System.Diagnostics.StackTrace(), + new System.Diagnostics.StackTrace().ToString(), acitivityId, 10.4, default, From a41b6157b4317edb5ca55108de3612edd4c295fe Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 21 Feb 2020 06:09:47 -0800 Subject: [PATCH 15/25] Updated changelog --- changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.md b/changelog.md index 77177eaf10..e41d058084 100644 --- a/changelog.md +++ b/changelog.md @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- [#1213](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1213) CosmosException now returns the original stack trace. +- [#1213](https://github.com/Azure/azure-cosmos-dotnet-v3/pull/1213) ResponseMessage.ErrorMessage is now always correctly populated. There was bug in some scenarios where the error message was left in the content stream. + ## [3.6.0](https://www.nuget.org/packages/Microsoft.Azure.Cosmos/3.6.0) - 2020-01-23 ### Added From 0d2e9707243749db0772101a8ffdff06a3660817 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Tue, 25 Feb 2020 14:31:06 -0800 Subject: [PATCH 16/25] Removed typed exceptions to avoid exposing internal types. --- .../src/Encryption/EncryptionProcessor.cs | 2 +- .../ItemProducers/ItemProducer.cs | 2 +- .../src/Query/Core/QueryResponseFactory.cs | 8 +- .../CosmosBadRequestException.cs | 37 ---- .../CosmosExceptionFactory.cs | 203 ++++++++++++------ .../CosmosExceptions/CosmosHttpException.cs | 37 ---- .../CosmosInternalServerErrorException.cs | 37 ---- .../CosmosNotFoundException.cs | 42 ---- .../CosmosRequestTimeoutException.cs | 42 ---- .../CosmosThrottledException.cs | 37 ---- .../DataEncryptionKeyCore.cs | 6 +- .../src/Resource/Offer/CosmosOffers.cs | 2 +- .../ChangeFeed/SmokeTests.cs | 2 +- .../CosmosContainerTests.cs | 2 +- .../CrossPartitionQueryTests.cs | 2 +- .../ChangeFeedResultSetIteratorTests.cs | 2 +- .../CosmosExceptionTests.cs | 16 +- .../CosmosQueryUnitTests.cs | 3 +- .../Query/ItemProducerTreeUnitTests.cs | 2 +- .../Query/QueryResponseFactoryTests.cs | 4 +- 20 files changed, 159 insertions(+), 329 deletions(-) delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs delete mode 100644 Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs diff --git a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs index ae5f82b77d..da39bddbd0 100644 --- a/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs +++ b/Microsoft.Azure.Cosmos/src/Encryption/EncryptionProcessor.cs @@ -135,7 +135,7 @@ public async Task DecryptAsync( EncryptionProperties encryptionProperties = encryptionPropertiesJObj.ToObject(); if (encryptionProperties.EncryptionFormatVersion != 1) { - throw new CosmosInternalServerErrorException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); + throw CosmosExceptionFactory.CreateInternalServerErrorException($"Unknown encryption format version: {encryptionProperties.EncryptionFormatVersion}. Please upgrade your SDK to the latest version."); } DataEncryptionKeyCore tempDek = (DataEncryptionKeyInlineCore)database.GetDataEncryptionKey(id: "unknown"); diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs index aad0449227..0008c30e3e 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/ExecutionContext/ItemProducers/ItemProducer.cs @@ -291,7 +291,7 @@ public async Task BufferMoreDocumentsAsync(CancellationToken token) feedResponse = QueryResponseCore.CreateFailure( statusCode: (System.Net.HttpStatusCode)429, subStatusCodes: null, - cosmosException: new CosmosThrottledException("Request Rate Too Large"), + cosmosException: CosmosExceptionFactory.CreateThrottledException("Request Rate Too Large"), requestCharge: 0, activityId: QueryResponseCore.EmptyGuidString, diagnostics: QueryResponseCore.EmptyDiagnostics); diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 167d137cc6..7ef376a308 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -145,7 +145,7 @@ private QueryExceptionConverter() public override CosmosException Visit(MalformedContinuationTokenException malformedContinuationTokenException) { - return new CosmosBadRequestException( + return CosmosExceptionFactory.CreateBadRequestException( message: malformedContinuationTokenException.Message, stackTrace: malformedContinuationTokenException.StackTrace, innerException: malformedContinuationTokenException); @@ -153,14 +153,14 @@ public override CosmosException Visit(MalformedContinuationTokenException malfor public override CosmosException Visit(UnexpectedQueryPartitionProviderException unexpectedQueryPartitionProviderException) { - return new CosmosInternalServerErrorException( - message: $"{nameof(CosmosInternalServerErrorException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", + return CosmosExceptionFactory.CreateInternalServerErrorException( + message: $"{nameof(CosmosException)} due to {nameof(UnexpectedQueryPartitionProviderException)}", innerException: unexpectedQueryPartitionProviderException); } public override CosmosException Visit(ExpectedQueryPartitionProviderException expectedQueryPartitionProviderException) { - return new CosmosBadRequestException( + return CosmosExceptionFactory.CreateBadRequestException( message: expectedQueryPartitionProviderException.Message, stackTrace: expectedQueryPartitionProviderException.StackTrace, innerException: expectedQueryPartitionProviderException); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs deleted file mode 100644 index c84ad2275a..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosBadRequestException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosBadRequestException : CosmosHttpException - { - internal CosmosBadRequestException( - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base( - HttpStatusCode.BadRequest, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index c3099bf279..58b87a64e4 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -100,7 +100,7 @@ internal static CosmosException Create( string stackTrace; if (responseMessage.CosmosException != null) { - stackTrace = responseMessage.CosmosException.StackTrace; + stackTrace = responseMessage.CosmosException.StackTrace; } else { @@ -189,6 +189,126 @@ internal static string GetErrorMessageFromStream( } } + internal static CosmosException CreateRequestTimeoutException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.RequestTimeout, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + + internal static CosmosException CreateThrottledException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + (HttpStatusCode)429, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + + internal static CosmosException CreateNotFoundException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.NotFound, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + + internal static CosmosException CreateInternalServerErrorException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.InternalServerError, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + + internal static CosmosException CreateBadRequestException( + string message, + int subStatusCode = default, + string stackTrace = default, + string activityId = default, + double requestCharge = default, + TimeSpan? retryAfter = default, + Headers headers = default, + CosmosDiagnosticsContext diagnosticsContext = default, + Exception innerException = default) + { + return CosmosExceptionFactory.Create( + HttpStatusCode.BadRequest, + subStatusCode, + message, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); + } + internal static CosmosException Create( HttpStatusCode statusCode, int subStatusCode, @@ -201,76 +321,17 @@ internal static CosmosException Create( CosmosDiagnosticsContext diagnosticsContext, Exception innerException) { - switch (statusCode) - { - case HttpStatusCode.NotFound: - return new CosmosNotFoundException( - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - case HttpStatusCode.InternalServerError: - return new CosmosInternalServerErrorException( - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - case HttpStatusCode.BadRequest: - return new CosmosBadRequestException( - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - case HttpStatusCode.RequestTimeout: - return new CosmosRequestTimeoutException( - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - case (HttpStatusCode)429: - return new CosmosThrottledException( - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - default: - return new CosmosException( - statusCode, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException); - } + return new CosmosException( + statusCode, + message, + subStatusCode, + stackTrace, + activityId, + requestCharge, + retryAfter, + headers, + diagnosticsContext, + innerException); } } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs deleted file mode 100644 index ee33416ad1..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosHttpException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal abstract class CosmosHttpException : CosmosException - { - internal CosmosHttpException( - HttpStatusCode statusCode, - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base(statusCode, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs deleted file mode 100644 index a13a14625b..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosInternalServerErrorException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosInternalServerErrorException : CosmosHttpException - { - internal CosmosInternalServerErrorException( - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base( - HttpStatusCode.InternalServerError, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs deleted file mode 100644 index 854e3d8dda..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosNotFoundException.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosNotFoundException : CosmosHttpException - { - public CosmosNotFoundException(string message) - : base(statusCode: HttpStatusCode.NotFound, message: message) - { - } - - internal CosmosNotFoundException( - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base( - HttpStatusCode.NotFound, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs deleted file mode 100644 index ad50b7f415..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosRequestTimeoutException.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosRequestTimeoutException : CosmosHttpException - { - public CosmosRequestTimeoutException(string message) - : base(statusCode: HttpStatusCode.RequestTimeout, message: message) - { - } - - internal CosmosRequestTimeoutException( - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base( - HttpStatusCode.RequestTimeout, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs deleted file mode 100644 index 2e408c69cf..0000000000 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosThrottledException.cs +++ /dev/null @@ -1,37 +0,0 @@ -// ------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All rights reserved. -// ------------------------------------------------------------ - -namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions -{ - using System; - using System.Diagnostics; - using System.Net; - - internal sealed class CosmosThrottledException : CosmosHttpException - { - internal CosmosThrottledException( - string message, - int subStatusCode = default, - string stackTrace = default, - string activityId = default, - double requestCharge = default, - TimeSpan? retryAfter = default, - Headers headers = default, - CosmosDiagnosticsContext diagnosticsContext = default, - Exception innerException = default) - : base( - (HttpStatusCode)429, - message, - subStatusCode, - stackTrace, - activityId, - requestCharge, - retryAfter, - headers, - diagnosticsContext, - innerException) - { - } - } -} diff --git a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs index 852f616ef0..5ffd3a221a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/DataEncryptionKey/DataEncryptionKeyCore.cs @@ -136,7 +136,7 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosNotFoundException( + throw CosmosExceptionFactory.CreateNotFoundException( ClientResources.DataEncryptionKeyNotFound, diagnosticsContext: diagnosticsContext, innerException: ex); @@ -176,7 +176,7 @@ internal static Uri CreateLinkUri(CosmosClientContext clientContext, DatabaseCor } catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { - throw new CosmosNotFoundException( + throw CosmosExceptionFactory.CreateNotFoundException( ClientResources.DataEncryptionKeyNotFound, diagnosticsContext: diagnosticsContext, innerException: ex); @@ -230,7 +230,7 @@ internal virtual byte[] GenerateKey(CosmosEncryptionAlgorithm encryptionAlgorith InMemoryRawDek roundTripResponse = await this.UnwrapAsync(tempDekProperties, diagnosticsContext, cancellationToken); if (!roundTripResponse.RawDek.SequenceEqual(key)) { - throw new CosmosBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip, + throw CosmosExceptionFactory.CreateBadRequestException(ClientResources.KeyWrappingDidNotRoundtrip, diagnosticsContext: diagnosticsContext); } diff --git a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs index 7893c7d265..f5750f9bd8 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Offer/CosmosOffers.cs @@ -146,7 +146,7 @@ private async Task GetOfferV2Async( if (offerV2 == null && failIfNotConfigured) { - throw (CosmosException)new CosmosNotFoundException( + throw (CosmosException)CosmosExceptionFactory.CreateNotFoundException( $"Throughput is not configured for {targetRID}"); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs index 8777c4e365..92801fe474 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/ChangeFeed/SmokeTests.cs @@ -76,7 +76,7 @@ public async Task NotExistentLeaseContainer() .WithInstanceName("random") .WithLeaseContainer(notFoundContainer).Build(); - CosmosException exception = await Assert.ThrowsExceptionAsync(() => processor.StartAsync()); + CosmosException exception = await Assert.ThrowsExceptionAsync(() => processor.StartAsync()); Assert.AreEqual(HttpStatusCode.NotFound, exception.StatusCode); } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs index 52d42af423..4b32bba569 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosContainerTests.cs @@ -568,7 +568,7 @@ public async Task StreamPartitionedCreateWithPathDelete() } [TestMethod] - [ExpectedException(typeof(CosmosBadRequestException))] + [ExpectedException(typeof(CosmosException))] public async Task NegativePartitionedCreateDelete() { string containerName = Guid.NewGuid().ToString(); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs index 62d6d578ba..a2773f5b76 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CrossPartitionQueryTests.cs @@ -795,7 +795,7 @@ private async Task TestBadQueriesOverMultiplePartitionsHelper(Container containe Assert.Fail($"Expected {nameof(CosmosException)}"); } - catch (CosmosBadRequestException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) + catch (CosmosException exception) when (exception.StatusCode == HttpStatusCode.BadRequest) { Assert.IsTrue(exception.Message.Contains(@"Identifier 'a' could not be resolved."), exception.Message); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs index 717546a838..a5c4956901 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/ChangeFeedResultSetIteratorTests.cs @@ -42,7 +42,7 @@ public async Task ContinuationTokenIsNotUpdatedOnFails() { ETag = "ShouldNotContainThis" }, - cosmosException: new CosmosNotFoundException("something"), + cosmosException: CosmosExceptionFactory.CreateNotFoundException("something"), diagnostics: CosmosDiagnosticsContext.Create()); mockContext.SetupSequence(x => x.ProcessResourceOperationAsync( diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs index d72a8eef0f..b93729c7c5 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -34,7 +34,7 @@ public void EnsureSuccessStatusCode_DontThrowOnSuccess() public void EnsureSuccessStatusCode_ThrowsOnFailure() { ResponseMessage responseMessage = new ResponseMessage(HttpStatusCode.NotFound); - Assert.ThrowsException(() => responseMessage.EnsureSuccessStatusCode()); + Assert.ThrowsException(() => responseMessage.EnsureSuccessStatusCode()); } [TestMethod] @@ -154,11 +154,11 @@ public void EnsureCorrectStatusCode() List<(HttpStatusCode statusCode, CosmosException exception)> exceptionsToStatusCodes = new List<(HttpStatusCode, CosmosException)>() { - (HttpStatusCode.NotFound, new CosmosNotFoundException(testMessage)), - (HttpStatusCode.InternalServerError, new CosmosInternalServerErrorException(testMessage)), - (HttpStatusCode.BadRequest, new CosmosBadRequestException(testMessage)), - (HttpStatusCode.RequestTimeout, new CosmosRequestTimeoutException(testMessage)), - ((HttpStatusCode)429, new CosmosThrottledException(testMessage)), + (HttpStatusCode.NotFound, CosmosExceptionFactory.CreateNotFoundException(testMessage)), + (HttpStatusCode.InternalServerError, CosmosExceptionFactory.CreateInternalServerErrorException(testMessage)), + (HttpStatusCode.BadRequest, CosmosExceptionFactory.CreateBadRequestException(testMessage)), + (HttpStatusCode.RequestTimeout,CosmosExceptionFactory.CreateRequestTimeoutException(testMessage)), + ((HttpStatusCode)429, CosmosExceptionFactory.CreateThrottledException(testMessage)), }; foreach((HttpStatusCode statusCode, CosmosException exception) item in exceptionsToStatusCodes) @@ -170,7 +170,7 @@ public void EnsureCorrectStatusCode() [TestMethod] public void ValidateExceptionStackTraceHandling() { - CosmosException cosmosException = new CosmosNotFoundException("TestMessage"); + CosmosException cosmosException = CosmosExceptionFactory.CreateNotFoundException("TestMessage"); Assert.AreEqual(null, cosmosException.StackTrace); Assert.IsFalse(cosmosException.ToString().Contains(nameof(ValidateExceptionStackTraceHandling))); try @@ -185,7 +185,7 @@ public void ValidateExceptionStackTraceHandling() string stackTrace = "OriginalDocumentClientExceptionStackTrace"; try { - throw new CosmosNotFoundException("TestMessage", stackTrace: stackTrace); + throw CosmosExceptionFactory.CreateNotFoundException("TestMessage", stackTrace: stackTrace); } catch (CosmosException ce) { diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index dfa43071be..8cf8e5916b 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -24,6 +24,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Query.Core.Monads; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; using Microsoft.Azure.Cosmos.Query.Core.QueryPlan; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -38,7 +39,7 @@ public void VerifyNegativeCosmosQueryResponseStream() string errorMessage = "TestErrorMessage"; string activityId = "TestActivityId"; double requestCharge = 42.42; - CosmosException cosmosException = new Cosmos.Resource.CosmosExceptions.CosmosBadRequestException(errorMessage); + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException(errorMessage); CosmosDiagnosticsContext diagnostics = CosmosDiagnosticsContext.Create(); QueryResponse queryResponse = QueryResponse.CreateFailure( statusCode: HttpStatusCode.NotFound, diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs index 5f217e0a8e..d813926cb6 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/ItemProducerTreeUnitTests.cs @@ -270,7 +270,7 @@ void produceAsyncCompleteCallback( Task.FromResult(QueryResponseCore.CreateFailure( statusCode: HttpStatusCode.InternalServerError, subStatusCodes: null, - cosmosException: new Cosmos.Resource.CosmosExceptions.CosmosInternalServerErrorException( + cosmosException: CosmosExceptionFactory.CreateInternalServerErrorException( "Error message"), requestCharge: 10.2, activityId: Guid.NewGuid().ToString(), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs index d4892833d4..8d3734704f 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseFactoryTests.cs @@ -20,7 +20,7 @@ public class QueryResponseFactoryTests [TestMethod] public void CosmosException() { - CosmosException cosmosException = new CosmosBadRequestException( + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException( message: "asdf"); QueryResponseCore queryResponse = QueryResponseFactory.CreateFromException(cosmosException); Assert.AreEqual(HttpStatusCode.BadRequest, queryResponse.StatusCode); @@ -69,7 +69,7 @@ public void ExceptionFromTryCatchWithCosmosException() CosmosException cosmosException; try { - throw new CosmosBadRequestException("InternalServerTestMessage"); + throw CosmosExceptionFactory.CreateBadRequestException("InternalServerTestMessage"); } catch (CosmosException ce) { From 74beae554b7ad9ba0a4f221b357b68e1adf90511 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Wed, 26 Feb 2020 14:11:39 -0800 Subject: [PATCH 17/25] Adding transport client exception tests. --- .../CosmosDiagnosticsTests.cs | 51 ++++++++++- .../Utils/TransportClientHelper.cs | 91 +++++++++++++++++++ 2 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs index f92f115025..450d1b4f5e 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosDiagnosticsTests.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests { using Microsoft.Azure.Cosmos.Query.Core; using Microsoft.VisualStudio.TestTools.UnitTesting; + using Moq; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; @@ -71,6 +72,53 @@ public async Task CustomHandlersDiagnostic() await databaseResponse.Database.DeleteAsync(); } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task PointOperationRequestTimeoutDiagnostic(bool disableDiagnostics) + { + ItemRequestOptions requestOptions = new ItemRequestOptions() + { + DiagnosticContext = disableDiagnostics ? EmptyCosmosDiagnosticsContext.Singleton : null + }; + + Guid exceptionActivityId = Guid.NewGuid(); + string transportExceptionDescription = "transportExceptionDescription" + Guid.NewGuid(); + Container containerWithTransportException = TransportClientHelper.GetContainerWithItemTransportException( + this.database.Id, + this.Container.Id, + exceptionActivityId, + transportExceptionDescription); + + //Checking point operation diagnostics on typed operations + ToDoActivity testItem = ToDoActivity.CreateRandomToDoActivity(); + try + { + ItemResponse createResponse = await containerWithTransportException.CreateItemAsync( + item: testItem, + requestOptions: requestOptions); + Assert.Fail("Should have thrown a request timeout exception"); + } + catch(CosmosException ce) when (ce.StatusCode == System.Net.HttpStatusCode.RequestTimeout) + { + string exception = ce.ToString(); + Assert.IsNotNull(exception); + Assert.IsTrue(exception.Contains(exceptionActivityId.ToString())); + Assert.IsTrue(exception.Contains(transportExceptionDescription)); + + string diagnosics = ce.Diagnostics.ToString(); + if (disableDiagnostics) + { + Assert.IsTrue(string.IsNullOrEmpty(diagnosics)); + } + else + { + Assert.IsFalse(string.IsNullOrEmpty(diagnosics)); + Assert.IsTrue(exception.Contains(diagnosics)); + } + } + } + [TestMethod] [DataRow(true)] [DataRow(false)] @@ -92,6 +140,7 @@ public async Task PointOperationDiagnostic(bool disableDiagnostics) id: testItem.id, partitionKey: new PartitionKey(testItem.status), requestOptions); + CosmosDiagnosticsTests.VerifyPointDiagnostics(readResponse.Diagnostics, disableDiagnostics); Assert.IsNotNull(readResponse.Diagnostics); testItem.description = "NewDescription"; @@ -102,7 +151,7 @@ public async Task PointOperationDiagnostic(bool disableDiagnostics) requestOptions: requestOptions); Assert.AreEqual(replaceResponse.Resource.description, "NewDescription"); - CosmosDiagnosticsTests.VerifyPointDiagnostics(createResponse.Diagnostics, disableDiagnostics); + CosmosDiagnosticsTests.VerifyPointDiagnostics(replaceResponse.Diagnostics, disableDiagnostics); ItemResponse deleteResponse = await this.Container.DeleteItemAsync( partitionKey: new Cosmos.PartitionKey(testItem.status), diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs new file mode 100644 index 0000000000..a0bb0ba87b --- /dev/null +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Utils/TransportClientHelper.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All rights reserved. +//------------------------------------------------------------ + +namespace Microsoft.Azure.Cosmos.SDK.EmulatorTests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Azure.Cosmos.SDK.EmulatorTests; + using Microsoft.Azure.Documents; + + + internal static class TransportClientHelper + { + internal static Container GetContainerWithItemTransportException( + string databaseId, + string containerId, + Guid activityId, + string transportExceptionSourceDescription) + { + CosmosClient clientWithIntercepter = TestCommon.CreateCosmosClient( + builder => + { + builder.WithTransportClientHandlerFactory(transportClient => new TransportClientWrapper( + transportClient, + (uri, resourceOperation, request) => TransportClientHelper.ThrowTransportExceptionOnItemOperation( + uri, + resourceOperation, + request, + activityId, + transportExceptionSourceDescription))); + }); + + return clientWithIntercepter.GetContainer(databaseId, containerId); + } + + private static void ThrowTransportExceptionOnItemOperation( + Uri physicalAddress, + ResourceOperation resourceOperation, + DocumentServiceRequest request, + Guid activityId, + string transportExceptionSourceDescription) + { + if (request.ResourceType == ResourceType.Document) + { + TransportException transportException = new TransportException( + errorCode: TransportErrorCode.RequestTimeout, + innerException: null, + activityId: activityId, + requestUri: physicalAddress, + sourceDescription: transportExceptionSourceDescription, + userPayload: true, + payloadSent: false); + + throw Documents.Rntbd.TransportExceptions.GetRequestTimeoutException(physicalAddress, Guid.NewGuid(), + transportException); + } + } + + private sealed class TransportClientWrapper : TransportClient + { + private readonly TransportClient baseClient; + private readonly Action interceptor; + + internal TransportClientWrapper( + TransportClient client, + Action interceptor) + { + Debug.Assert(client != null); + Debug.Assert(interceptor != null); + + this.baseClient = client; + this.interceptor = interceptor; + } + + internal override async Task InvokeStoreAsync( + Uri physicalAddress, + ResourceOperation resourceOperation, + DocumentServiceRequest request) + { + this.interceptor(physicalAddress, resourceOperation, request); + + StoreResponse response = await this.baseClient.InvokeStoreAsync(physicalAddress, resourceOperation, request); + return response; + } + } + } +} From 300872ba45c422d223c77cfb3287b0b8bdc3c928 Mon Sep 17 00:00:00 2001 From: j82w Date: Thu, 27 Feb 2020 05:07:41 -0800 Subject: [PATCH 18/25] Update Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs Co-Authored-By: Matias Quaranta --- .../src/Resource/CosmosExceptions/CosmosExceptionFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 58b87a64e4..c0eff587bc 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -84,7 +84,7 @@ internal static CosmosException Create( // If content was added after the response message // creation the exception should be updated. string errorMessage = responseMessage.ErrorMessage; - string contentMessage = GetErrorMessageFromStream(responseMessage.Content); + string contentMessage = CosmosExceptionFactory.GetErrorMessageFromStream(responseMessage.Content); if (!string.IsNullOrEmpty(contentMessage)) { if (string.IsNullOrEmpty(errorMessage)) From fce942a0c1800e293939200a728288d847423873 Mon Sep 17 00:00:00 2001 From: j82w Date: Thu, 27 Feb 2020 05:07:55 -0800 Subject: [PATCH 19/25] Update Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs Co-Authored-By: Matias Quaranta --- .../src/Resource/CosmosExceptions/CosmosExceptionFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index c0eff587bc..aae153dcf7 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -225,7 +225,7 @@ internal static CosmosException CreateThrottledException( Exception innerException = default) { return CosmosExceptionFactory.Create( - (HttpStatusCode)429, + (HttpStatusCode)StatusCodes.TooManyRequests, subStatusCode, message, stackTrace, From 680cb2e7b01661b10e622b32825758759c3d22d4 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 27 Feb 2020 05:43:27 -0800 Subject: [PATCH 20/25] Removed diagnostics from Response.ErrorMessage --- .../src/Handler/ResponseMessage.cs | 2 +- .../src/Resource/Container/ContainerCore.cs | 4 ++- .../CosmosExceptions/CosmosException.cs | 32 ++++++++++++------- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index f848350af8..6bfa25b52f 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -99,7 +99,7 @@ public virtual Stream Content /// /// Gets the reason for a failure in the current response. /// - public virtual string ErrorMessage => this.CosmosException?.ToString(); + public virtual string ErrorMessage => this.CosmosException?.ToString(includeDiagnostics: false); /// /// Gets the current HTTP headers. diff --git a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs index f06b01e2ca..b64ebd395a 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/Container/ContainerCore.cs @@ -380,7 +380,9 @@ async Task> GetPartitionKeyRangesAsync( } catch (DocumentClientException ex) { - throw CosmosExceptionFactory.Create(ex, null); + throw CosmosExceptionFactory.Create( + dce: ex, + diagnosticsContext: null); } } diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 8e0c8915fd..9523a17850 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -153,6 +153,26 @@ public virtual bool TryGetHeader(string headerName, out string value) /// /// A string representation of the exception. public override string ToString() + { + return this.ToStringHelper(true); + } + + internal string ToString(bool includeDiagnostics) + { + return this.ToStringHelper(includeDiagnostics); + } + + internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) + { + return new ResponseMessage( + headers: this.Headers, + requestMessage: request, + cosmosException: this, + statusCode: this.StatusCode, + diagnostics: this.DiagnosticsContext); + } + + private string ToStringHelper(bool includeDiagnostics) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(this.GetType().FullName); @@ -175,7 +195,7 @@ public override string ToString() stringBuilder.AppendFormat("RequestCharge = {0};", this.RequestCharge); stringBuilder.AppendLine(); - if (this.Diagnostics != null) + if (includeDiagnostics && this.Diagnostics != null) { stringBuilder.Append(this.Diagnostics); stringBuilder.AppendLine(); @@ -195,15 +215,5 @@ public override string ToString() return stringBuilder.ToString(); } - - internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) - { - return new ResponseMessage( - headers: this.Headers, - requestMessage: request, - cosmosException: this, - statusCode: this.StatusCode, - diagnostics: this.DiagnosticsContext); - } } } From c4e949067691cc4dc3256e3fd791e821bcde3f62 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 27 Feb 2020 05:54:27 -0800 Subject: [PATCH 21/25] Fixed unit test --- .../tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index 8cf8e5916b..593eea760a 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -57,7 +57,7 @@ public void VerifyNegativeCosmosQueryResponseStream() diagnostics: diagnostics); Assert.AreEqual(HttpStatusCode.NotFound, queryResponse.StatusCode); - Assert.AreEqual(cosmosException.ToString(), queryResponse.ErrorMessage); + Assert.AreEqual(cosmosException.ToString(includeDiagnostics: false), queryResponse.ErrorMessage); Assert.AreEqual(requestCharge, queryResponse.Headers.RequestCharge); Assert.AreEqual(activityId, queryResponse.Headers.ActivityId); Assert.AreEqual(diagnostics, queryResponse.Diagnostics); From 46d9b1c0a762d94a64d9cca81c423b3fe7c0276f Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 27 Feb 2020 13:10:36 -0800 Subject: [PATCH 22/25] Adding Error object to CosmosException for back compatability. --- .../Core/QueryPlan/QueryPlanRetriever.cs | 12 +-- .../src/Query/Core/QueryResponseFactory.cs | 4 +- .../CosmosExceptions/CosmosException.cs | 36 ++++++++- .../CosmosExceptionFactory.cs | 80 +++++++++---------- .../CosmosQueryUnitTests.cs | 21 ++--- .../Query/QueryResponseMessageFactory.cs | 22 ++--- 6 files changed, 102 insertions(+), 73 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs index b2d4637107..8d56169b29 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryPlan/QueryPlanRetriever.cs @@ -68,17 +68,9 @@ public static async Task GetQueryPlanWithServiceI throw tryGetQueryPlan.Exception; } - throw CosmosExceptionFactory.Create( - statusCode: System.Net.HttpStatusCode.BadRequest, - subStatusCode: default, + throw CosmosExceptionFactory.CreateBadRequestException( message: tryGetQueryPlan.Exception.ToString(), - stackTrace: tryGetQueryPlan.Exception.StackTrace, - activityId: default, - requestCharge: default, - retryAfter: default, - headers: default, - diagnosticsContext: default, - innerException: default); + stackTrace: tryGetQueryPlan.Exception.StackTrace); } return tryGetQueryPlan.Result; diff --git a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs index 7ef376a308..2342e38f17 100644 --- a/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Query/Core/QueryResponseFactory.cs @@ -46,8 +46,7 @@ public static QueryResponseCore CreateFromException(Exception exception) } else { - CosmosException unkownCosmosException = CosmosExceptionFactory.Create( - statusCode: System.Net.HttpStatusCode.InternalServerError, + CosmosException unkownCosmosException = CosmosExceptionFactory.CreateInternalServerErrorException( subStatusCode: default, message: exception.Message, stackTrace: exception.StackTrace, @@ -127,6 +126,7 @@ private static QueryResponseCore CreateFromExceptionWithStackTrace(ExceptionWith cosmosException.RetryAfter, cosmosException.Headers, cosmosException.DiagnosticsContext, + cosmosException.Error, cosmosException.InnerException), requestCharge: queryResponseCore.RequestCharge, activityId: queryResponseCore.ActivityId, diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs index 9523a17850..0d797f9848 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosException.cs @@ -7,6 +7,7 @@ namespace Microsoft.Azure.Cosmos using System; using System.Net; using System.Text; + using Microsoft.Azure.Documents; /// /// The Cosmos Client exception @@ -25,8 +26,9 @@ internal CosmosException( TimeSpan? retryAfter, Headers headers, CosmosDiagnosticsContext diagnosticsContext, + Error error, Exception innerException) - : base(message, innerException) + : base(MergeErrorMessages(message, error), innerException) { this.stackTrace = stackTrace; this.ActivityId = activityId; @@ -35,6 +37,7 @@ internal CosmosException( this.RetryAfter = retryAfter; this.RequestCharge = requestCharge; this.Headers = headers; + this.Error = error; // Always have a diagnostic context. A new diagnostic will have useful info like user agent this.DiagnosticsContext = diagnosticsContext ?? CosmosDiagnosticsContext.Create(); @@ -131,6 +134,11 @@ public override string StackTrace internal virtual CosmosDiagnosticsContext DiagnosticsContext { get; } + /// + /// Gets the internal error object. + /// + internal virtual Documents.Error Error { get; set; } + /// /// Try to get a header from the cosmos response message /// @@ -172,6 +180,24 @@ internal ResponseMessage ToCosmosResponseMessage(RequestMessage request) diagnostics: this.DiagnosticsContext); } + /// + /// This handles the scenario there is a message and the error object is set. + /// + private static string MergeErrorMessages(string message, Error error) + { + if (error == null) + { + return message; + } + + if (string.IsNullOrEmpty(message)) + { + return error.Message; + } + + return $"{message}; Inner Message:{error.Message}"; + } + private string ToStringHelper(bool includeDiagnostics) { StringBuilder stringBuilder = new StringBuilder(); @@ -183,6 +209,14 @@ private string ToStringHelper(bool includeDiagnostics) stringBuilder.AppendLine(); } + if (this.Error != null) + { + stringBuilder.Append(" : "); + stringBuilder.Append($"Code :{this.Error.Code ?? string.Empty}; Details :{this.Error.ErrorDetails ?? string.Empty}; "); + stringBuilder.Append($"Additional Details: {this.Error.AdditionalErrorInfo ?? string.Empty};"); + stringBuilder.AppendLine(); + } + stringBuilder.AppendFormat("StatusCode = {0};", this.StatusCode); stringBuilder.AppendLine(); diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index aae153dcf7..0f39cfe073 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -49,6 +49,7 @@ internal static CosmosException Create( dce.RetryAfter, headers, diagnosticsContext, + dce.Error, dce.InnerException); } @@ -67,6 +68,7 @@ internal static CosmosException Create( retryAfter: default, headers: requestMessage?.Headers, diagnosticsContext: default, + error: default, innerException: default); } @@ -83,8 +85,8 @@ internal static CosmosException Create( // If content was added after the response message // creation the exception should be updated. - string errorMessage = responseMessage.ErrorMessage; - string contentMessage = CosmosExceptionFactory.GetErrorMessageFromStream(responseMessage.Content); + string errorMessage = responseMessage.CosmosException?.Message; + (Error error, string contentMessage) = CosmosExceptionFactory.GetErrorFromStream(responseMessage.Content); if (!string.IsNullOrEmpty(contentMessage)) { if (string.IsNullOrEmpty(errorMessage)) @@ -97,26 +99,17 @@ internal static CosmosException Create( } } - string stackTrace; - if (responseMessage.CosmosException != null) - { - stackTrace = responseMessage.CosmosException.StackTrace; - } - else - { - stackTrace = null; - } - return CosmosExceptionFactory.Create( responseMessage.StatusCode, (int)responseMessage.Headers.SubStatusCode, errorMessage, - stackTrace, + responseMessage?.CosmosException?.StackTrace, responseMessage.Headers.ActivityId, responseMessage.Headers.RequestCharge, responseMessage.Headers.RetryAfter, responseMessage.Headers, responseMessage.DiagnosticsContext, + error, responseMessage.CosmosException?.InnerException); } @@ -134,23 +127,24 @@ internal static CosmosException Create( throw new ArgumentNullException(nameof(requestMessage)); } - string errorMessage = CosmosExceptionFactory.GetErrorMessageFromStream(storeResponse.ResponseBody); + (Error error, string errorMessage) = CosmosExceptionFactory.GetErrorFromStream(storeResponse.ResponseBody); Headers headers = storeResponse.ToCosmosHeaders(); return CosmosExceptionFactory.Create( - storeResponse.StatusCode, - (int)headers.SubStatusCode, - errorMessage, - null, - headers.ActivityId, - headers.RequestCharge, - headers.RetryAfter, - headers, - requestMessage.DiagnosticsContext, - null); + statusCode: storeResponse.StatusCode, + subStatusCode: (int)headers.SubStatusCode, + message: errorMessage, + stackTrace: null, + activityId: headers.ActivityId, + requestCharge: headers.RequestCharge, + retryAfter: headers.RetryAfter, + headers: headers, + diagnosticsContext: requestMessage.DiagnosticsContext, + error: error, + innerException: null); } - internal static string GetErrorMessageFromStream( + internal static (Error, string) GetErrorFromStream( Stream content) { using (content) @@ -164,28 +158,22 @@ internal static string GetErrorMessageFromStream( if (error != null) { // Error format is not consistent across modes - if (!string.IsNullOrEmpty(error.Message)) - { - return error.Message; - } - else - { - return error.ToString(); - } + return (error, null); } } catch (Newtonsoft.Json.JsonReaderException) { - // Content is not Json - content.Position = 0; - using (StreamReader streamReader = new StreamReader(content)) - { - return streamReader.ReadToEnd(); - } + } + + // Content is not Json + content.Position = 0; + using (StreamReader streamReader = new StreamReader(content)) + { + return (null, streamReader.ReadToEnd()); } } - return null; + return (null, null); } } @@ -198,6 +186,7 @@ internal static CosmosException CreateRequestTimeoutException( TimeSpan? retryAfter = default, Headers headers = default, CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, Exception innerException = default) { return CosmosExceptionFactory.Create( @@ -210,6 +199,7 @@ internal static CosmosException CreateRequestTimeoutException( retryAfter, headers, diagnosticsContext, + error, innerException); } @@ -222,6 +212,7 @@ internal static CosmosException CreateThrottledException( TimeSpan? retryAfter = default, Headers headers = default, CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, Exception innerException = default) { return CosmosExceptionFactory.Create( @@ -234,6 +225,7 @@ internal static CosmosException CreateThrottledException( retryAfter, headers, diagnosticsContext, + error, innerException); } @@ -246,6 +238,7 @@ internal static CosmosException CreateNotFoundException( TimeSpan? retryAfter = default, Headers headers = default, CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, Exception innerException = default) { return CosmosExceptionFactory.Create( @@ -258,6 +251,7 @@ internal static CosmosException CreateNotFoundException( retryAfter, headers, diagnosticsContext, + error, innerException); } @@ -270,6 +264,7 @@ internal static CosmosException CreateInternalServerErrorException( TimeSpan? retryAfter = default, Headers headers = default, CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, Exception innerException = default) { return CosmosExceptionFactory.Create( @@ -282,6 +277,7 @@ internal static CosmosException CreateInternalServerErrorException( retryAfter, headers, diagnosticsContext, + error, innerException); } @@ -294,6 +290,7 @@ internal static CosmosException CreateBadRequestException( TimeSpan? retryAfter = default, Headers headers = default, CosmosDiagnosticsContext diagnosticsContext = default, + Error error = default, Exception innerException = default) { return CosmosExceptionFactory.Create( @@ -306,6 +303,7 @@ internal static CosmosException CreateBadRequestException( retryAfter, headers, diagnosticsContext, + error, innerException); } @@ -319,6 +317,7 @@ internal static CosmosException Create( TimeSpan? retryAfter, Headers headers, CosmosDiagnosticsContext diagnosticsContext, + Error error, Exception innerException) { return new CosmosException( @@ -331,6 +330,7 @@ internal static CosmosException Create( retryAfter, headers, diagnosticsContext, + error, innerException); } } diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs index 593eea760a..2291308714 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosQueryUnitTests.cs @@ -364,16 +364,17 @@ public async Task TestCosmosQueryPartitionKeyDefinition() System.Net.HttpStatusCode.Unauthorized, SubStatusCodes.PartitionKeyMismatch, new CosmosException( - HttpStatusCode.Unauthorized, - "Random error message", - default, - default, - "TestActivityId", - 42.89, - default, - default, - default, - default), + statusCodes: HttpStatusCode.Unauthorized, + message: "Random error message", + subStatusCode: default, + stackTrace: default, + activityId: "TestActivityId", + requestCharge: 42.89, + retryAfter: default, + headers: default, + diagnosticsContext: default, + error: default, + innerException: default), 42.89, "TestActivityId", diagnostics); diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs index 80d076b356..ddcbf71b93 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Query/QueryResponseMessageFactory.cs @@ -15,6 +15,7 @@ namespace Microsoft.Azure.Cosmos.Tests using Microsoft.Azure.Cosmos.Json; using Microsoft.Azure.Cosmos.Query.Core.Metrics; using Microsoft.Azure.Cosmos.Query.Core.QueryClient; + using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; internal static class QueryResponseMessageFactory @@ -171,17 +172,18 @@ public static QueryResponseCore CreateFailureResponse( QueryResponseCore splitResponse = QueryResponseCore.CreateFailure( statusCode: httpStatusCode, subStatusCodes: subStatusCodes, - cosmosException: new CosmosException( - httpStatusCode, - errorMessage, - (int)subStatusCodes, - new System.Diagnostics.StackTrace().ToString(), - acitivityId, - 10.4, - default, - default, + cosmosException: CosmosExceptionFactory.Create( + statusCode: httpStatusCode, + subStatusCode: (int)subStatusCodes, + message: errorMessage, + stackTrace: new System.Diagnostics.StackTrace().ToString(), + activityId: acitivityId, + requestCharge: 10.4, + retryAfter: default, + headers: default, diagnosticsContext: diagnosticsContext, - default), + error: default, + innerException: default), requestCharge: 10.4, activityId: acitivityId, diagnostics: diagnostics); From 4a720937743276771593c98775461850e6329c68 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Thu, 27 Feb 2020 14:23:22 -0800 Subject: [PATCH 23/25] Adding unit test for Error handling --- .../CosmosExceptionTests.cs | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs index b93729c7c5..e265ede0cc 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/CosmosExceptionTests.cs @@ -193,6 +193,46 @@ public void ValidateExceptionStackTraceHandling() } } + [TestMethod] + public void ValidateErrorHandling() + { + Error error = new Error() + { + Code = System.Net.HttpStatusCode.BadRequest.ToString(), + Message = "Unsupported Query", + AdditionalErrorInfo = "Additional error info message" + }; + + CosmosDiagnosticsContext diagnostics = new CosmosDiagnosticsContextCore(); + + CosmosException cosmosException = CosmosExceptionFactory.CreateBadRequestException( + null, + error: error, + diagnosticsContext: diagnostics); + + ResponseMessage responseMessage = QueryResponse.CreateFailure( + statusCode: System.Net.HttpStatusCode.BadRequest, + cosmosException: cosmosException, + requestMessage: null, + diagnostics: diagnostics, + responseHeaders: null); + + Assert.AreEqual(error, responseMessage.CosmosException.Error); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(error.Message)); + Assert.IsTrue(responseMessage.ErrorMessage.Contains(error.AdditionalErrorInfo)); + + try + { + responseMessage.EnsureSuccessStatusCode(); + Assert.Fail("Should throw exception"); + }catch(CosmosException ce ) when (ce.StatusCode == HttpStatusCode.BadRequest) + { + Assert.IsTrue(ce.Message.Contains(error.Message)); + Assert.IsTrue(ce.ToString().Contains(error.Message)); + Assert.IsTrue(ce.ToString().Contains(error.AdditionalErrorInfo)); + } + } + private void ValidateExceptionInfo( CosmosException exception, HttpStatusCode httpStatusCode, From cad13c74531ef377c5d1e41757ffe88a943a8974 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 28 Feb 2020 05:52:05 -0800 Subject: [PATCH 24/25] Fixed DocumentServiceResponse handling --- .../src/Handler/ResponseMessage.cs | 2 +- .../CosmosExceptionFactory.cs | 37 +++++++++- Microsoft.Azure.Cosmos/src/Util/Extensions.cs | 68 +++++++++++++------ 3 files changed, 83 insertions(+), 24 deletions(-) diff --git a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs index 6bfa25b52f..e8985efc23 100644 --- a/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs +++ b/Microsoft.Azure.Cosmos/src/Handler/ResponseMessage.cs @@ -135,7 +135,7 @@ public virtual Stream Content /// /// Asserts if the current is a success. /// - public virtual bool IsSuccessStatusCode => ((int)this.StatusCode >= 200) && ((int)this.StatusCode <= 299); + public virtual bool IsSuccessStatusCode => this.StatusCode.IsSuccess(); /// /// Checks if the current has a successful status code, otherwise, throws. diff --git a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs index 0f39cfe073..d83ae36ae0 100644 --- a/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs +++ b/Microsoft.Azure.Cosmos/src/Resource/CosmosExceptions/CosmosExceptionFactory.cs @@ -5,7 +5,6 @@ namespace Microsoft.Azure.Cosmos.Resource.CosmosExceptions { using System; - using System.Diagnostics; using System.IO; using System.Net; using Microsoft.Azure.Documents; @@ -113,6 +112,42 @@ internal static CosmosException Create( responseMessage.CosmosException?.InnerException); } + internal static CosmosException Create( + DocumentServiceResponse documentServiceResponse, + Headers responseHeaders, + RequestMessage requestMessage) + { + if (documentServiceResponse == null) + { + throw new ArgumentNullException(nameof(documentServiceResponse)); + } + + if (requestMessage == null) + { + throw new ArgumentNullException(nameof(requestMessage)); + } + + if (responseHeaders == null) + { + responseHeaders = documentServiceResponse.Headers.ToCosmosHeaders(); + } + + (Error error, string errorMessage) = CosmosExceptionFactory.GetErrorFromStream(documentServiceResponse.ResponseBody); + + return CosmosExceptionFactory.Create( + statusCode: documentServiceResponse.StatusCode, + subStatusCode: (int)responseHeaders.SubStatusCode, + message: errorMessage, + stackTrace: null, + activityId: responseHeaders.ActivityId, + requestCharge: responseHeaders.RequestCharge, + retryAfter: responseHeaders.RetryAfter, + headers: responseHeaders, + diagnosticsContext: requestMessage.DiagnosticsContext, + error: error, + innerException: null); + } + internal static CosmosException Create( StoreResponse storeResponse, RequestMessage requestMessage) diff --git a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs index d45d34c2ba..819148626b 100644 --- a/Microsoft.Azure.Cosmos/src/Util/Extensions.cs +++ b/Microsoft.Azure.Cosmos/src/Util/Extensions.cs @@ -15,43 +15,56 @@ namespace Microsoft.Azure.Cosmos using Microsoft.Azure.Cosmos.Diagnostics; using Microsoft.Azure.Cosmos.Resource.CosmosExceptions; using Microsoft.Azure.Documents; + using Microsoft.Azure.Documents.Collections; internal static class Extensions { private static readonly char[] NewLineCharacters = new[] { '\r', '\n' }; + internal static bool IsSuccess(this HttpStatusCode httpStatusCode) + { + return ((int)httpStatusCode >= 200) && ((int)httpStatusCode <= 299); + } + internal static ResponseMessage ToCosmosResponseMessage(this DocumentServiceResponse documentServiceResponse, RequestMessage requestMessage) { Debug.Assert(requestMessage != null, nameof(requestMessage)); - ResponseMessage responseMessage = new ResponseMessage(documentServiceResponse.StatusCode, requestMessage); - if (documentServiceResponse.ResponseBody != null) - { - responseMessage.Content = documentServiceResponse.ResponseBody; - } - - if (documentServiceResponse.Headers != null) - { - foreach (string key in documentServiceResponse.Headers) - { - responseMessage.Headers.Add(key, documentServiceResponse.Headers[key]); - } - } - + Headers headers = documentServiceResponse.Headers.ToCosmosHeaders(); CosmosClientSideRequestStatistics cosmosClientSideRequestStatistics = documentServiceResponse.RequestStats as CosmosClientSideRequestStatistics; - PointOperationStatistics pointOperationStatistics = new PointOperationStatistics( - activityId: responseMessage.Headers.ActivityId, + requestMessage.DiagnosticsContext.AddDiagnosticsInternal(new PointOperationStatistics( + activityId: headers.ActivityId, statusCode: documentServiceResponse.StatusCode, subStatusCode: documentServiceResponse.SubStatusCode, - requestCharge: responseMessage.Headers.RequestCharge, - errorMessage: responseMessage.ErrorMessage, + requestCharge: headers.RequestCharge, + errorMessage: null, method: requestMessage?.Method, requestUri: requestMessage?.RequestUri, requestSessionToken: requestMessage?.Headers?.Session, - responseSessionToken: responseMessage.Headers.Session, - clientSideRequestStatistics: cosmosClientSideRequestStatistics); + responseSessionToken: headers.Session, + clientSideRequestStatistics: cosmosClientSideRequestStatistics)); + + // If it's considered a failure create the corresponding CosmosException + if (!documentServiceResponse.StatusCode.IsSuccess()) + { + CosmosException cosmosException = CosmosExceptionFactory.Create( + documentServiceResponse, + headers, + requestMessage); + + return cosmosException.ToCosmosResponseMessage(requestMessage); + } + + ResponseMessage responseMessage = new ResponseMessage( + statusCode: documentServiceResponse.StatusCode, + requestMessage: requestMessage, + headers: headers, + cosmosException: null, + diagnostics: requestMessage.DiagnosticsContext) + { + Content = documentServiceResponse.ResponseBody + }; - requestMessage.DiagnosticsContext.AddDiagnosticsInternal(pointOperationStatistics); return responseMessage; } @@ -102,7 +115,7 @@ internal static ResponseMessage ToCosmosResponseMessage(this DocumentClientExcep internal static ResponseMessage ToCosmosResponseMessage(this StoreResponse storeResponse, RequestMessage requestMessage) { // If it's considered a failure create the corresponding CosmosException - if (((int)storeResponse.StatusCode >= 200) && ((int)storeResponse.StatusCode <= 299)) + if (!storeResponse.StatusCode.IsSuccess()) { CosmosException cosmosException = CosmosExceptionFactory.Create( storeResponse, @@ -132,6 +145,17 @@ internal static Headers ToCosmosHeaders(this StoreResponse storeResponse) return headers; } + internal static Headers ToCosmosHeaders(this INameValueCollection nameValueCollection) + { + Headers headers = new Headers(); + foreach (string key in nameValueCollection) + { + headers.Add(key, nameValueCollection[key]); + } + + return headers; + } + internal static void TraceException(Exception exception) { AggregateException aggregateException = exception as AggregateException; From 553a7615e4c25d1cf7e4d1c6de9ccf0bad6efbf2 Mon Sep 17 00:00:00 2001 From: Jake Willey Date: Fri, 28 Feb 2020 06:10:02 -0800 Subject: [PATCH 25/25] Increased EndpointFailureMockTest wait time to avoid transient failures. --- .../GlobalEndpointManagerTest.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs index e1983f15df..b17f9ab481 100644 --- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs +++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/GlobalEndpointManagerTest.cs @@ -6,6 +6,7 @@ namespace Microsoft.Azure.Cosmos using System; using System.Collections.ObjectModel; using System.Threading; + using System.Threading.Tasks; using Microsoft.Azure.Cosmos.Routing; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -20,7 +21,7 @@ public class GlobalEndpointManagerTest /// Tests for /// [TestMethod] - public void EndpointFailureMockTest() + public async Task EndpointFailureMockTest() { // Setup dummpy read locations for the database account Collection readableLocations = new Collection(); @@ -56,7 +57,7 @@ public void EndpointFailureMockTest() GlobalEndpointManager globalEndpointManager = new GlobalEndpointManager(mockOwner.Object, connectionPolicy); - globalEndpointManager.RefreshLocationAsync(databaseAccount).Wait(); + await globalEndpointManager.RefreshLocationAsync(databaseAccount); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); //Mark each of the read locations as unavailable and validate that the read endpoint switches to the next preferred region / default endpoint. @@ -65,12 +66,12 @@ public void EndpointFailureMockTest() Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation2.Endpoint)); globalEndpointManager.MarkEndpointUnavailableForRead(globalEndpointManager.ReadEndpoints[0]); - globalEndpointManager.RefreshLocationAsync(null).Wait(); + await globalEndpointManager.RefreshLocationAsync(null); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], globalEndpointManager.WriteEndpoints[0]); //Sleep a second for the unavailable endpoint entry to expire and background refresh timer to kick in - Thread.Sleep(1000); - globalEndpointManager.RefreshLocationAsync(null).Wait(); + Thread.Sleep(2000); + await globalEndpointManager.RefreshLocationAsync(null); Assert.AreEqual(globalEndpointManager.ReadEndpoints[0], new Uri(readLocation1.Endpoint)); }