diff --git a/sdk/core/Azure.Core/src/Pipeline/AzureError.cs b/sdk/core/Azure.Core/src/Pipeline/AzureError.cs new file mode 100644 index 0000000000000..6a7a3b1519b74 --- /dev/null +++ b/sdk/core/Azure.Core/src/Pipeline/AzureError.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Text; + +namespace Azure.Core.Pipeline +{ + /// + /// + public class AzureError + { + /// + /// + /// + public AzureError() + { + Data = new Dictionary(); + } + + /// + /// Gets or sets the error message. + /// + public string? Message { get; set; } + + /// + /// Gets or sets the error code. + /// + public string? ErrorCode { get; set; } + + /// + /// Gets an additional data returned with the error response. + /// + public IDictionary Data { get; } + } +} diff --git a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs index c283475c0d86e..2e41a8fad7442 100644 --- a/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs +++ b/sdk/core/Azure.Core/src/Pipeline/HttpPipeline.cs @@ -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.EvaluateError(message); + return value; } /// @@ -83,7 +85,9 @@ public void Send(HttpMessage message, CancellationToken cancellationToken) message.CancellationToken = cancellationToken; AddHttpMessageProperties(message); _pipeline.Span[0].Process(message, _pipeline.Slice(1)); + message.Response.EvaluateError(message); } + /// /// Invokes the pipeline asynchronously with the provided request. /// diff --git a/sdk/core/Azure.Core/src/Response.cs b/sdk/core/Azure.Core/src/Response.cs index e31a6ec4752ad..65ea569f9d17b 100644 --- a/sdk/core/Azure.Core/src/Response.cs +++ b/sdk/core/Azure.Core/src/Response.cs @@ -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 { @@ -111,6 +113,51 @@ public virtual BinaryData Content /// The enumerating in the response. protected internal abstract IEnumerable EnumerateHeaders(); + internal bool? IsError { get; set; } + + internal ResponseClassifier? ResponseClassifier { get; set; } + + internal void EvaluateError(HttpMessage message) + { + if (!IsError.HasValue) + { + IsError = message.ResponseClassifier.IsErrorResponse(message); + ResponseClassifier = message.ResponseClassifier; + } + } + + /// + /// If the response is an error response, throw a RequestFailedException. + /// + public void ThrowIfError() + { + if (!IsError.HasValue) + { + throw new InvalidOperationException("IsError value should have been cached by the pipeline."); + } + + if (IsError.Value) + { + throw ResponseClassifier!.CreateRequestFailedException(this); + } + } + + /// + /// If the response is an error response, throw a RequestFailedException. + /// + public async Task ThrowIfErrorAsync() + { + if (!IsError.HasValue) + { + throw new InvalidOperationException("IsError value should have been cached by the pipeline."); + } + + if (IsError.Value) + { + throw await ResponseClassifier!.CreateRequestFailedExceptionAsync(this).ConfigureAwait(false); + } + } + /// /// Creates a new instance of with the provided value and HTTP response. /// diff --git a/sdk/core/Azure.Core/src/ResponseClassifier.cs b/sdk/core/Azure.Core/src/ResponseClassifier.cs index e770e6e43271f..54d6226975acc 100644 --- a/sdk/core/Azure.Core/src/ResponseClassifier.cs +++ b/sdk/core/Azure.Core/src/ResponseClassifier.cs @@ -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 { @@ -11,6 +18,30 @@ namespace Azure.Core /// public class ResponseClassifier { + private const string DefaultFailureMessage = "Service request failed."; + + private readonly HttpMessageSanitizer _sanitizer; + + /// + /// Initializes a new instance of . + /// + public ResponseClassifier() : this(new DefaultClientOptions()) + { + } + + /// + /// + + /// + /// Initializes a new instance of . + /// + public ResponseClassifier(ClientOptions options) + { + _sanitizer = new HttpMessageSanitizer( + options?.Diagnostics.LoggedQueryParameters.ToArray() ?? Array.Empty(), + options?.Diagnostics.LoggedHeaderNames.ToArray() ?? Array.Empty()); + } + /// /// Specifies if the request contained in the should be retried. /// @@ -57,5 +88,191 @@ public virtual bool IsErrorResponse(HttpMessage message) var statusKind = message.Response.Status / 100; return statusKind == 4 || statusKind == 5; } + + /// + /// Creates an instance of for the provided failed . + /// + /// + /// + /// + /// + public virtual async ValueTask CreateRequestFailedExceptionAsync( + Response response, + AzureError? error = null, + Exception? innerException = null) + { + var content = await ReadContentAsync(response, true).ConfigureAwait(false); + return CreateRequestFailedExceptionWithContent(response, content, error, innerException); + } + + /// + /// Creates an instance of for the provided failed . + /// + /// + /// + /// + /// + public virtual RequestFailedException CreateRequestFailedException( + Response response, + AzureError? error = null, + Exception? innerException = null) + { + string? content = ReadContentAsync(response, false).EnsureCompleted(); + return CreateRequestFailedExceptionWithContent(response, content, error, innerException); + } + + /// + /// Partial method that can optionally be defined to extract the error + /// message, code, and details in a service specific manner. + /// + /// The response headers. + /// The extracted text content + protected virtual AzureError? ExtractFailureContent(Response response, string? textContent) + { + try + { + // Optimistic check for JSON object we expect + if (textContent == null || + !textContent.StartsWith("{", StringComparison.OrdinalIgnoreCase)) + return null; + + var extractFailureContent = new AzureError(); + + using JsonDocument document = JsonDocument.Parse(textContent); + if (document.RootElement.TryGetProperty("error", out var errorProperty)) + { + if (errorProperty.TryGetProperty("code", out var codeProperty)) + { + extractFailureContent.ErrorCode = codeProperty.GetString(); + } + if (errorProperty.TryGetProperty("message", out var messageProperty)) + { + extractFailureContent.Message = messageProperty.GetString(); + } + } + + return extractFailureContent; + } + catch (Exception) + { + // Ignore any failures - unexpected content will be + // included verbatim in the detailed error message + } + + return null; + } + + private RequestFailedException CreateRequestFailedExceptionWithContent( + Response response, + string? content, + AzureError? details, + Exception? innerException) + { + var errorInformation = ExtractFailureContent(response, content); + + var message = details?.Message ?? errorInformation?.Message ?? DefaultFailureMessage; + var errorCode = details?.ErrorCode ?? errorInformation?.ErrorCode; + + IDictionary? data = null; + if (errorInformation?.Data != null) + { + if (details?.Data == null) + { + data = errorInformation.Data; + } + else + { + data = new Dictionary(details.Data); + foreach (var pair in errorInformation.Data) + { + data[pair.Key] = pair.Value; + } + } + } + + StringBuilder messageBuilder = new StringBuilder() + .AppendLine(message) + .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 (data != null && data.Count > 0) + { + messageBuilder + .AppendLine() + .AppendLine("Additional Information:"); + foreach (KeyValuePair info in data) + { + messageBuilder + .Append(info.Key) + .Append(": ") + .Append(info.Value) + .AppendLine(); + } + } + + 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}"); + } + + var exception = new RequestFailedException(response.Status, messageBuilder.ToString(), errorCode, innerException); + + if (data != null) + { + foreach (KeyValuePair keyValuePair in data) + { + exception.Data.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + return exception; + } + + private static async ValueTask 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; + } } } diff --git a/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs b/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs index a7dac8c8e88f5..440270a002c6a 100644 --- a/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs +++ b/sdk/core/Azure.Core/src/Shared/ClientDiagnostics.cs @@ -3,16 +3,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; -using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Text.Json; using System.Threading.Tasks; -using Azure.Core.Pipeline; #nullable enable @@ -20,184 +17,11 @@ namespace Azure.Core.Pipeline { internal class ClientDiagnostics : DiagnosticScopeFactory { - private const string DefaultMessage = "Service request failed."; - - private readonly HttpMessageSanitizer _sanitizer; public ClientDiagnostics(ClientOptions options) : base( options.GetType().Namespace!, GetResourceProviderNamespace(options.GetType().Assembly), options.Diagnostics.IsDistributedTracingEnabled) { - _sanitizer = new HttpMessageSanitizer( - options.Diagnostics.LoggedQueryParameters.ToArray(), - options.Diagnostics.LoggedHeaderNames.ToArray()); - } - - /// - /// Partial method that can optionally be defined to extract the error - /// message, code, and details in a service specific manner. - /// - /// The error content. - /// The response headers. - /// The error message. - /// The error code. - /// Additional error details. - protected virtual void ExtractFailureContent( - string? content, - ResponseHeaders responseHeaders, - ref string? message, - ref string? errorCode, - ref IDictionary? additionalInfo) - { - 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 - } - } - - public async ValueTask CreateRequestFailedExceptionAsync(Response response, string? message = null, string? errorCode = null, IDictionary? additionalInfo = null, Exception? innerException = null) - { - var content = await ReadContentAsync(response, true).ConfigureAwait(false); - ExtractFailureContent(content, response.Headers, ref message, ref errorCode, ref additionalInfo); - return CreateRequestFailedExceptionWithContent(response, message, content, errorCode, additionalInfo, innerException); - } - - public RequestFailedException CreateRequestFailedException(Response response, string? message = null, string? errorCode = null, IDictionary? additionalInfo = null, Exception? innerException = null) - { - string? content = ReadContentAsync(response, false).EnsureCompleted(); - ExtractFailureContent(content, response.Headers, ref message, ref errorCode, ref additionalInfo); - return CreateRequestFailedExceptionWithContent(response, message, content, errorCode, additionalInfo, innerException); - } - - public RequestFailedException CreateRequestFailedExceptionWithContent( - Response response, - string? message = null, - string? content = null, - string? errorCode = null, - IDictionary? 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 keyValuePair in additionalInfo) - { - exception.Data.Add(keyValuePair.Key, keyValuePair.Value); - } - } - - return exception; - } - - public async ValueTask CreateRequestFailedMessageAsync(Response response, string? message, string? errorCode, IDictionary? additionalInfo, bool async) - { - var content = await ReadContentAsync(response, async).ConfigureAwait(false); - return CreateRequestFailedMessageWithContent(response, message, content, errorCode, additionalInfo); - } - - public string CreateRequestFailedMessageWithContent(Response response, string? message, string? content, string? errorCode, IDictionary? additionalInfo) - { - StringBuilder messageBuilder = new StringBuilder() - .AppendLine(message ?? DefaultMessage) - .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 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(); - } - - private static async ValueTask 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; } internal static string? GetResourceProviderNamespace(Assembly assembly) diff --git a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Azure.Analytics.Synapse.AccessControl.csproj b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Azure.Analytics.Synapse.AccessControl.csproj index 50f6ae6f1918e..497eb0123dee1 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Azure.Analytics.Synapse.AccessControl.csproj +++ b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Azure.Analytics.Synapse.AccessControl.csproj @@ -18,8 +18,6 @@ - - @@ -38,5 +36,9 @@ + + + + diff --git a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Generated/SynapseAccessControlClient.cs b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Generated/SynapseAccessControlClient.cs index 2a12a3c5b376b..5666338325d03 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Generated/SynapseAccessControlClient.cs +++ b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Generated/SynapseAccessControlClient.cs @@ -23,6 +23,7 @@ public partial class SynapseAccessControlClient private Uri endpoint; private readonly string apiVersion; private readonly ClientDiagnostics _clientDiagnostics; + private readonly ResponseClassifier _responseClassifier; /// Initializes a new instance of SynapseAccessControlClient for mocking. protected SynapseAccessControlClient() @@ -46,6 +47,7 @@ public SynapseAccessControlClient(Uri endpoint, TokenCredential credential, Syna options ??= new SynapseAdministrationClientOptions(); _clientDiagnostics = new ClientDiagnostics(options); + _responseClassifier = new ResponseClassifier(options); _tokenCredential = credential; var authPolicy = new BearerTokenAuthenticationPolicy(_tokenCredential, AuthorizationScopes); Pipeline = HttpPipelineBuilder.Build(options, new HttpPipelinePolicy[] { new LowLevelCallbackPolicy() }, new HttpPipelinePolicy[] { authPolicy }, new ResponseClassifier()); @@ -149,7 +151,7 @@ public virtual async Task CheckPrincipalAccessAsync(RequestContent con case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -260,7 +262,7 @@ public virtual Response CheckPrincipalAccess(RequestContent content, RequestOpti case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -322,7 +324,7 @@ public virtual async Task ListRoleAssignmentsAsync(string roleId = nul case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -365,7 +367,7 @@ public virtual Response ListRoleAssignments(string roleId = null, string princip case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -461,10 +463,15 @@ public virtual async Task CreateRoleAssignmentAsync(string roleAssignm { options ??= new RequestOptions(); HttpMessage message = CreateCreateRoleAssignmentRequest(roleAssignmentId, content, options); - if (options.PerCallPolicy != null) - { - message.SetProperty("RequestOptionsPerCallPolicyCallback", options.PerCallPolicy); - } + + // The following: + // 1. Optionally adds a policy to this pipeline for the scope of the message + // 2. Overwrites this message's ResponseClassifier with classification options set on RequestOptions + // + // Note: we are essentially configuring the pipeline for the request and response + // before we call Pipeline.SendAsync(); + RequestOptions.Apply(options, message); + using var scope0 = _clientDiagnostics.CreateScope("SynapseAccessControlClient.CreateRoleAssignment"); scope0.Start(); try @@ -472,18 +479,10 @@ public virtual async Task CreateRoleAssignmentAsync(string roleAssignm await Pipeline.SendAsync(message, options.CancellationToken).ConfigureAwait(false); if (options.StatusOption == ResponseStatusOption.Default) { - switch (message.Response.Status) - { - case 200: - return message.Response; - default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); - } - } - else - { - return message.Response; + await message.Response.ThrowIfErrorAsync().ConfigureAwait(false); } + + return message.Response; } catch (Exception e) { @@ -553,7 +552,7 @@ public virtual Response CreateRoleAssignment(string roleAssignmentId, RequestCon case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -614,7 +613,7 @@ public virtual async Task GetRoleAssignmentByIdAsync(string roleAssign case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -654,7 +653,7 @@ public virtual Response GetRoleAssignmentById(string roleAssignmentId, RequestOp case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -714,7 +713,7 @@ public virtual async Task DeleteRoleAssignmentByIdAsync(string roleAss case 204: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -756,7 +755,7 @@ public virtual Response DeleteRoleAssignmentById(string roleAssignmentId, string case 204: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -820,7 +819,7 @@ public virtual async Task ListRoleDefinitionsAsync(bool? isBuiltIn = n case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -861,7 +860,7 @@ public virtual Response ListRoleDefinitions(bool? isBuiltIn = null, string scope case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -927,7 +926,7 @@ public virtual async Task GetRoleDefinitionByIdAsync(string roleDefini case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -967,7 +966,7 @@ public virtual Response GetRoleDefinitionById(string roleDefinitionId, RequestOp case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else @@ -1024,7 +1023,7 @@ public virtual async Task ListScopesAsync(RequestOptions options = nul case 200: return message.Response; default: - throw await _clientDiagnostics.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); + throw await _responseClassifier.CreateRequestFailedExceptionAsync(message.Response).ConfigureAwait(false); } } else @@ -1063,7 +1062,7 @@ public virtual Response ListScopes(RequestOptions options = null) case 200: return message.Response; default: - throw _clientDiagnostics.CreateRequestFailedException(message.Response); + throw _responseClassifier.CreateRequestFailedException(message.Response); } } else diff --git a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Models/SynapseRoleAssignment.cs b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Models/SynapseRoleAssignment.cs index d83b721e7b72b..7e668fe285530 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Models/SynapseRoleAssignment.cs +++ b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/src/Models/SynapseRoleAssignment.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Text.Json; using Azure.Core; namespace Azure.Analytics.Synapse.AccessControl @@ -41,5 +42,21 @@ public static implicit operator RequestContent(SynapseRoleAssignment value) => R Id = value.Id, Properties = value.Properties }); + + public static implicit operator SynapseRoleAssignment(Response response) + { + response.ThrowIfError(); // What about async? + return DeserializeResponse(response); + } + + private static SynapseRoleAssignment DeserializeResponse(Response response) + { + JsonDocument roleAssignment = JsonDocument.Parse(response.Content.ToMemory()); + return new SynapseRoleAssignment( + roleAssignment.RootElement.GetProperty("id").GetGuid(), + roleAssignment.RootElement.GetProperty("principalId").GetGuid(), + roleAssignment.RootElement.GetProperty("roleDefinitionId").GetGuid(), + roleAssignment.RootElement.GetProperty("scope").ToString()); + } } } diff --git a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/tests/AccessControlClientLiveTests.cs b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/tests/AccessControlClientLiveTests.cs index 79b58e6219a35..c402ac4d324ec 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.AccessControl/tests/AccessControlClientLiveTests.cs +++ b/sdk/synapse/Azure.Analytics.Synapse.AccessControl/tests/AccessControlClientLiveTests.cs @@ -87,13 +87,26 @@ public async Task CreateRoleAssignment_ModelTypeParameterOverload() SynapseRoleAssignment roleAssignment = new SynapseRoleAssignment(roleId, principalId, scope); - await client.CreateRoleAssignmentAsync(roleAssignmentId, roleAssignment); - - await using DisposableClientRole role = await DisposableClientRole.Create(client, TestEnvironment); - - Assert.NotNull(role.Assignment.Id); - Assert.NotNull(role.Assignment.Properties.RoleDefinitionId); - Assert.NotNull(role.Assignment.Properties.PrincipalId); + // Calling the LLC directly: + Response response = await client.CreateRoleAssignmentAsync(roleAssignmentId, roleAssignment, + new RequestOptions() + { + StatusOption = ResponseStatusOption.NoThrow + }); + + // This is the implicit cast -- it will throw if the response is an error + // according to the classifier + SynapseRoleAssignment returnedRoleAssignment = response; + + // But since we suppressed the error, we might want to think + // about it the following way: + + await response.ThrowIfErrorAsync(); + + // else + Assert.NotNull(returnedRoleAssignment.Id); + Assert.NotNull(returnedRoleAssignment.Properties.RoleDefinitionId); + Assert.NotNull(returnedRoleAssignment.Properties.PrincipalId); } [Test] diff --git a/sdk/synapse/Azure.Analytics.Synapse.Artifacts/src/Azure.Analytics.Synapse.Artifacts.csproj b/sdk/synapse/Azure.Analytics.Synapse.Artifacts/src/Azure.Analytics.Synapse.Artifacts.csproj index 70437c33a89ff..1daf3a7bda92a 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.Artifacts/src/Azure.Analytics.Synapse.Artifacts.csproj +++ b/sdk/synapse/Azure.Analytics.Synapse.Artifacts/src/Azure.Analytics.Synapse.Artifacts.csproj @@ -37,5 +37,9 @@ + + + + diff --git a/sdk/synapse/Azure.Analytics.Synapse.ManagedPrivateEndpoints/src/Azure.Analytics.Synapse.ManagedPrivateEndpoints.csproj b/sdk/synapse/Azure.Analytics.Synapse.ManagedPrivateEndpoints/src/Azure.Analytics.Synapse.ManagedPrivateEndpoints.csproj index 4ecaa9333be08..3e85530abe188 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.ManagedPrivateEndpoints/src/Azure.Analytics.Synapse.ManagedPrivateEndpoints.csproj +++ b/sdk/synapse/Azure.Analytics.Synapse.ManagedPrivateEndpoints/src/Azure.Analytics.Synapse.ManagedPrivateEndpoints.csproj @@ -37,5 +37,9 @@ + + + + diff --git a/sdk/synapse/Azure.Analytics.Synapse.Monitoring/src/Azure.Analytics.Synapse.Monitoring.csproj b/sdk/synapse/Azure.Analytics.Synapse.Monitoring/src/Azure.Analytics.Synapse.Monitoring.csproj index a837b7ffcfe5c..1e05f94afebaa 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.Monitoring/src/Azure.Analytics.Synapse.Monitoring.csproj +++ b/sdk/synapse/Azure.Analytics.Synapse.Monitoring/src/Azure.Analytics.Synapse.Monitoring.csproj @@ -37,5 +37,9 @@ + + + + diff --git a/sdk/synapse/Azure.Analytics.Synapse.Spark/src/Azure.Analytics.Synapse.Spark.csproj b/sdk/synapse/Azure.Analytics.Synapse.Spark/src/Azure.Analytics.Synapse.Spark.csproj index 89d64c1d254f0..95dae92494fcb 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.Spark/src/Azure.Analytics.Synapse.Spark.csproj +++ b/sdk/synapse/Azure.Analytics.Synapse.Spark/src/Azure.Analytics.Synapse.Spark.csproj @@ -39,5 +39,9 @@ + + + + diff --git a/sdk/synapse/Azure.Analytics.Synapse.sln b/sdk/synapse/Azure.Analytics.Synapse.sln index 8be378f54fee2..77e004e479612 100644 --- a/sdk/synapse/Azure.Analytics.Synapse.sln +++ b/sdk/synapse/Azure.Analytics.Synapse.sln @@ -29,6 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Analytics.Synapse.Mon EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Analytics.Synapse.Monitoring.Tests", "Azure.Analytics.Synapse.Monitoring\tests\Azure.Analytics.Synapse.Monitoring.Tests.csproj", "{729E92B7-E47B-442C-836F-27B8A7806D2F}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core", "..\core\Azure.Core\src\Azure.Core.csproj", "{431E8B75-EE0F-46E9-BC1A-2BC4436E8C5D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.Core.Experimental", "..\core\Azure.Core.Experimental\src\Azure.Core.Experimental.csproj", "{8D8EB800-3496-4E0D-A2D0-F46CC2BA44DB}" +EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution Azure.Analytics.Synapse.Shared\tests\Azure.Analytics.Synapse.Shared.Tests.projitems*{1afa2644-a1d9-419f-b87d-9b519b673f24}*SharedItemsImports = 13 @@ -93,6 +97,14 @@ Global {729E92B7-E47B-442C-836F-27B8A7806D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU {729E92B7-E47B-442C-836F-27B8A7806D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU {729E92B7-E47B-442C-836F-27B8A7806D2F}.Release|Any CPU.Build.0 = Release|Any CPU + {431E8B75-EE0F-46E9-BC1A-2BC4436E8C5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {431E8B75-EE0F-46E9-BC1A-2BC4436E8C5D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {431E8B75-EE0F-46E9-BC1A-2BC4436E8C5D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {431E8B75-EE0F-46E9-BC1A-2BC4436E8C5D}.Release|Any CPU.Build.0 = Release|Any CPU + {8D8EB800-3496-4E0D-A2D0-F46CC2BA44DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8D8EB800-3496-4E0D-A2D0-F46CC2BA44DB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8D8EB800-3496-4E0D-A2D0-F46CC2BA44DB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8D8EB800-3496-4E0D-A2D0-F46CC2BA44DB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE