Skip to content

Commit 869478c

Browse files
authored
Merge pull request #87 from firebase/error-handling-revamp
Introducing the platform-wide error codes
2 parents f640512 + d6a8553 commit 869478c

File tree

7 files changed

+254
-8
lines changed

7 files changed

+254
-8
lines changed

FirebaseAdmin/FirebaseAdmin.Tests/FirebaseAppTest.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@
1313
// limitations under the License.
1414

1515
using System;
16-
using System.Threading.Tasks;
17-
using FirebaseAdmin;
1816
using Google.Apis.Auth.OAuth2;
19-
using Google.Apis.Http;
2017
using Xunit;
2118

2219
namespace FirebaseAdmin.Tests
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2019, Google Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System;
16+
using System.Collections.Generic;
17+
using System.Net.Http;
18+
using System.Net.Sockets;
19+
using Xunit;
20+
21+
namespace FirebaseAdmin.Tests
22+
{
23+
public class FirebaseExceptionTest
24+
{
25+
[Fact]
26+
public void ErrorCodeAndMessage()
27+
{
28+
var exception = new FirebaseException(ErrorCode.Internal, "Test error message");
29+
30+
Assert.Equal(ErrorCode.Internal, exception.ErrorCode);
31+
Assert.Equal("Test error message", exception.Message);
32+
Assert.Null(exception.InnerException);
33+
}
34+
35+
[Fact]
36+
public void InnerException()
37+
{
38+
var inner = new Exception("Inner exception");
39+
var exception = new FirebaseException(ErrorCode.Internal, "Test error message", inner);
40+
41+
Assert.Equal(ErrorCode.Internal, exception.ErrorCode);
42+
Assert.Equal("Test error message", exception.Message);
43+
Assert.Same(inner, exception.InnerException);
44+
}
45+
46+
[Fact]
47+
public void TimeOutError()
48+
{
49+
var socketError = new SocketException((int)SocketError.TimedOut);
50+
var httpError = new HttpRequestException("Test error message", socketError);
51+
var exception = httpError.ToFirebaseException();
52+
53+
Assert.Equal(ErrorCode.DeadlineExceeded, exception.ErrorCode);
54+
Assert.Equal(
55+
"Timed out while making an API call: Test error message", exception.Message);
56+
Assert.Same(httpError, exception.InnerException);
57+
}
58+
59+
[Fact]
60+
public void NetworkUnavailableError()
61+
{
62+
var socketErrorCodes = new List<SocketError>()
63+
{
64+
SocketError.HostDown,
65+
SocketError.HostNotFound,
66+
SocketError.HostUnreachable,
67+
SocketError.NetworkDown,
68+
SocketError.NetworkUnreachable,
69+
};
70+
71+
foreach (var code in socketErrorCodes)
72+
{
73+
var socketError = new SocketException((int)code);
74+
var httpError = new HttpRequestException("Test error message", socketError);
75+
var exception = httpError.ToFirebaseException();
76+
77+
Assert.Equal(ErrorCode.Unavailable, exception.ErrorCode);
78+
Assert.Equal(
79+
"Failed to establish a connection: Test error message", exception.Message);
80+
Assert.Same(httpError, exception.InnerException);
81+
}
82+
}
83+
84+
[Fact]
85+
public void UnknownLowLevelError()
86+
{
87+
var httpError = new HttpRequestException("Test error message");
88+
var exception = httpError.ToFirebaseException();
89+
90+
Assert.Equal(ErrorCode.Unknown, exception.ErrorCode);
91+
Assert.Equal(
92+
"Unknown error while making a remote service call: Test error message",
93+
exception.Message);
94+
Assert.Same(httpError, exception.InnerException);
95+
}
96+
}
97+
}

