Skip to content

Commit 1cd6e4a

Browse files
committed
Updated documentation; Throwing only FirebaseMessagingException from FCM APIs
1 parent 4a10389 commit 1cd6e4a

File tree

6 files changed

+111
-29
lines changed

6 files changed

+111
-29
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/Messaging/FirebaseMessagingClientTest.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
using System;
1616
using System.Linq;
1717
using System.Net;
18+
using System.Net.Http;
1819
using System.Threading.Tasks;
1920
using FirebaseAdmin.Tests;
2021
using Google.Apis.Auth.OAuth2;
@@ -464,6 +465,38 @@ public async Task HttpErrorNonJsonAsync()
464465
Assert.Equal(1, handler.Calls);
465466
}
466467

468+
[Fact]
469+
public async Task TransportError()
470+
{
471+
var handler = new MockMessageHandler()
472+
{
473+
Exception = new HttpRequestException("Transport error"),
474+
};
475+
var factory = new MockHttpClientFactory(handler);
476+
var client = new FirebaseMessagingClient(factory, MockCredential, "test-project");
477+
var message = new Message()
478+
{
479+
Topic = "test-topic",
480+
};
481+
482+
var ex = await Assert.ThrowsAsync<FirebaseMessagingException>(
483+
async () => await client.SendAsync(message));
484+
485+
Assert.Equal(ErrorCode.Unknown, ex.ErrorCode);
486+
Assert.Equal(
487+
"Unknown error while making a remote service call: Transport error",
488+
ex.Message);
489+
Assert.Null(ex.MessagingErrorCode);
490+
Assert.Null(ex.HttpResponse);
491+
Assert.Same(handler.Exception, ex.InnerException);
492+
493+
var req = JsonConvert.DeserializeObject<FirebaseMessagingClient.SendRequest>(
494+
handler.LastRequestBody);
495+
Assert.Equal("test-topic", req.Message.Topic);
496+
Assert.False(req.ValidateOnly);
497+
Assert.Equal(1, handler.Calls);
498+
}
499+
467500
private int CountLinesWithPrefix(string body, string linePrefix)
468501
{
469502
return body.Split('\n').Count((line) => line.StartsWith(linePrefix));

FirebaseAdmin/FirebaseAdmin.Tests/MockMessageHandler.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,12 @@ public HttpRequestHeaders LastRequestHeaders
7878
/// </summary>
7979
public object Response { get; set; }
8080

81+
/// <summary>
82+
/// Gets or sets an exception to simulate low-level network errors. When specified, all invocations
83+
/// of this message handler will fail by throwing this exception.
84+
/// </summary>
85+
public Exception Exception { get; set; }
86+
8187
/// <summary>
8288
/// Gets or sets the function for modifying the response headers.
8389
/// </summary>
@@ -89,6 +95,13 @@ protected override async Task<HttpResponseMessage> DoSendAsync(
8995
var incomingRequest = await IncomingRequest.CreateAsync(request);
9096
this.requests.Add(incomingRequest);
9197

98+
var tcs = new TaskCompletionSource<HttpResponseMessage>();
99+
if (this.Exception != null)
100+
{
101+
tcs.SetException(this.Exception);
102+
return await tcs.Task;
103+
}
104+
92105
string json;
93106
if (this.Response is byte[])
94107
{
@@ -116,7 +129,6 @@ protected override async Task<HttpResponseMessage> DoSendAsync(
116129
this.ApplyHeaders(resp.Headers, content.Headers);
117130
}
118131

119-
var tcs = new TaskCompletionSource<HttpResponseMessage>();
120132
tcs.SetResult(resp);
121133
return await tcs.Task;
122134
}

FirebaseAdmin/FirebaseAdmin/HttpErrorHandler.cs

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,18 +52,18 @@ internal void ThrowIfError(HttpResponseMessage response, string body)
5252
return;
5353
}
5454

55-
var info = this.ExtractErrorInfo(response, body);
56-
var args = new FirebaseExceptionArgs()
57-
{
58-
Code = info.Code,
59-
Message = info.Message,
60-
HttpResponse = response,
61-
ResponseBody = body,
62-
};
55+
var args = this.CreateExceptionArgs(response, body);
6356
throw this.CreateException(args);
6457
}
6558

