Skip to content

Commit 58205aa

Browse files
authored
Tunnel underlying NSErrors through C++ Status (#2808)
* Tunnel underlying NSErrors through Status * Make FIRFirestoreErrorDomain available to CMake builds The prior state was actually semi-broken. The firebase_credentials_provider_apple definition wasn't extern "C" so it was defining the value in addition to whatever FIRFirestore was doing. FIRFirestore's value isn't available to straight C++ though, so this change pushes the definition down into error_apple.mm so that it can be shared by the Objective-C API and MakeNSError. Pushing MakeNSError up into the API layer isn't practical yet so this change is a compromise that makes things work
1 parent f4d9c99 commit 58205aa

File tree

13 files changed

+259
-18
lines changed

13 files changed

+259
-18
lines changed

Firestore/Example/Firestore.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@
259259
5492E0BF2021555100B64F25 /* FSTFieldValueTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */; };
260260
5492E0C72021557E00B64F25 /* FSTSerializerBetaTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */; };
261261
5492E0C92021557E00B64F25 /* FSTRemoteEventTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */; };
262+
5493A424225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; };
263+
5493A425225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; };
264+
5493A426225F9990006DE7BA /* status_apple_test.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5493A423225F9990006DE7BA /* status_apple_test.mm */; };
262265
5495EB032040E90200EBA509 /* CodableGeoPointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */; };
263266
54995F6F205B6E12004EFFA0 /* leveldb_key_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */; };
264267
549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */ = {isa = PBXBuildFile; fileRef = 549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */; };
@@ -831,6 +834,7 @@
831834
5492E0B82021555100B64F25 /* FSTFieldValueTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTFieldValueTests.mm; sourceTree = "<group>"; };
832835
5492E0C12021557E00B64F25 /* FSTSerializerBetaTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTSerializerBetaTests.mm; sourceTree = "<group>"; };
833836
5492E0C32021557E00B64F25 /* FSTRemoteEventTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FSTRemoteEventTests.mm; sourceTree = "<group>"; };
837+
5493A423225F9990006DE7BA /* status_apple_test.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = status_apple_test.mm; sourceTree = "<group>"; };
834838
5495EB022040E90200EBA509 /* CodableGeoPointTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CodableGeoPointTests.swift; sourceTree = "<group>"; };
835839
54995F6E205B6E12004EFFA0 /* leveldb_key_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = leveldb_key_test.cc; sourceTree = "<group>"; };
836840
549CCA4C20A36DBB00BCEB75 /* sorted_set_test.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = sorted_set_test.cc; sourceTree = "<group>"; };
@@ -1251,6 +1255,7 @@
12511255
AB380D03201BC6E400D97691 /* ordered_code_test.cc */,
12521256
403DBF6EFB541DFD01582AA3 /* path_test.cc */,
12531257
54740A531FC913E500713A1A /* secure_random_test.cc */,
1258+
5493A423225F9990006DE7BA /* status_apple_test.mm */,
12541259
54A0352C20A3B3D7003E0143 /* status_test.cc */,
12551260
54A0352B20A3B3D7003E0143 /* status_test_util.h */,
12561261
54A0352D20A3B3D7003E0143 /* statusor_test.cc */,
@@ -3051,6 +3056,7 @@
30513056
862B1AC9EDAB309BBF4FB18C /* sorted_map_test.cc in Sources */,
30523057
4A62B708A6532DD45414DA3A /* sorted_set_test.cc in Sources */,
30533058
C9F96C511F45851D38EC449C /* status.pb.cc in Sources */,
3059+
5493A425225F9990006DE7BA /* status_apple_test.mm in Sources */,
30543060
4DC660A62BC2B6369DA5C563 /* status_test.cc in Sources */,
30553061
74985DE2C7EF4150D7A455FD /* statusor_test.cc in Sources */,
30563062
C5C01A1FB216DA4BA8BF1A02 /* stream_test.mm in Sources */,
@@ -3218,6 +3224,7 @@
32183224
86E6FC2B7657C35B342E1436 /* sorted_map_test.cc in Sources */,
32193225
8413BD9958F6DD52C466D70F /* sorted_set_test.cc in Sources */,
32203226
0D2D25522A94AA8195907870 /* status.pb.cc in Sources */,
3227+
5493A426225F9990006DE7BA /* status_apple_test.mm in Sources */,
32213228
C0AD8DB5A84CAAEE36230899 /* status_test.cc in Sources */,
32223229
DC48407370E87F2233D7AB7E /* statusor_test.cc in Sources */,
32233230
215643858470A449D3A3E168 /* stream_test.mm in Sources */,
@@ -3460,6 +3467,7 @@
34603467
549CCA5220A36DBC00BCEB75 /* sorted_map_test.cc in Sources */,
34613468
549CCA5020A36DBC00BCEB75 /* sorted_set_test.cc in Sources */,
34623469
618BBEB120B89AAC00B5BCE7 /* status.pb.cc in Sources */,
3470+
5493A424225F9990006DE7BA /* status_apple_test.mm in Sources */,
34633471
54A0352F20A3B3D8003E0143 /* status_test.cc in Sources */,
34643472
54A0353020A3B3D8003E0143 /* statusor_test.cc in Sources */,
34653473
B66D8996213609EE0086DA0C /* stream_test.mm in Sources */,