FirebaseAdmin/FirebaseAdmin/AppOptions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using System;
16-
using System.Collections.Generic;
1715
using Google.Apis.Auth.OAuth2;
1816
using Google.Apis.Http;
1917

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// Copyright 2019, Google Inc. All rights reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace FirebaseAdmin
16+
{
17+
/// <summary>
18+
/// Platform-wide error codes that can be raised by Admin SDK APIs.
19+
/// </summary>
20+
public enum ErrorCode
21+
{
22+
/// <summary>
23+
/// Client specified an invalid argument.
24+
/// </summary>
25+
InvalidArgument,
26+
27+
/// <summary>
28+
/// Request can not be executed in the current system state, such as deleting a non-empty
29+
/// directory.
30+
/// </summary>
31+
FailedPrecondition,
32+
33+
/// <summary>
34+
/// Client specified an invalid range.
35+
/// </summary>
36+
OutOfRange,
37+
38+
/// <summary>
39+
/// Request not authenticated due to missing, invalid, or expired OAuth token.
40+
/// </summary>
41+
Unauthenticated,
42+
43+
/// <summary>
44+
/// Client does not have sufficient permission. This can happen because the OAuth token
45+
/// does not have the right scopes, the client doesn't have permission, or the API has not
46+
/// been enabled for the client project.
47+
/// </summary>
48+
PermissionDenied,
49+
50+
/// <summary>
51+
/// A specified resource is not found, or the request is rejected due to undisclosed
52+
/// reasons, such as whitelisting.
53+
/// </summary>
54+
NotFound,
55+
56+
/// <summary>
57+
/// Concurrency conflict, such as read-modify-write conflict.
58+
/// </summary>
59+
Conflict,
60+
61+
/// <summary>
62+
/// Concurrency conflict, such as read-modify-write conflict.
63+
/// </summary>
64+
Aborted,
65+
66+
/// <summary>
67+
/// The resource that a client tried to create already exists.
68+
/// </summary>
69+
AlreadyExists,
70+
71+
/// <summary>
72+
/// Either out of resource quota or reaching rate limiting.
73+
/// </summary>
74+
ResourceExhausted,
75+
76+
/// <summary>
77+
/// Request cancelled by the client.
78+
/// </summary>
79+
Cancelled,
80+
81+
/// <summary>
82+
/// Unrecoverable data loss or data corruption.
83+
/// </summary>
84+
DataLoss,
85+
86+
/// <summary>
87+
/// Unknown server error.
88+
/// </summary>
89+
Unknown,
90+
91+
/// <summary>
92+
/// Internal server error.
93+
/// </summary>
94+
Internal,
95+
96+
/// <summary>
97+
/// Service unavailable. Typically the server is down.
98+
/// </summary>
99+
Unavailable,
100+
101+
/// <summary>
102+
/// Request deadline exceeded. This will happen only if the caller sets a deadline that is
103+
/// shorter than the method's default deadline (i.e. requested deadline is not enough for
104+
/// the server to process the request) and the request did not finish within the deadline.
105+
/// </summary>
106+
DeadlineExceeded,
107+
}
108+
}

FirebaseAdmin/FirebaseAdmin/Extensions.cs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17+
using System.Linq;
1718
using System.Net.Http;
19+
using System.Net.Sockets;
1820
using System.Text;
1921
using System.Threading;
2022
using System.Threading.Tasks;
@@ -30,6 +32,16 @@ namespace FirebaseAdmin
3032
/// </summary>
3133
internal static class Extensions
3234
{
35+
private static readonly IReadOnlyList<SocketError> NetworkAvailabilityErrors =
36+
new List<SocketError>()
37+
{
38+
SocketError.HostDown,
39+
SocketError.HostNotFound,
40+
SocketError.HostUnreachable,
41+
SocketError.NetworkDown,
42+
SocketError.NetworkUnreachable,
43+
};
44+
3345
/// <summary>
3446
/// Extracts and returns the underlying <see cref="ServiceAccountCredential"/> from a
3547
/// <see cref="GoogleCredential"/>. Returns null if the <c>GoogleCredential</c> is not
@@ -153,5 +165,27 @@ public static IReadOnlyDictionary<TKey, TValue> Copy<TKey, TValue>(
153165

154166
return copy;
155167
}
168+
169+
public static FirebaseException ToFirebaseException(this HttpRequestException exception)
170+
{
171+
ErrorCode code = ErrorCode.Unknown;
172+
string message = "Unknown error while making a remote service call";
173+
if (exception.InnerException is SocketException)
174+
{
175+
var socketException = (SocketException)exception.InnerException;
176+
if (socketException.SocketErrorCode == SocketError.TimedOut)
177+
{
178+
code = ErrorCode.DeadlineExceeded;
179+
message = "Timed out while making an API call";
180+
}
181+
else if (NetworkAvailabilityErrors.Contains(socketException.SocketErrorCode))
182+
{
183+
code = ErrorCode.Unavailable;
184+
message = "Failed to establish a connection";
185+
}
186+
}
187+
188+
return new FirebaseException(code, $"{message}: {exception.Message}", exception);
189+
}
156190
}
157191
}

FirebaseAdmin/FirebaseAdmin/FirebaseException.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,20 @@ namespace FirebaseAdmin
2222
public sealed class FirebaseException : Exception
2323
{
2424
internal FirebaseException(string message)
25-
: base(message) { }
25+
: base(message) { } // TODO: Remove this constructor
2626

2727
internal FirebaseException(string message, Exception inner)
28-
: base(message, inner) { }
28+
: base(message, inner) { } // TODO: Remove this constructor
29+
30+
internal FirebaseException(ErrorCode code, string message, Exception inner = null)
31+
: base(message, inner)
32+
{
33+
this.ErrorCode = code;
34+
}
35+
36+
/// <summary>
37+
/// Gets the platform-wide error code associated with this exception.
38+
/// </summary>
39+
internal ErrorCode ErrorCode { get; private set; } // TODO: Expose this as public
2940
}
3041
}

FirebaseAdmin/FirebaseAdmin/Messaging/FirebaseMessagingClient.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ public async Task<string> SendAsync(
105105
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
106106
if (!response.IsSuccessStatusCode)
107107
{
108+
// TODO(hkj): Handle error responses.
108109
var error = "Response status code does not indicate success: "
109110
+ $"{(int)response.StatusCode} ({response.StatusCode})"
110111
+ $"{Environment.NewLine}{json}";
@@ -116,7 +117,7 @@ public async Task<string> SendAsync(
116117
}
117118
catch (HttpRequestException e)
118119
{
119-
throw new FirebaseException("Error while calling the FCM service.", e);
120+
throw e.ToFirebaseException();
120121
}
121122
}
122123

0 commit comments

Comments
 (0)