Skip to content

Commit 3542159

Browse files
author
childish-sambino
authored
feat: add option for throwing exception on non-successful API request (#1019)
1 parent e96cf82 commit 3542159

20 files changed

+766
-16
lines changed

ExampleNet45Project/Program.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ private static async Task Main()
2626
// Retrieve the API key.
2727
var apiKey = Environment.GetEnvironmentVariable("SENDGRID_API_KEY") ?? configuration["SendGrid:ApiKey"];
2828

29-
var client = new SendGridClient(HttpClient, apiKey);
29+
var client = new SendGridClient(HttpClient, apiKey, httpErrorAsException: true);
3030

3131
// Send a Single Email using the Mail Helper
3232
var from = new EmailAddress(configuration.GetValue("SendGrid:From", "[email protected]"), "Example User");
@@ -118,8 +118,8 @@ private static async Task Main()
118118

119119
// POST
120120
var requestBody = @"{
121-
'description': 'Suggestions for products our users might like.',
122-
'is_default': false,
121+
'description': 'Suggestions for products our users might like.',
122+
'is_default': false,
123123
'name': 'Magic Products'
124124
}";
125125
json = JsonConvert.DeserializeObject<object>(requestBody);
@@ -173,4 +173,4 @@ private static async Task Main()
173173
}
174174
}
175175
}
176-
}
176+
}

TROUBLESHOOTING.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,78 @@ In the first case SENDGRID_API_KEY is in reference to the name of the environmen
5353
<a name="error"></a>
5454
## Error Messages
5555

56-
To read the error message returned by Twilio SendGrid's API:
56+
By default if the API returns an error, it doesn't throw an exception, but you can read the error message returned by Twilio SendGrid's API:
5757

5858
```csharp
5959
var response = await client.RequestAsync(method: SendGridClient.Method.POST,
60-
requestBody: msg.Serialize(),
61-
urlPath: "mail/send");
60+
requestBody: msg.Serialize(),
61+
urlPath: "mail/send");
6262
Console.WriteLine(response.StatusCode);
6363
Console.WriteLine(response.Body.ReadAsStringAsync().Result); // The message will be here
6464
Console.WriteLine(response.Headers.ToString());
6565
```
6666