Firestore/Source/API/FIRFirestore.mm

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,6 @@
5555

5656
NS_ASSUME_NONNULL_BEGIN
5757

58-
extern "C" NSString *const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
59-
6058
#pragma mark - FIRFirestore
6159

6260
@interface FIRFirestore ()

Firestore/core/src/firebase/firestore/auth/firebase_credentials_provider_apple.mm

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,10 @@
2222
#import <FirebaseCore/FIRComponentContainer.h>
2323
#import <FirebaseCore/FIROptionsInternal.h>
2424

25+
#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
2526
#include "Firestore/core/src/firebase/firestore/util/hard_assert.h"
2627
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
2728

28-
// NB: This is also defined in Firestore/Source/Public/FIRFirestoreErrors.h
29-
// NOLINTNEXTLINE: public constant
30-
NSString* const FIRFirestoreErrorDomain = @"FIRFirestoreErrorDomain";
31-
3229
namespace firebase {
3330
namespace firestore {
3431
namespace auth {

Firestore/core/src/firebase/firestore/util/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,8 @@ cc_library(
246246
comparison.h
247247
config.h
248248
delayed_constructor.h
249+
error_apple.h
250+
error_apple.mm
249251
hashing.h
250252
iterator_adaptors.h
251253
objc_compatibility.h

Firestore/core/src/firebase/firestore/util/error_apple.h

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,43 @@
2222

2323
#import <Foundation/Foundation.h>
2424

25-
#import <FirebaseFirestore/FIRFirestoreErrors.h> // for FIRFirestoreErrorDomain
26-
2725
#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
2826
#include "Firestore/core/src/firebase/firestore/util/status.h"
2927
#include "Firestore/core/src/firebase/firestore/util/string_apple.h"
3028
#include "absl/strings/string_view.h"
3129

30+
// The Cloud Firestore error domain. Keep in sync with FIRFirestoreErrors.h.
31+
// Exposed here to make it possible to build in CMake without bringing in the
32+
// sources under Firestore/Source.
33+
FOUNDATION_EXPORT NSString* const FIRFirestoreErrorDomain
34+
NS_SWIFT_NAME(FirestoreErrorDomain);
35+
3236
namespace firebase {
3337
namespace firestore {
3438
namespace util {
3539

3640
// Translates a set of error_code and error_msg to an NSError.
3741
inline NSError* MakeNSError(const int64_t error_code,
38-
const absl::string_view error_msg) {
42+
const absl::string_view error_msg,
43+
NSError* cause = nil) {
3944
if (error_code == FirestoreErrorCode::Ok) {
4045
return nil;
4146
}
42-
return [NSError
43-
errorWithDomain:FIRFirestoreErrorDomain
44-
code:static_cast<NSInteger>(error_code)
45-
userInfo:@{NSLocalizedDescriptionKey : WrapNSString(error_msg)}];
47+
48+
NSMutableDictionary<NSString*, id>* user_info =
49+
[NSMutableDictionary dictionary];
50+
user_info[NSLocalizedDescriptionKey] = WrapNSString(error_msg);
51+
if (cause) {
52+
user_info[NSUnderlyingErrorKey] = cause;
53+
}
54+
55+
return [NSError errorWithDomain:FIRFirestoreErrorDomain
56+
code:static_cast<NSInteger>(error_code)
57+
userInfo:user_info];
4658
}
4759

4860
inline NSError* MakeNSError(const util::Status& status) {
49-
return MakeNSError(status.code(), status.error_message());
61+
return status.ToNSError();
5062
}
5163

5264
inline Status MakeStatus(NSError* error) {
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2019 Google
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
18+
19+
// NB: This is also defined in Firestore/Source/Public/FIRFirestoreErrors.h
20+
// NOLINTNEXTLINE: public constant
21+
FOUNDATION_EXPORT NSString* const FIRFirestoreErrorDomain =
22+
@"FIRFirestoreErrorDomain";

Firestore/core/src/firebase/firestore/util/status.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include "Firestore/core/src/firebase/firestore/util/status.h"
1818

1919
#include <ostream>
20+
#include <utility>
2021

2122
#include "Firestore/core/src/firebase/firestore/util/string_format.h"
2223
#include "absl/memory/memory.h"
@@ -49,6 +50,22 @@ Status& Status::CausedBy(const Status& cause) {
4950
}
5051

5152
absl::StrAppend(&state_->msg, ": ", cause.error_message());
53+
54+
// If this Status has no accompanying PlatformError but the cause does, create
55+
// an PlatformError for this Status ahead of time to preserve the causal chain
56+
// that Status doesn't otherwise support.
57+
if (state_->platform_error == nullptr &&
58+
cause.state_->platform_error != nullptr) {
59+
state_->platform_error =
60+
cause.state_->platform_error->WrapWith(code(), error_message());
61+
}
62+
63+
return *this;
64+
}
65+
66+
Status& Status::WithPlatformError(std::unique_ptr<PlatformError> error) {
67+
HARD_ASSERT(!ok(), "Platform errors should not be applied to Status::OK()");
68+
state_->platform_error = std::move(error);
5269
return *this;
5370
}
5471

Firestore/core/src/firebase/firestore/util/status.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ namespace firebase {
3535
namespace firestore {
3636
namespace util {
3737

38+
class PlatformError;
39+
3840
/// Denotes success or failure of a call.
3941
class ABSL_MUST_USE_RESULT Status {
4042
public:
@@ -63,6 +65,8 @@ class ABSL_MUST_USE_RESULT Status {
6365

6466
#if defined(__OBJC__)
6567
static Status FromNSError(NSError* error);
68+
69+
NSError* ToNSError() const;
6670
#endif // defined(__OBJC__)
6771

6872
/// Returns true iff the status indicates success.
@@ -97,6 +101,8 @@ class ABSL_MUST_USE_RESULT Status {
97101
/// \return *this
98102
Status& CausedBy(const Status& cause);
99103

104+
Status& WithPlatformError(std::unique_ptr<PlatformError> error);
105+
100106
/// \brief Return a string representation of this status suitable for
101107
/// printing. Returns the string `"OK"` for success.
102108
std::string ToString() const;
@@ -110,20 +116,51 @@ class ABSL_MUST_USE_RESULT Status {
110116
private:
111117
static const std::string& empty_string();
112118
struct State {
119+
State() = default;
120+
State(const State& other);
121+
113122
FirestoreErrorCode code;
114123
std::string msg;
124+
125+
// An additional platform-specific error representation that was used to
126+
// generate this Status. The PlatformError does not meaningfully contribute
127+
// to the identity of this Status: it exists to allow tunneling e.g.
128+
// NSError* to Status and back to NSError* losslessly.
129+
std::unique_ptr<PlatformError> platform_error;
115130
};
116-
// OK status has a `NULL` state_. Otherwise, `state_` points to
131+
// OK status has a `nullptr` state_. Otherwise, `state_` points to
117132
// a `State` structure containing the error code and message(s)
118133
std::unique_ptr<State> state_;
119134

120135
void SlowCopyFrom(const State* src);
121136
};
122137

138+
class PlatformError {
139+
public:
140+
virtual ~PlatformError() {
141+
}
142+
143+
virtual std::unique_ptr<PlatformError> Copy() = 0;
144+
145+
/**
146+
* Creates a new PlatformError with the given code and message, whose cause is
147+
* this PlatformError.
148+
*/
149+
virtual std::unique_ptr<PlatformError> WrapWith(FirestoreErrorCode code,
150+
std::string message) = 0;
151+
};
152+
123153
inline Status::Status(const Status& s)
124154
: state_((s.state_ == nullptr) ? nullptr : new State(*s.state_)) {
125155
}
126156

157+
inline Status::State::State(const State& s)
158+
: code(s.code),
159+
msg(s.msg),
160+
platform_error((s.platform_error == nullptr) ? nullptr
161+
: s.platform_error->Copy()) {
162+
}
163+
127164
inline void Status::operator=(const Status& s) {
128165
// The following condition catches both aliasing (when this == &s),
129166
// and the common case where both s and *this are ok.

Firestore/core/src/firebase/firestore/util/status_apple.mm

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,30 +18,79 @@
1818

1919
#if defined(__APPLE__)
2020

21+
#include "Firestore/core/src/firebase/firestore/util/error_apple.h"
2122
#include "Firestore/core/src/firebase/firestore/util/string_format.h"
23+
#include "absl/memory/memory.h"
2224

2325
namespace firebase {
2426
namespace firestore {
2527
namespace util {
2628

29+
class UnderlyingNSError : public PlatformError {
30+
public:
31+
explicit UnderlyingNSError(NSError* error) : error_(error) {
32+
}
33+
34+
static std::unique_ptr<UnderlyingNSError> Create(NSError* error) {
35+
return absl::make_unique<UnderlyingNSError>(error);
36+
}
37+
38+
static NSError* Recover(
39+
const std::unique_ptr<PlatformError>& platform_error) {
40+
if (platform_error == nullptr) {
41+
return nil;
42+
}
43+
44+
return static_cast<UnderlyingNSError*>(platform_error.get())->error();
45+
}
46+
47+
std::unique_ptr<PlatformError> Copy() override {
48+
return absl::make_unique<UnderlyingNSError>(error_);
49+
}
50+
51+
std::unique_ptr<PlatformError> WrapWith(FirestoreErrorCode code,
52+
std::string message) override {
53+
NSError* chain = MakeNSError(code, message, error_);
54+
return Create(chain);
55+
}
56+
57+
NSError* error() const {
58+
return error_;
59+
}
60+
61+
private:
62+
NSError* error_;
63+
};
64+
2765
Status Status::FromNSError(NSError* error) {
2866
if (!error) {
2967
return Status::OK();
3068
}
3169

32-
NSError* original = error;
70+
auto original = UnderlyingNSError::Create(error);
3371

3472
while (error) {
3573
if ([error.domain isEqualToString:NSPOSIXErrorDomain]) {
3674
return FromErrno(static_cast<int>(error.code),
37-
MakeString(original.localizedDescription));
75+
MakeString(original->error().localizedDescription))
76+
.WithPlatformError(std::move(original));
3877
}
3978

4079
error = error.userInfo[NSUnderlyingErrorKey];
4180
}
4281

4382
return Status{FirestoreErrorCode::Unknown,
44-
StringFormat("Unknown error: %s", original)};
83+
StringFormat("Unknown error: %s", original->error())}
84+
.WithPlatformError(std::move(original));
85+
}
86+
87+
NSError* Status::ToNSError() const {
88+
if (ok()) return nil;
89+
90+
NSError* error = UnderlyingNSError::Recover(state_->platform_error);
91+
if (error) return error;
92+
93+
return MakeNSError(code(), error_message());
4594
}
4695

4796
} // namespace util

Firestore/core/src/firebase/firestore/util/status_posix.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ static FirestoreErrorCode CodeForErrno(int errno_code) {
184184
}
185185

186186
Status Status::FromErrno(int errno_code, absl::string_view msg) {
187+
if (errno_code == 0) {
188+
return Status::OK();
189+
}
190+
187191
FirestoreErrorCode canonical_code = CodeForErrno(errno_code);
188192
return Status{canonical_code,
189193
util::StringFormat("%s (errno %s: %s)", msg, errno_code,

Firestore/core/test/firebase/firestore/util/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ cc_test(
154154
hashing_test.cc
155155
iterator_adaptors_test.cc
156156
ordered_code_test.cc
157+
status_apple_test.mm
157158
status_test.cc
158159
status_test_util.h
159160
statusor_test.cc

0 commit comments

Comments
 (0)