Improve error message handling (#497)

- Add special handling for token expired errors so they send with a clear flag that'll allow clients to take action on this case
- Send error message instead of callstack for all messages, and ensure all resource manager paths send back inner exceptions so users can understand the true root cause.
This commit is contained in:
Kevin Cunnane
2017-10-13 17:48:25 -07:00
committed by GitHub
parent b416951414
commit 0c7f559315
17 changed files with 381 additions and 275 deletions

View File

@@ -0,0 +1,53 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Runtime.Serialization;
namespace Microsoft.SqlTools.ResourceProvider.Core
{
/// <summary>
/// The exception is used if any operation fails as a request failed due to an expired token
/// </summary>
public class ExpiredTokenException : ServiceExceptionBase
{
/// <summary>
/// Initializes a new instance of the ServiceFailedException class.
/// </summary>
public ExpiredTokenException()
{
}
/// <summary>
/// Initializes a new instance of the ServiceFailedException class with a specified error message.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception. </param>
public ExpiredTokenException(string message)
: base(message)
{
}
/// <summary>
/// Initializes a new instance of the ServiceFailedException class with a specified error message
/// and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception. </param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference
/// (Nothing in Visual Basic) if no inner exception is specified</param>
public ExpiredTokenException(string message, Exception innerException)
: base(message, innerException)
{
}
/// <summary>
/// Initializes a new instance of the ServiceFailedException class with serialized data.
/// </summary>
/// <param name="info">The SerializationInfo that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The StreamingContext that contains contextual information about the source or destination.</param>
public ExpiredTokenException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}

View File

@@ -50,9 +50,11 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts
}
public class CreateFirewallRuleResponse
public class CreateFirewallRuleResponse : TokenReliantResponse
{
public bool Result { get; set; }
/// <summary>
/// An error message for why the request failed, if any
/// </summary>
public string ErrorMessage { get; set; }
}
@@ -97,6 +99,4 @@ namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts
/// </summary>
public string IpAddress { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using System.Collections.Generic;
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
namespace Microsoft.SqlTools.ResourceProvider.Core.Contracts
{
/// <summary>
/// Any response which relies on a token may indicated that the operation failed due to token being expired.
/// All operational response messages should inherit from this class in order to support a standard method for defining
/// this failure path
/// </summary>
public class TokenReliantResponse
{
/// <summary>
/// Did this succeed?
/// </summary>
public bool Result { get; set; }
/// <summary>
/// If this failed, was it due to a token expiring?
/// </summary>
public bool IsTokenExpiredFailure { get; set; }
}
}

View File

@@ -27,7 +27,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
Keys.Culture = value;
}
}
public static string NoSubscriptionsFound
{
@@ -35,7 +35,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.NoSubscriptionsFound);
}
}
}
public static string AzureServerNotFound
{
@@ -43,7 +43,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.AzureServerNotFound);
}
}
}
public static string AzureSubscriptionFailedErrorMessage
{
@@ -51,7 +51,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.AzureSubscriptionFailedErrorMessage);
}
}
}
public static string DatabaseDiscoveryFailedErrorMessage
{
@@ -59,7 +59,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.DatabaseDiscoveryFailedErrorMessage);
}
}
}
public static string FirewallRuleAccessForbidden
{
@@ -67,7 +67,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.FirewallRuleAccessForbidden);
}
}
}
public static string FirewallRuleCreationFailed
{
@@ -75,7 +75,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.FirewallRuleCreationFailed);
}
}
}
public static string FirewallRuleCreationFailedWithError
{
@@ -83,7 +83,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.FirewallRuleCreationFailedWithError);
}
}
}
public static string InvalidIpAddress
{
@@ -91,7 +91,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.InvalidIpAddress);
}
}
}
public static string InvalidServerTypeErrorMessage
{
@@ -99,7 +99,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.InvalidServerTypeErrorMessage);
}
}
}
public static string LoadingExportableFailedGeneralErrorMessage
{
@@ -107,7 +107,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.LoadingExportableFailedGeneralErrorMessage);
}
}
}
public static string FirewallRuleUnsupportedConnectionType
{
@@ -115,7 +115,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return Keys.GetString(Keys.FirewallRuleUnsupportedConnectionType);
}
}
}
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
public class Keys
@@ -123,40 +123,40 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
static ResourceManager resourceManager = new ResourceManager("Microsoft.SqlTools.ResourceProvider.Core.Localization.SR", typeof(SR).GetTypeInfo().Assembly);
static CultureInfo _culture = null;
public const string NoSubscriptionsFound = "NoSubscriptionsFound";
public const string AzureServerNotFound = "AzureServerNotFound";
public const string AzureSubscriptionFailedErrorMessage = "AzureSubscriptionFailedErrorMessage";
public const string DatabaseDiscoveryFailedErrorMessage = "DatabaseDiscoveryFailedErrorMessage";
public const string FirewallRuleAccessForbidden = "FirewallRuleAccessForbidden";
public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed";
public const string FirewallRuleCreationFailedWithError = "FirewallRuleCreationFailedWithError";
public const string InvalidIpAddress = "InvalidIpAddress";
public const string InvalidServerTypeErrorMessage = "InvalidServerTypeErrorMessage";
public const string LoadingExportableFailedGeneralErrorMessage = "LoadingExportableFailedGeneralErrorMessage";
public const string FirewallRuleUnsupportedConnectionType = "FirewallRuleUnsupportedConnectionType";
public const string NoSubscriptionsFound = "NoSubscriptionsFound";
public const string AzureServerNotFound = "AzureServerNotFound";
public const string AzureSubscriptionFailedErrorMessage = "AzureSubscriptionFailedErrorMessage";
public const string DatabaseDiscoveryFailedErrorMessage = "DatabaseDiscoveryFailedErrorMessage";
public const string FirewallRuleAccessForbidden = "FirewallRuleAccessForbidden";
public const string FirewallRuleCreationFailed = "FirewallRuleCreationFailed";
public const string FirewallRuleCreationFailedWithError = "FirewallRuleCreationFailedWithError";
public const string InvalidIpAddress = "InvalidIpAddress";
public const string InvalidServerTypeErrorMessage = "InvalidServerTypeErrorMessage";
public const string LoadingExportableFailedGeneralErrorMessage = "LoadingExportableFailedGeneralErrorMessage";
public const string FirewallRuleUnsupportedConnectionType = "FirewallRuleUnsupportedConnectionType";
private Keys()
{ }
@@ -177,7 +177,7 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return resourceManager.GetString(key, _culture);
}
}
}
}
}
}
}

