Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw error from Response #23475

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7867325
init
annelo-msft Jul 23, 2021
9162980
generate LLC
annelo-msft Jul 23, 2021
1d9acf5
move to local swagger files
annelo-msft Jul 23, 2021
a5a65ab
update client
annelo-msft Jul 23, 2021
c37444d
client options
annelo-msft Jul 23, 2021
ffa2e24
add LLC sample
annelo-msft Jul 23, 2021
7aeb6d3
add CreateRoleAssignment method
annelo-msft Jul 23, 2021
034a174
Merge remote-tracking branch 'upstream/main' into users/annelo/synaps…
annelo-msft Aug 4, 2021
dfc8f35
add customization layer for several APIs
wanyang7 Aug 6, 2021
a8900ba
add role definition related APIs
wanyang7 Aug 11, 2021
da8dcc2
Merge pull request #1 from wonner/llc-accesscontrol
annelo-msft Aug 11, 2021
88ea366
Merge remote-tracking branch 'upstream/main' into users/annelo/synaps…
annelo-msft Aug 11, 2021
ecc788f
remove cancellation token from HLC methods
annelo-msft Aug 11, 2021
37a3cc0
interim checkin; in process of testing implicit cast operators
annelo-msft Aug 12, 2021
5a865cf
Merge remote-tracking branch 'upstream/main' into users/annelo/synaps…
annelo-msft Aug 13, 2021
8b0681c
upgrade to 2020-12-01 of AccessControl
annelo-msft Aug 13, 2021
7aac545
add autorest generated models
annelo-msft Aug 13, 2021
8b278f7
remove generated models and add test for CreateRoleAssignment overload
annelo-msft Aug 13, 2021
5930a0b
Merge remote-tracking branch 'upstream/main' into users/annelo/synaps…
annelo-msft Aug 23, 2021
fce3b2a
move to project referencse for Core
annelo-msft Aug 17, 2021
7627b8d
refactor to put Sanitizer and Classifier on transport
annelo-msft Aug 17, 2021
f249fb6
wip; need to resolve nullables
annelo-msft Aug 18, 2021
63d2210
set options and classifier in SendAsync
annelo-msft Aug 18, 2021
dcae304
missed file
annelo-msft Aug 18, 2021
90739ec
update to move exception formatting logic to RequestClassifier
annelo-msft Aug 20, 2021
3e72195
updates
annelo-msft Aug 20, 2021
ff0c952
WIP
annelo-msft Aug 21, 2021
2338b56
put Message on Response instead of classifier
annelo-msft Aug 23, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ public ValueTask SendAsync(HttpMessage message, CancellationToken cancellationTo
{
message.CancellationToken = cancellationToken;
AddHttpMessageProperties(message);
return _pipeline.Span[0].ProcessAsync(message, _pipeline.Slice(1));
var value = _pipeline.Span[0].ProcessAsync(message, _pipeline.Slice(1));
message.Response.Message = message;
return value;
}

/// <summary>
Expand All @@ -83,6 +85,7 @@ public void Send(HttpMessage message, CancellationToken cancellationToken)
message.CancellationToken = cancellationToken;
AddHttpMessageProperties(message);
_pipeline.Span[0].Process(message, _pipeline.Slice(1));
message.Response.Message = message;
}
/// <summary>
/// Invokes the pipeline asynchronously with the provided request.
Expand Down
30 changes: 30 additions & 0 deletions sdk/core/Azure.Core/src/Response.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Azure.Core;
using Azure.Core.Pipeline;

