| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.Collections.Generic; |
| | 6 | | using System.Text.RegularExpressions; |
| | 7 | | using Microsoft.Azure.Amqp; |
| | 8 | | using Microsoft.Azure.Amqp.Encoding; |
| | 9 | | using Microsoft.Azure.Amqp.Framing; |
| | 10 | |
|
| | 11 | | namespace Azure.Messaging.EventHubs |
| | 12 | | { |
| | 13 | | /// <summary> |
| | 14 | | /// The set of well-known error codes associated with an AMQP messages and |
| | 15 | | /// entities. |
| | 16 | | /// </summary> |
| | 17 | | /// |
| | 18 | | internal static class AmqpError |
| | 19 | | { |
| | 20 | | /// <summary> |
| | 21 | | /// The status text that appears when an AMQP error was due to a missing resource. |
| | 22 | | /// </summary> |
| | 23 | | /// |
| | 24 | | private const string NotFoundStatusText = "status-code: 404"; |
| | 25 | |
|
| | 26 | | /// <summary> |
| | 27 | | /// Indicates that a timeout occurred on the link. |
| | 28 | | /// </summary> |
| | 29 | | /// |
| 144 | 30 | | public static AmqpSymbol TimeoutError { get; } = AmqpConstants.Vendor + ":timeout"; |
| | 31 | |
|
| | 32 | | /// <summary> |
| | 33 | | /// Indicates that the server was busy and could not allow the requested operation. |
| | 34 | | /// </summary> |
| | 35 | | /// |
| 164 | 36 | | public static AmqpSymbol ServerBusyError { get; } = AmqpConstants.Vendor + ":server-busy"; |
| | 37 | |
|
| | 38 | | /// <summary> |
| | 39 | | /// Indicates that an argument provided to the Event Hubs service was incorrect. |
| | 40 | | /// </summary> |
| | 41 | | /// |
| 112 | 42 | | public static AmqpSymbol ArgumentError { get; } = AmqpConstants.Vendor + ":argument-error"; |
| | 43 | |
|
| | 44 | | /// <summary> |
| | 45 | | /// Indicates that an argument provided to the Event Hubs service was incorrect. |
| | 46 | | /// </summary> |
| | 47 | | /// |
| 78 | 48 | | public static AmqpSymbol ArgumentOutOfRangeError { get; } = AmqpConstants.Vendor + ":argument-out-of-range"; |
| | 49 | |
|
| | 50 | | /// <summary> |
| | 51 | | /// The expression to test for when the service returns a "Not Found" response to determine the context. |
| | 52 | | /// </summary> |
| | 53 | | /// |
| 26 | 54 | | private static Regex NotFoundExpression { get; } = new Regex("The messaging entity .* could not be found", Regex |
| | 55 | |
|
| | 56 | | /// <summary>The set of mappings from AMQP error conditions to response status codes.</summary> |
| 2 | 57 | | private static readonly IReadOnlyDictionary<AmqpResponseStatusCode, AmqpSymbol> StatusCodeMap = new Dictionary<A |
| 2 | 58 | | { |
| 2 | 59 | | { AmqpResponseStatusCode.NotFound, AmqpErrorCode.NotFound }, |
| 2 | 60 | | { AmqpResponseStatusCode.NotImplemented, AmqpErrorCode.NotImplemented}, |
| 2 | 61 | | { AmqpResponseStatusCode.Unauthorized, AmqpErrorCode.UnauthorizedAccess }, |
| 2 | 62 | | { AmqpResponseStatusCode.Forbidden, AmqpErrorCode.ResourceLimitExceeded }, |
| 2 | 63 | | { AmqpResponseStatusCode.Gone, AmqpErrorCode.Stolen.Value }, |
| 2 | 64 | | { AmqpResponseStatusCode.InternalServerError, AmqpErrorCode.InternalError }, |
| 2 | 65 | | { AmqpResponseStatusCode.BadRequest, ArgumentError }, |
| 2 | 66 | | { AmqpResponseStatusCode.RequestTimeout, TimeoutError }, |
| 2 | 67 | | { AmqpResponseStatusCode.ServiceUnavailable, ServerBusyError } |
| 2 | 68 | | }; |
| | 69 | |
|
| | 70 | | /// <summary> |
| | 71 | | /// Creates the exception that corresponds to a given AMQP response message. |
| | 72 | | /// </summary> |
| | 73 | | /// |
| | 74 | | /// <param name="response">The response to consider.</param> |
| | 75 | | /// <param name="eventHubsResource">The Event Hubs resource associated with the request.</param> |
| | 76 | | /// |
| | 77 | | /// <returns>The exception that most accurately represents the response failure.</returns> |
| | 78 | | /// |
| | 79 | | public static Exception CreateExceptionForResponse(AmqpMessage response, |
| | 80 | | string eventHubsResource) |
| | 81 | | { |
| 70 | 82 | | if (response == null) |
| | 83 | | { |
| 2 | 84 | | return new EventHubsException(eventHubsResource, Resources.UnknownCommunicationException, EventHubsExcep |
| | 85 | | } |
| | 86 | |
|
| 68 | 87 | | if (!response.ApplicationProperties.Map.TryGetValue<string>(AmqpResponse.StatusDescription, out var descript |
| | 88 | | { |
| 0 | 89 | | description = Resources.UnknownCommunicationException; |
| | 90 | | } |
| | 91 | |
|
| 68 | 92 | | return CreateException(DetermineErrorCondition(response).Value, description, eventHubsResource); |
| | 93 | | } |
| | 94 | |
|
| | 95 | | /// <summary> |
| | 96 | | /// Creates the exception that corresponds to a given AMQP error. |
| | 97 | | /// </summary> |
| | 98 | | /// |
| | 99 | | /// <param name="error">The AMQP error to consider.</param> |
| | 100 | | /// <param name="eventHubsResource">The Event Hubs resource associated with the operation.</param> |
| | 101 | | /// |
| | 102 | | /// <returns>The exception that most accurately represents the error that was encountered.</returns> |
| | 103 | | /// |
| | 104 | | public static Exception CreateExceptionForError(Error error, |
| | 105 | | string eventHubsResource) |
| | 106 | | { |
| 70 | 107 | | if (error == null) |
| | 108 | | { |
| 2 | 109 | | return new EventHubsException(true, eventHubsResource, Resources.UnknownCommunicationException); |
| | 110 | | } |
| | 111 | |
|
| 68 | 112 | | return CreateException(error.Condition.Value, error.Description, eventHubsResource); |
| | 113 | | } |
| | 114 | |
|
| | 115 | | /// <summary> |
| | 116 | | /// Determines if a given AMQP message response is an error and, if so, throws the |
| | 117 | | /// appropriate corresponding exception type. |
| | 118 | | /// </summary> |
| | 119 | | /// |
| | 120 | | /// <param name="response">The AMQP response message to consider.</param> |
| | 121 | | /// <param name="eventHubName">The name of the Event Hub associated with the request.</param> |
| | 122 | | /// |
| | 123 | | public static void ThrowIfErrorResponse(AmqpMessage response, |
| | 124 | | string eventHubName) |
| | 125 | | { |
| 22 | 126 | | var statusCode = default(int); |
| | 127 | |
|
| 22 | 128 | | if ((response?.ApplicationProperties?.Map.TryGetValue(AmqpResponse.StatusCode, out statusCode) == false) |
| 22 | 129 | | || (!AmqpResponse.IsSuccessStatus((AmqpResponseStatusCode)statusCode))) |
| | 130 | | { |
| 18 | 131 | | throw CreateExceptionForResponse(response, eventHubName); |
| | 132 | | } |
| 4 | 133 | | } |
| | 134 | |
|
| | 135 | | /// <summary> |
| | 136 | | /// Creates the exception that corresponds to a given AMQP failure scenario. |
| | 137 | | /// </summary> |
| | 138 | | /// |
| | 139 | | /// <param name="condition">The error condition that represents the failure scenario.</param> |
| | 140 | | /// <param name="description">The descriptive text to use for messaging the scenario.</param> |
| | 141 | | /// <param name="eventHubsResource">The Event Hubs resource associated with the failure.</param> |
| | 142 | | /// |
| | 143 | | /// <returns>The exception that most accurately represents the failure scenario.</returns> |
| | 144 | | /// |
| | 145 | | private static Exception CreateException(string condition, |
| | 146 | | string description, |
| | 147 | | string eventHubsResource) |
| | 148 | | { |
| | 149 | | // The request timed out. |
| | 150 | |
|
| 136 | 151 | | if (string.Equals(condition, TimeoutError.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 152 | | { |
| 8 | 153 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ServiceTi |
| | 154 | | } |
| | 155 | |
|
| | 156 | | // The Event Hubs service was busy. |
| | 157 | |
|
| 128 | 158 | | if (string.Equals(condition, ServerBusyError.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 159 | | { |
| 36 | 160 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ServiceBu |
| | 161 | | } |
| | 162 | |
|
| | 163 | | // An argument was rejected by the Event Hubs service. |
| | 164 | |
|
| 92 | 165 | | if (string.Equals(condition, ArgumentError.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 166 | | { |
| 20 | 167 | | return new ArgumentException(description); |
| | 168 | | } |
| | 169 | |
|
| 72 | 170 | | if (string.Equals(condition, ArgumentOutOfRangeError.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 171 | | { |
| 4 | 172 | | return new ArgumentOutOfRangeException(description); |
| | 173 | | } |
| | 174 | |
|
| | 175 | | // The consumer was superseded by one with a higher owner level. |
| | 176 | |
|
| 68 | 177 | | if (string.Equals(condition, AmqpErrorCode.Stolen.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 178 | | { |
| 6 | 179 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ConsumerD |
| | 180 | | } |
| | 181 | |
|
| | 182 | | // Authorization was denied. |
| | 183 | |
|
| 62 | 184 | | if (string.Equals(condition, AmqpErrorCode.UnauthorizedAccess.Value, StringComparison.InvariantCultureIgnore |
| | 185 | | { |
| 8 | 186 | | return new UnauthorizedAccessException(description); |
| | 187 | | } |
| | 188 | |
|
| | 189 | | // Requests are being throttled due to exceeding the service quota. |
| | 190 | |
|
| 54 | 191 | | if (string.Equals(condition, AmqpErrorCode.ResourceLimitExceeded.Value, StringComparison.InvariantCultureIgn |
| | 192 | | { |
| 8 | 193 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.QuotaExce |
| | 194 | | } |
| | 195 | |
|
| | 196 | | // The link was closed, generally this exception would be thrown for partition specific producers and would |
| | 197 | | // between an operation and a request to close a client. |
| | 198 | |
|
| 46 | 199 | | if (string.Equals(condition, AmqpErrorCode.IllegalState.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 200 | | { |
| 4 | 201 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ClientClo |
| | 202 | | } |
| | 203 | |
|
| | 204 | | // The service does not understand how to process the request. |
| | 205 | |
|
| 42 | 206 | | if (string.Equals(condition, AmqpErrorCode.NotAllowed.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 207 | | { |
| 4 | 208 | | return new InvalidOperationException(description); |
| | 209 | | } |
| | 210 | |
|
| 38 | 211 | | if (string.Equals(condition, AmqpErrorCode.NotImplemented.Value, StringComparison.InvariantCultureIgnoreCase |
| | 212 | | { |
| 6 | 213 | | return new NotSupportedException(description); |
| | 214 | | } |
| | 215 | |
|
| | 216 | | // The Event Hubs resource was not valid or communication with the service was interrupted. |
| | 217 | |
|
| 32 | 218 | | if (string.Equals(condition, AmqpErrorCode.NotFound.Value, StringComparison.InvariantCultureIgnoreCase)) |
| | 219 | | { |
| 18 | 220 | | if (NotFoundExpression.IsMatch(description) |
| 18 | 221 | | || (description.IndexOf(NotFoundStatusText, StringComparison.InvariantCultureIgnoreCase) >= 0)) |
| | 222 | | { |
| 12 | 223 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.Resou |
| | 224 | | } |
| | 225 | |
|
| 6 | 226 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ServiceCo |
| | 227 | | } |
| | 228 | |
|
| | 229 | | // There was no specific exception that could be determined; fall back to a generic one. |
| | 230 | |
|
| 14 | 231 | | return new EventHubsException(eventHubsResource, description, EventHubsException.FailureReason.ServiceCommun |
| | 232 | | } |
| | 233 | |
|
| | 234 | | /// <summary> |
| | 235 | | /// Determines the applicable error condition for a given response message. |
| | 236 | | /// </summary> |
| | 237 | | /// |
| | 238 | | /// <param name="response">The AMQP response message to consider.</param> |
| | 239 | | /// |
| | 240 | | /// <returns>The AMQP error condition that best represents the response.</returns> |
| | 241 | | /// |
| | 242 | | private static AmqpSymbol DetermineErrorCondition(AmqpMessage response) |
| | 243 | | { |
| | 244 | |
|
| | 245 | | // If there was an error condition present, use that. |
| | 246 | |
|
| 68 | 247 | | if (response.ApplicationProperties.Map.TryGetValue(AmqpResponse.ErrorCondition, out AmqpSymbol condition)) |
| | 248 | | { |
| 28 | 249 | | return condition; |
| | 250 | | } |
| | 251 | |
|
| | 252 | | // If no error condition was present, perform a reverse lookup in the mappings to determine the |
| | 253 | | // condition from the response status code. |
| | 254 | |
|
| 40 | 255 | | if ((response.ApplicationProperties.Map.TryGetValue<int>(AmqpResponse.StatusCode, out var statusCode)) |
| 40 | 256 | | && (StatusCodeMap.TryGetValue((AmqpResponseStatusCode)statusCode, out condition))) |
| | 257 | | { |
| 34 | 258 | | return condition; |
| | 259 | | } |
| | 260 | |
|
| | 261 | | // If no specific value could be determined, fall back to a generic condition. |
| | 262 | |
|
| 6 | 263 | | return AmqpErrorCode.InternalError; |
| | 264 | | } |
| | 265 | | } |
| | 266 | | } |