67+
If you want to throw the exception when the API returns an error, init the SendGridClient with the parameter 'httpErrorAsException' as true:
68+
69+
```csharp
70+
SendGridClient client = new SendGridClient(apiKey, httpErrorAsException: true);
71+
```
72+
73+
Then if an error is thrown due to a failed request, a summary of that error along with a link to the appropriate place in the documentation will be sent back as a JSON object in the Exception Message. You can also deserialize the JSON object as a SendGridErrorResponse. Every error status code returned by the API has its own type of exception (BadRequestException, UnauthorizedException, PayloadTooLargeException, SendGridInternalException, etc.). All the possibles status codes and errors are documented [here](https://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html).
74+
75+
### EXAMPLES
76+
77+
#### 401 - Unauthorized
78+
79+
```csharp
80+
SendGridClient client = new SendGridClient("", httpErrorAsException: true);
81+
82+
try
83+
{
84+
var response = await client.RequestAsync(method: SendGridClient.Method.POST,
85+
requestBody: msg.Serialize(),
86+
urlPath: "mail/send");
87+
}
88+
catch(Exception ex)
89+
{
90+
SendGridErrorResponse errorResponse = JsonConvert.DeserializeObject<SendGridErrorResponse>(ex.Message);
91+
Console.WriteLine(ex.Message);
92+
//{
93+
// "ErrorHttpStatusCode":401,
94+
// "ErrorReasonPhrase":"Unauthorized",
95+
// "SendGridErrorMessage":"Permission denied, wrong credentials",
96+
// "FieldWithError":null,
97+
// "HelpLink":null
98+
//}
99+
}
100+
```
101+
102+
#### 400 - Bad Request - From Email Null
103+
104+
```csharp
105+
var client = new SendGridClient(apiKey, httpErrorAsException: true);
106+
107+
try
108+
{
109+
var from = new EmailAddress("", "Example User"); // From email null
110+
var msg = MailHelper.CreateSingleEmail(from, to, subject, plainTextContent, htmlContent);
111+
112+
var response = await client.SendEmailAsync(msg).ConfigureAwait(false);
113+
}
114+
catch (Exception ex)
115+
{
116+
SendGridErrorResponse errorResponse = JsonConvert.DeserializeObject<SendGridErrorResponse>(ex.Message);
117+
Console.WriteLine(ex.Message);
118+
//{
119+
// "ErrorHttpStatusCode":400,
120+
// "ErrorReasonPhrase":"Bad Request",
121+
// "SendGridErrorMessage":"The from email does not contain a valid address.",
122+
// "FieldWithError":"from.email",
123+
// "HelpLink":"http://sendgrid.com/docs/API_Reference/Web_API_v3/Mail/errors.html#message.from"
124+
//}
125+
}
126+
```
127+
67128
<a name="migrating"></a>
68129
## Migrating from the v2 API to v3
69130

@@ -134,7 +195,7 @@ In our example code, you would change:
134195
var response = await client.SendEmailAsync(msg);
135196
```
136197

137-
to
198+
to
138199

139200
```csharp
140201
var response = await client.SendEmailAsync(msg).ConfigureAwait(false);

src/SendGrid/BaseClient.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using Newtonsoft.Json;
2+
using SendGrid.Helpers.Errors;
23
using SendGrid.Helpers.Mail;
34
using SendGrid.Helpers.Reliability;
45
using System;
@@ -153,6 +154,12 @@ public virtual AuthenticationHeaderValue AddAuthorization(KeyValuePair<string, s
153154
public virtual async Task<Response> MakeRequest(HttpRequestMessage request, CancellationToken cancellationToken = default(CancellationToken))
154155
{
155156
HttpResponseMessage response = await this.client.SendAsync(request, cancellationToken).ConfigureAwait(false);
157+
158+
if (!response.IsSuccessStatusCode && this.options.HttpErrorAsException)
159+
{
160+
await ErrorHandler.ThrowException(response).ConfigureAwait(false);
161+
}
162+
156163
return new Response(response.StatusCode, response.Content, response.Headers);
157164
}
158165

src/SendGrid/BaseClientOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,10 @@ public ReliabilitySettings ReliabilitySettings
4545
/// The Auth header value.
4646
/// </summary>
4747
public AuthenticationHeaderValue Auth { get; set; }
48+
49+
/// <summary>
50+
/// Gets or sets a value indicating whether HTTP error responses should be raised as exceptions. Default is false.
51+
/// </summary>
52+
public bool HttpErrorAsException { get; set; } = false;
4853
}
4954
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
using Newtonsoft.Json.Linq;
2+
using SendGrid.Helpers.Errors.Model;
3+
using System.Net.Http;
4+
using System.Threading.Tasks;
5+
6+
namespace SendGrid.Helpers.Errors
7+
{
8+
/// <summary>
9+
/// Error handler for requests.
10+
/// </summary>
11+
public class ErrorHandler
12+
{
13+
/// <summary>
14+
/// Throw the exception based on HttpResponseMessage
15+
/// </summary>
16+
/// <param name="message">Response Message from API</param>
17+
/// <returns>Return the exception. Obs: Exceptions from an Async Void Method Can’t Be Caught with Catch </returns>
18+
public static async Task ThrowException(HttpResponseMessage message)
19+
{
20+
var errorMessage = await ErrorHandler.GetErrorMessage(message).ConfigureAwait(false);
21+
22+
var errorStatusCode = (int)message.StatusCode;
23+
24+
switch (errorStatusCode)
25+
{
26+
// 400 - BAD REQUEST
27+
case 400:
28+
throw new BadRequestException(errorMessage);
29+
30+
// 401 - UNAUTHORIZED
31+
case 401:
32+
throw new UnauthorizedException(errorMessage);
33+
34+
// 403 - FORBIDDEN
35+
case 403:
36+
throw new ForbiddenException(errorMessage);
37+
38+
// 404 - NOT FOUND
39+
case 404:
40+
throw new NotFoundException(errorMessage);
41+
42+
// 405 - METHOD NOT ALLOWED
43+
case 405:
44+
throw new MethodNotAllowedException(errorMessage);
45+
46+
// 413 - PAYLOAD TOO LARGE
47+
case 413:
48+
throw new PayloadTooLargeException(errorMessage);
49+
50+
// 415 - UNSUPPORTED MEDIA TYPE
51+
case 415:
52+
throw new UnsupportedMediaTypeException(errorMessage);
53+
54+
// 429 - TOO MANY REQUESTS
55+
case 429:
56+
throw new TooManyRequestsException(errorMessage);
57+
58+
// 500 - SERVER UNAVAILABLE
59+
case 500:
60+
throw new ServerUnavailableException(errorMessage);
61+
62+
// 503 - SERVICE NOT AVAILABLE
63+
case 503:
64+
throw new ServiceNotAvailableException(errorMessage);
65+
}
66+
67+
// 4xx - Error with the request
68+
if (errorStatusCode >= 400 && errorStatusCode < 500)
69+
{
70+
throw new RequestErrorException(errorMessage);
71+
}
72+
73+
// 5xx - Error made by SendGrid
74+
if (errorStatusCode >= 500)
75+
{
76+
throw new SendGridInternalException(errorMessage);
77+
}
78+
79+
throw new BadRequestException(errorMessage);
80+
}
81+
82+
/// <summary>
83+
/// Get error based on Response from SendGrid API
84+
/// Method taken from the StrongGrid project (https://github.com/Jericho/StrongGrid) with some minor changes. Thanks Jericho (https://github.com/Jericho)
85+
/// </summary>
86+
/// <param name="message">Response Message from API</param>
87+
/// <returns>Return string with the error Status Code and the Message</returns>
88+
private static async Task<string> GetErrorMessage(HttpResponseMessage message)
89+
{
90+
var errorStatusCode = (int)message.StatusCode;
91+
var errorReasonPhrase = message.ReasonPhrase;
92+
93+
string errorValue = null;
94+
string fieldValue = null;
95+
string helpValue = null;
96+
97+
if (message.Content != null)
98+
{
99+
var responseContent = await message.Content.ReadAsStringAsync().ConfigureAwait(false);
100+
101+
if (!string.IsNullOrEmpty(responseContent))
102+
{
103+
try
104+
{
105+
// Check for the presence of property called 'errors'
106+
var jObject = JObject.Parse(responseContent);
107+
var errorsArray = (JArray)jObject["errors"];
108+
if (errorsArray != null && errorsArray.Count > 0)
109+
{
110+
// Get the first error message
111+
errorValue = errorsArray[0]["message"].Value<string>();
112+
113+
// Check for the presence of property called 'field'
114+
if (errorsArray[0]["field"] != null)
115+
{
116+
fieldValue = errorsArray[0]["field"].Value<string>();
117+
}
118+
119+
// Check for the presence of property called 'help'
120+
if (errorsArray[0]["help"] != null)
121+
{
122+
helpValue = errorsArray[0]["help"].Value<string>();
123+
}
124+
}
125+
else
126+
{
127+
// Check for the presence of property called 'error'
128+
var errorProperty = jObject["error"];
129+
if (errorProperty != null)
130+
{
131+
errorValue = errorProperty.Value<string>();
132+
}
133+
}
134+
}
135+
catch
136+
{
137+
// Intentionally ignore parsing errors to return default error message
138+
}
139+
}
140+
}
141+
142+
SendGridErrorResponse errorResponse = new SendGridErrorResponse
143+
{
144+
ErrorHttpStatusCode = errorStatusCode,
145+
ErrorReasonPhrase = errorReasonPhrase,
146+
SendGridErrorMessage = errorValue,
147+
FieldWithError = fieldValue,
148+
HelpLink = helpValue
149+
};
150+
151+
return Newtonsoft.Json.JsonConvert.SerializeObject(errorResponse);
152+
}
153+
}
154+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
3+
namespace SendGrid.Helpers.Errors.Model
4+
{
5+
/// <summary>
6+
/// Represents errors with status code 400
7+
/// </summary>
8+
public class BadRequestException : RequestErrorException
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="BadRequestException"/> class.
12+
/// </summary>
13+
public BadRequestException()
14+
: base()
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error.
20+
/// </summary>
21+
/// <param name="message"> The error message that explains the reason for the exception.</param>
22+
public BadRequestException(string message)
23+
: base(message)
24+
{
25+
}
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error and a reference to the inner exception that is the cause of this exception.
29+
/// </summary>
30+
/// <param name="message">The error message that explains the reason for the exception.</param>
31+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
32+
public BadRequestException(string message, Exception innerException)
33+
: base(message, innerException)
34+
{
35+
}
36+
}
37+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System;
2+
3+
namespace SendGrid.Helpers.Errors.Model
4+
{
5+
/// <summary>
6+
/// Represents errors with status code 403
7+
/// </summary>
8+
public class ForbiddenException : RequestErrorException
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="ForbiddenException"/> class.
12+
/// </summary>
13+
public ForbiddenException()
14+
: base()
15+
{
16+
}
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="ForbiddenException"/> class with a specified error.
20+
/// </summary>
21+
/// <param name="message"> The error message that explains the reason for the exception.</param>
22+
public ForbiddenException(string message)
23+
: base(message)
24+
{
25+
}
26+
27+
/// <summary>
28+
/// Initializes a new instance of the <see cref="ForbiddenException"/> class with a specified error and a reference to the inner exception that is the cause of this exception.
29+
/// </summary>
30+
/// <param name="message">The error message that explains the reason for the exception.</param>
31+
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
32+
public ForbiddenException(string message, Exception innerException)
33+
: base(message, innerException)
34+
{
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)