namespace Azure
{
Expand Down Expand Up @@ -111,6 +113,34 @@ public virtual BinaryData Content
/// <returns>The <see cref="IEnumerable{T}"/> enumerating <see cref="HttpHeader"/> in the response.</returns>
protected internal abstract IEnumerable<HttpHeader> EnumerateHeaders();

internal HttpMessage? Message { get; set; }

/// <summary>
/// </summary>
/// <returns></returns>
public bool IsError()
{
return this.Message!.ResponseClassifier.IsErrorResponse(this.Message);
}

/// <summary>
/// Throw a RequestFailedException appropriate to the Response.
/// </summary>
public void Throw()
{
throw this.Message!.ResponseClassifier.CreateRequestFailedException(this);
//throw new RequestFailedException("<error message>");
}

/// <summary>
/// Throw a RequestFailedException appropriate to the Response.
/// </summary>
public async Task ThrowAsync()
{
throw await this.Message!.ResponseClassifier.CreateRequestFailedExceptionAsync(this).ConfigureAwait(false);
//throw new RequestFailedException("<error message>");
}

/// <summary>
/// Creates a new instance of <see cref="Response{T}"/> with the provided value and HTTP response.
/// </summary>
Expand Down
191 changes: 191 additions & 0 deletions sdk/core/Azure.Core/src/ResponseClassifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using Azure.Core.Pipeline;

namespace Azure.Core
{
Expand All @@ -11,6 +18,30 @@ namespace Azure.Core
/// </summary>
public class ResponseClassifier
{
private const string DefaultFailureMessage = "Service request failed.";

private readonly HttpMessageSanitizer _sanitizer;

/// <summary>
/// Initializes a new instance of <see cref="ResponseClassifier"/>.
/// </summary>
public ResponseClassifier() : this(new DefaultClientOptions())
{
}

/// <summary>
/// </summary>

/// <summary>
/// Initializes a new instance of <see cref="ResponseClassifier"/>.
/// </summary>
public ResponseClassifier(ClientOptions options)
{
_sanitizer = new HttpMessageSanitizer(
options?.Diagnostics.LoggedQueryParameters.ToArray() ?? Array.Empty<string>(),
options?.Diagnostics.LoggedHeaderNames.ToArray() ?? Array.Empty<string>());
}

/// <summary>
/// Specifies if the request contained in the <paramref name="message"/> should be retried.
/// </summary>
Expand Down Expand Up @@ -57,5 +88,165 @@ public virtual bool IsErrorResponse(HttpMessage message)
var statusKind = message.Response.Status / 100;
return statusKind == 4 || statusKind == 5;
}

/// <summary>
/// </summary>
public async ValueTask<RequestFailedException> CreateRequestFailedExceptionAsync(Response response, string? message = null, string? errorCode = null, IDictionary<string, string>? additionalInfo = null, Exception? innerException = null)
{
var content = await ReadContentAsync(response, true).ConfigureAwait(false);
ExtractFailureContent(content, ref message, ref errorCode);
return CreateRequestFailedExceptionWithContent(response, message, content, errorCode, additionalInfo, innerException);
}

/// <summary>
/// </summary>
public RequestFailedException CreateRequestFailedException(Response response, string? message = null, string? errorCode = null, IDictionary<string, string>? additionalInfo = null, Exception? innerException = null)
{
string? content = ReadContentAsync(response, false).EnsureCompleted();
ExtractFailureContent(content, ref message, ref errorCode);
return CreateRequestFailedExceptionWithContent(response, message, content, errorCode, additionalInfo, innerException);
}

private static async ValueTask<string?> ReadContentAsync(Response response, bool async)
{
string? content = null;

if (response.ContentStream != null &&
ContentTypeUtilities.TryGetTextEncoding(response.Headers.ContentType, out var encoding))
{
using (var streamReader = new StreamReader(response.ContentStream, encoding))
{
content = async ? await streamReader.ReadToEndAsync().ConfigureAwait(false) : streamReader.ReadToEnd();
}
}

return content;
}

private static void ExtractFailureContent(
string? content,
ref string? message,
ref string? errorCode)
{
try
{
// Optimistic check for JSON object we expect
if (content == null ||
!content.StartsWith("{", StringComparison.OrdinalIgnoreCase))
return;

string? parsedMessage = null;
using JsonDocument document = JsonDocument.Parse(content);
if (document.RootElement.TryGetProperty("error", out var errorProperty))
{
if (errorProperty.TryGetProperty("code", out var codeProperty))
{
errorCode = codeProperty.GetString();
}
if (errorProperty.TryGetProperty("message", out var messageProperty))
{
parsedMessage = messageProperty.GetString();
}
}

// Make sure we parsed a message so we don't overwrite the value with null
if (parsedMessage != null)
{
message = parsedMessage;
}
}
catch (Exception)
{
// Ignore any failures - unexpected content will be
// included verbatim in the detailed error message
}
}

private RequestFailedException CreateRequestFailedExceptionWithContent(
Response response,
string? message = null,
string? content = null,
string? errorCode = null,
IDictionary<string, string>? additionalInfo = null,
Exception? innerException = null)
{
var formatMessage = CreateRequestFailedMessageWithContent(response, message, content, errorCode, additionalInfo);
var exception = new RequestFailedException(response.Status, formatMessage, errorCode, innerException);

if (additionalInfo != null)
{
foreach (KeyValuePair<string, string> keyValuePair in additionalInfo)
{
exception.Data.Add(keyValuePair.Key, keyValuePair.Value);
}
}

return exception;
}

private string CreateRequestFailedMessageWithContent(
Response response,
string? message,
string? content,
string? errorCode,
IDictionary<string, string>? additionalInfo)
{
StringBuilder messageBuilder = new StringBuilder()
.AppendLine(message ?? DefaultFailureMessage)
.Append("Status: ")
.Append(response.Status.ToString(CultureInfo.InvariantCulture));

if (!string.IsNullOrEmpty(response.ReasonPhrase))
{
messageBuilder.Append(" (")
.Append(response.ReasonPhrase)
.AppendLine(")");
}
else
{
messageBuilder.AppendLine();
}

if (!string.IsNullOrWhiteSpace(errorCode))
{
messageBuilder.Append("ErrorCode: ")
.Append(errorCode)
.AppendLine();
}

if (additionalInfo != null && additionalInfo.Count > 0)
{
messageBuilder
.AppendLine()
.AppendLine("Additional Information:");
foreach (KeyValuePair<string, string> info in additionalInfo)
{
messageBuilder
.Append(info.Key)
.Append(": ")
.AppendLine(info.Value);
}
}

if (content != null)
{
messageBuilder
.AppendLine()
.AppendLine("Content:")
.AppendLine(content);
}

messageBuilder
.AppendLine()
.AppendLine("Headers:");

foreach (HttpHeader responseHeader in response.Headers)
{
string headerValue = _sanitizer.SanitizeHeader(responseHeader.Name, responseHeader.Value);
messageBuilder.AppendLine($"{responseHeader.Name}: {headerValue}");
}

return messageBuilder.ToString();
}
}
}
Loading