66-
protected virtual ErrorInfo ExtractErrorInfo(HttpResponseMessage response, string body)
59+
/// <summary>
60+
/// Parses the given HTTP response to extract an error code and message from it.
61+
/// </summary>
62+
/// <param name="response">HTTP response message to process.</param>
63+
/// <param name="body">The response body read from the message.</param>
64+
/// <returns>A <see cref="FirebaseExceptionArgs"/> object containing error code and message.
65+
/// Both the code and the message should not be null or empty.</returns>
66+
protected virtual FirebaseExceptionArgs CreateExceptionArgs(HttpResponseMessage response, string body)
6767
{
6868
ErrorCode code;
6969
if (!HttpErrorCodes.TryGetValue(response.StatusCode, out code))
@@ -74,25 +74,26 @@ protected virtual ErrorInfo ExtractErrorInfo(HttpResponseMessage response, strin
7474
var message = "Unexpected HTTP response with status: "
7575
+ $"{(int)response.StatusCode} ({response.StatusCode})"
7676
+ $"{Environment.NewLine}{body}";
77-
return new ErrorInfo()
77+
return new FirebaseExceptionArgs()
7878
{
7979
Code = code,
8080
Message = message,
81+
HttpResponse = response,
82+
ResponseBody = body,
8183
};
8284
}
8385

86+
/// <summary>
87+
/// Creates a new <see cref="FirebaseException"/> from the specified arguments.
88+
/// </summary>
89+
/// <param name="args">A <see cref="FirebaseExceptionArgs"/> instance containing error
90+
/// code, message and other details.</param>
91+
/// <returns>A <see cref="FirebaseException"/> object.</returns>
8492
protected virtual FirebaseException CreateException(FirebaseExceptionArgs args)
8593
{
8694
return new FirebaseException(args.Code, args.Message, response: args.HttpResponse);
8795
}
8896

89-
internal sealed class ErrorInfo
90-
{
91-
internal ErrorCode Code { get; set; }
92-
93-
internal string Message { get; set; }
94-
}
95-
9697
internal sealed class FirebaseExceptionArgs
9798
{
9899
internal ErrorCode Code { get; set; }

FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessaging.cs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public static FirebaseMessaging GetMessaging(FirebaseApp app)
8383
/// <exception cref="ArgumentNullException">If the message argument is null.</exception>
8484
/// <exception cref="ArgumentException">If the message contains any invalid
8585
/// fields.</exception>
86-
/// <exception cref="FirebaseException">If an error occurs while sending the
86+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
8787
/// message.</exception>
8888
/// <param name="message">The message to be sent. Must not be null.</param>
8989
public async Task<string> SendAsync(Message message)
@@ -102,7 +102,7 @@ public async Task<string> SendAsync(Message message)
102102
/// <exception cref="ArgumentNullException">If the message argument is null.</exception>
103103
/// <exception cref="ArgumentException">If the message contains any invalid
104104
/// fields.</exception>
105-
/// <exception cref="FirebaseException">If an error occurs while sending the
105+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
106106
/// message.</exception>
107107
/// <param name="message">The message to be sent. Must not be null.</param>
108108
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
@@ -127,7 +127,7 @@ public async Task<string> SendAsync(Message message, CancellationToken cancellat
127127
/// <exception cref="ArgumentNullException">If the message argument is null.</exception>
128128
/// <exception cref="ArgumentException">If the message contains any invalid
129129
/// fields.</exception>
130-
/// <exception cref="FirebaseException">If an error occurs while sending the
130+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
131131
/// message.</exception>
132132
/// <param name="message">The message to be sent. Must not be null.</param>
133133
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
@@ -153,7 +153,7 @@ public async Task<string> SendAsync(Message message, bool dryRun)
153153
/// <exception cref="ArgumentNullException">If the message argument is null.</exception>
154154
/// <exception cref="ArgumentException">If the message contains any invalid
155155
/// fields.</exception>
156-
/// <exception cref="FirebaseException">If an error occurs while sending the
156+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
157157
/// message.</exception>
158158
/// <param name="message">The message to be sent. Must not be null.</param>
159159
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
@@ -173,6 +173,8 @@ public async Task<string> SendAsync(
173173
/// send the entire list as a single RPC call. Compared to the <see cref="SendAsync(Message)"/>
174174
/// method, this is a significantly more efficient way to send multiple messages.
175175
/// </summary>
176+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
177+
/// messages.</exception>
176178
/// <param name="messages">Up to 100 messages to send in the batch. Cannot be null.</param>
177179
/// <returns>A <see cref="BatchResponse"/> containing details of the batch operation's
178180
/// outcome.</returns>
@@ -186,6 +188,8 @@ public async Task<BatchResponse> SendAllAsync(IEnumerable<Message> messages)
186188
/// send the entire list as a single RPC call. Compared to the <see cref="SendAsync(Message)"/>
187189
/// method, this is a significantly more efficient way to send multiple messages.
188190
/// </summary>
191+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
192+
/// messages.</exception>
189193
/// <param name="messages">Up to 100 messages to send in the batch. Cannot be null.</param>
190194
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
191195
/// operation.</param>
@@ -201,6 +205,8 @@ public async Task<BatchResponse> SendAllAsync(IEnumerable<Message> messages, Can
201205
/// send the entire list as a single RPC call. Compared to the <see cref="SendAsync(Message)"/>
202206
/// method, this is a significantly more efficient way to send multiple messages.
203207
/// </summary>
208+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
209+
/// messages.</exception>
204210
/// <param name="messages">Up to 100 messages to send in the batch. Cannot be null.</param>
205211
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
206212
/// only) of the send. If set to true, the message will be sent to the FCM backend service,
@@ -217,6 +223,8 @@ public async Task<BatchResponse> SendAllAsync(IEnumerable<Message> messages, boo
217223
/// send the entire list as a single RPC call. Compared to the <see cref="SendAsync(Message)"/>
218224
/// method, this is a significantly more efficient way to send multiple messages.
219225
/// </summary>
226+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
227+
/// messages.</exception>
220228
/// <param name="messages">Up to 100 messages to send in the batch. Cannot be null.</param>
221229
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
222230
/// only) of the send. If set to true, the message will be sent to the FCM backend service,
@@ -233,6 +241,8 @@ public async Task<BatchResponse> SendAllAsync(IEnumerable<Message> messages, boo
233241
/// <summary>
234242
/// Sends the given multicast message to all the FCM registration tokens specified in it.
235243
/// </summary>
244+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
245+
/// messages.</exception>
236246
/// <param name="message">The message to be sent. Must not be null.</param>
237247
/// <returns>A <see cref="BatchResponse"/> containing details of the batch operation's
238248
/// outcome.</returns>
@@ -244,6 +254,8 @@ public async Task<BatchResponse> SendMulticastAsync(MulticastMessage message)
244254
/// <summary>
245255
/// Sends the given multicast message to all the FCM registration tokens specified in it.
246256
/// </summary>
257+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
258+
/// messages.</exception>
247259
/// <param name="message">The message to be sent. Must not be null.</param>
248260
/// <param name="cancellationToken">A cancellation token to monitor the asynchronous
249261
/// operation.</param>
@@ -261,6 +273,8 @@ public async Task<BatchResponse> SendMulticastAsync(MulticastMessage message, Ca
261273
/// validations, and emulates the send operation. This is a good way to check if a
262274
/// certain message will be accepted by FCM for delivery.</para>
263275
/// </summary>
276+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
277+
/// messages.</exception>
264278
/// <param name="message">The message to be sent. Must not be null.</param>
265279
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
266280
/// only) of the send. If set to true, the message will be sent to the FCM backend service,
@@ -279,6 +293,8 @@ public async Task<BatchResponse> SendMulticastAsync(MulticastMessage message, bo
279293
/// validations, and emulates the send operation. This is a good way to check if a
280294
/// certain message will be accepted by FCM for delivery.</para>
281295
/// </summary>
296+
/// <exception cref="FirebaseMessagingException">If an error occurs while sending the
297+
/// messages.</exception>
282298
/// <param name="message">The message to be sent. Must not be null.</param>
283299
/// <param name="dryRun">A boolean indicating whether to perform a dry run (validation
284300
/// only) of the send. If set to true, the message will be sent to the FCM backend service,

FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public async Task<string> SendAsync(
112112
}
113113
catch (HttpRequestException e)
114114
{
115-
throw e.ToFirebaseException();
115+
throw this.ToFirebaseMessagingException(e);
116116
}
117117
}
118118

@@ -152,7 +152,7 @@ public async Task<BatchResponse> SendAllAsync(
152152
}
153153
catch (HttpRequestException e)
154154
{
155-
throw e.ToFirebaseException();
155+
throw this.ToFirebaseMessagingException(e);
156156
}
157157
}
158158

@@ -193,7 +193,7 @@ private async Task<BatchResponse> SendBatchRequestAsync(
193193
SendResponse sendResponse;
194194
if (error != null)
195195
{
196-
sendResponse = SendResponse.FromException(await this.CreateExceptionFor(message));
196+
sendResponse = SendResponse.FromException(await this.CreateException(message));
197197
}
198198
else if (content != null)
199199
{
@@ -234,7 +234,7 @@ private BatchRequest CreateBatchRequest(
234234
return batch;
235235
}
236236

237-
private async Task<FirebaseMessagingException> CreateExceptionFor(HttpResponseMessage response)
237+
private async Task<FirebaseMessagingException> CreateException(HttpResponseMessage response)
238238
{
239239
var json = await response.Content.ReadAsStringAsync();
240240
try
@@ -248,6 +248,17 @@ private async Task<FirebaseMessagingException> CreateExceptionFor(HttpResponseMe
248248
}
249249
}
250250

251+
private FirebaseMessagingException ToFirebaseMessagingException(
252+
HttpRequestException exception)
253+
{
254+
var temp = exception.ToFirebaseException();
255+
return new FirebaseMessagingException(
256+
temp.ErrorCode,
257+
temp.Message,
258+
inner: temp.InnerException,
259+
response: temp.HttpResponse);
260+
}
261+
251262
/// <summary>
252263
/// Represents the envelope message accepted by the FCM backend service, including the message
253264
/// payload and other options like <c>validate_only</c>.

FirebaseAdmin/FirebaseAdmin/PlatformErrorHandler.cs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919

2020
namespace FirebaseAdmin
2121
{
22+
/// <summary>
23+
/// Base class for handling HTTP error responses returned by Google Cloud Platform APIs.
24+
/// See <a href="https://cloud.google.com/apis/design/errors">Errors</a> for more details on
25+
/// how Google Cloud Platform APIs report back error codes and details. If this class fails
26+
/// to determine an error code or message from a given API response, it falls back to the
27+
/// error handling logic defined in the parent <see cref="HttpErrorHandler"/> class.
28+
/// </summary>
2229
internal class PlatformErrorHandler : HttpErrorHandler
2330
{
2431
private static readonly IReadOnlyDictionary<string, ErrorCode> PlatformErrorCodes =
@@ -31,11 +38,11 @@ internal class PlatformErrorHandler : HttpErrorHandler
3138
{ "UNAVAILABLE", ErrorCode.Unavailable },
3239
};
3340

34-
protected sealed override ErrorInfo ExtractErrorInfo(HttpResponseMessage response, string body)
41+
protected sealed override FirebaseExceptionArgs CreateExceptionArgs(HttpResponseMessage response, string body)
3542
{
3643
var parsedResponse = this.ParseResponseBody(body);
3744
var status = parsedResponse.Error?.Status ?? string.Empty;
38-
var defaults = base.ExtractErrorInfo(response, body);
45+
var defaults = base.CreateExceptionArgs(response, body);
3946

4047
ErrorCode code;
4148
if (!PlatformErrorCodes.TryGetValue(status, out code))
@@ -49,10 +56,12 @@ protected sealed override ErrorInfo ExtractErrorInfo(HttpResponseMessage respons
4956
message = defaults.Message;
5057
}
5158

52-
return new ErrorInfo()
59+
return new FirebaseExceptionArgs()
5360
{
5461
Code = code,
5562
Message = message,
63+
HttpResponse = response,
64+
ResponseBody = body,
5665
};
5766
}
5867

0 commit comments

Comments
 (0)