View File

@@ -120,45 +120,45 @@
<data name="NoSubscriptionsFound" xml:space="preserve">
<value>No subscriptions were found for the currently logged in user account.</value>
<comment></comment>
</data>
</data>
<data name="AzureServerNotFound" xml:space="preserve">
<value>The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.</value>
<comment></comment>
</data>
</data>
<data name="AzureSubscriptionFailedErrorMessage" xml:space="preserve">
<value>An error occurred while getting Azure subscriptions</value>
<value>An error occurred while getting Azure subscriptions: {0}</value>
<comment></comment>
</data>
</data>
<data name="DatabaseDiscoveryFailedErrorMessage" xml:space="preserve">
<value>An error occurred while getting databases from servers of type {0} from {1}</value>
<comment></comment>
</data>
</data>
<data name="FirewallRuleAccessForbidden" xml:space="preserve">
<value>{0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.</value>
<comment></comment>
</data>
</data>
<data name="FirewallRuleCreationFailed" xml:space="preserve">
<value>An error occurred while creating a new firewall rule.</value>
<comment></comment>
</data>
</data>
<data name="FirewallRuleCreationFailedWithError" xml:space="preserve">
<value>An error occurred while creating a new firewall rule: '{0}'</value>
<comment></comment>
</data>
</data>
<data name="InvalidIpAddress" xml:space="preserve">
<value>Invalid IP address</value>
<comment></comment>
</data>
</data>
<data name="InvalidServerTypeErrorMessage" xml:space="preserve">
<value>Server Type is invalid.</value>
<comment></comment>
</data>
</data>
<data name="LoadingExportableFailedGeneralErrorMessage" xml:space="preserve">
<value>A required dll cannot be loaded. Please repair your application.</value>
<comment></comment>
</data>
</data>
<data name="FirewallRuleUnsupportedConnectionType" xml:space="preserve">
<value>Cannot open a firewall rule for the specified connection type</value>
<comment></comment>
</data>
</root>
</data>
</root>

View File

@@ -24,7 +24,7 @@
# Azure Core DLL
NoSubscriptionsFound = No subscriptions were found for the currently logged in user account.
AzureServerNotFound = The server you specified {0} does not exist in any subscription in {1}. Either you have signed in with an incorrect account or your server was removed from subscription(s) in this account. Please check your account and try again.
AzureSubscriptionFailedErrorMessage = An error occurred while getting Azure subscriptions
AzureSubscriptionFailedErrorMessage = An error occurred while getting Azure subscriptions: {0}
DatabaseDiscoveryFailedErrorMessage = An error occurred while getting databases from servers of type {0} from {1}
FirewallRuleAccessForbidden = {0} does not have permission to change the server firewall rule. Try again with a different account that is an Owner or Contributor of the Azure subscription or the server.
FirewallRuleCreationFailed = An error occurred while creating a new firewall rule.

View File

@@ -8,7 +8,7 @@
<note></note>
</trans-unit>
<trans-unit id="AzureSubscriptionFailedErrorMessage">
<source>An error occurred while getting Azure subscriptions</source>
<source>An error occurred while getting Azure subscriptions: {0}</source>
<target state="new">An error occurred while getting Azure subscriptions</target>
<note></note>
</trans-unit>

View File

@@ -56,7 +56,16 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
{
return DoHandleCreateFirewallRuleRequest(firewallRule);
};
await HandleRequest(requestHandler, requestContext, "HandleCreateFirewallRuleRequest");
Func<ExpiredTokenException, CreateFirewallRuleResponse> tokenExpiredHandler = (ExpiredTokenException ex) =>
{
return new CreateFirewallRuleResponse()
{
Result = false,
IsTokenExpiredFailure = true,
ErrorMessage = ex.Message
};
};
await HandleRequest(requestHandler, tokenExpiredHandler, requestContext, "HandleCreateFirewallRuleRequest");
}
private async Task<CreateFirewallRuleResponse> DoHandleCreateFirewallRuleRequest(CreateFirewallRuleParams firewallRule)
@@ -98,10 +107,10 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
}
return Task.FromResult(response);
};
await HandleRequest(requestHandler, requestContext, "HandleCreateFirewallRuleRequest");
await HandleRequest(requestHandler, null, requestContext, "HandleCreateFirewallRuleRequest");
}
private async Task HandleRequest<T>(Func<Task<T>> handler, RequestContext<T> requestContext, string requestType)
private async Task HandleRequest<T>(Func<Task<T>> handler, Func<ExpiredTokenException, T> expiredTokenHandler, RequestContext<T> requestContext, string requestType)
{
Logger.Write(LogLevel.Verbose, requestType);
@@ -110,9 +119,25 @@ namespace Microsoft.SqlTools.ResourceProvider.Core
T result = await handler();
await requestContext.SendResult(result);
}
catch(ExpiredTokenException ex)
{
if (expiredTokenHandler != null)
{
// This is a special exception indicating the token(s) used to request resources had expired.
// Any Azure resource should have handling for this such as an error path that clearly indicates that a refresh is needed
T result = expiredTokenHandler(ex);
await requestContext.SendResult(result);
}
else
{
// No handling for expired tokens defined / expected
await requestContext.SendError(ex.Message);
}
}
catch (Exception ex)
{
await requestContext.SendError(ex.ToString());
// Send just the error message back for now as stack trace isn't useful
await requestContext.SendError(ex.Message);
}
}
}