Skip to content

Commit 678ea76

Browse files
authored
Merge pull request #324 from firebase/g3merge10
Firestore: Expand validation_test to cover other platforms
2 parents a84d891 + 0d5f778 commit 678ea76

File tree

8 files changed

+384
-199
lines changed

8 files changed

+384
-199
lines changed

firestore/src/android/field_path_portable.cc

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,22 @@ std::vector<std::string> SplitOnDots(const std::string& input) {
9090
return result;
9191
}
9292

93+
void ValidateSegments(const std::vector<std::string>& segments) {
94+
if (segments.empty()) {
95+
SimpleThrowInvalidArgument(
96+
"Invalid field path. Provided names must not be empty.");
97+
}
98+
99+
for (size_t i = 0; i < segments.size(); ++i) {
100+
if (segments[i].empty()) {
101+
std::ostringstream message;
102+
message << "Invalid field name at index " << i
103+
<< ". Field names must not be empty.";
104+
SimpleThrowInvalidArgument(message.str());
105+
}
106+
}
107+
}
108+
93109
} // anonymous namespace
94110

95111
std::string FieldPathPortable::CanonicalString() const {
@@ -118,6 +134,12 @@ bool FieldPathPortable::IsKeyFieldPath() const {
118134
return size() == 1 && segments_[0] == FieldPathPortable::kDocumentKeyPath;
119135
}
120136

137+
FieldPathPortable FieldPathPortable::FromSegments(
138+
std::vector<std::string> segments) {
139+
ValidateSegments(segments);
140+
return FieldPathPortable(Move(segments));
141+
}
142+
121143
FieldPathPortable FieldPathPortable::FromDotSeparatedString(
122144
const std::string& path) {
123145
if (path.find_first_of("~*/[]") != std::string::npos) {

firestore/src/android/field_path_portable.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,16 @@ class FieldPathPortable {
6565
return segments_ >= rhs.segments_;
6666
}
6767

68-
// Creates and returns a new path from a dot-separated field-path string,
69-
// where path segments are separated by a dot ".".
68+
/**
69+
* Creates and returns a new path from an explicitly pre-split list of
70+
* segments.
71+
*/
72+
static FieldPathPortable FromSegments(std::vector<std::string> segments);
73+
74+
/**
75+
* Creates and returns a new path from a dot-separated field-path string,
76+
* where path segments are separated by a dot ".".
77+
*/
7078
static FieldPathPortable FromDotSeparatedString(const std::string& path);
7179

7280
static FieldPathPortable KeyFieldPath();

firestore/src/android/firestore_android.cc

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ namespace {
6161

6262
using jni::Constructor;
6363
using jni::Env;
64+
using jni::Global;
65+
using jni::HashMap;
6466
using jni::Loader;
6567
using jni::Local;
6668
using jni::Long;
@@ -170,15 +172,15 @@ class JavaFirestoreMap {
170172
private:
171173
// Ensures that the backing map is initialized.
172174
// Prerequisite: `mutex_` must be locked before calling this method.
173-
jni::HashMap& GetMapLocked(Env& env) {
175+
HashMap& GetMapLocked(Env& env) {
174176
if (!firestores_) {
175-
firestores_ = jni::HashMap::Create(env);
177+
firestores_ = HashMap::Create(env);
176178
}
177179
return firestores_;
178180
}
179181

180182
Mutex mutex_;
181-
jni::Global<jni::HashMap> firestores_;
183+
Global<HashMap> firestores_;
182184
};
183185

184186
// init_mutex protects all the globals below.
@@ -496,34 +498,34 @@ void FirestoreInternal::ClearListeners() {
496498
listener_registrations_.clear();
497499
}
498500

499-
jni::Env FirestoreInternal::GetEnv() {
500-
jni::Env env;
501+
Env FirestoreInternal::GetEnv() {
502+
Env env;
501503
env.SetUnhandledExceptionHandler(GlobalUnhandledExceptionHandler, nullptr);
502504
return env;
503505
}
504506

505507
CollectionReference FirestoreInternal::NewCollectionReference(
506-
jni::Env& env, const jni::Object& reference) const {
508+
Env& env, const jni::Object& reference) const {
507509
return MakePublic<CollectionReference>(env, mutable_this(), reference);
508510
}
509511

510512
DocumentReference FirestoreInternal::NewDocumentReference(
511-
jni::Env& env, const jni::Object& reference) const {
513+
Env& env, const jni::Object& reference) const {
512514
return MakePublic<DocumentReference>(env, mutable_this(), reference);
513515
}
514516

515517
DocumentSnapshot FirestoreInternal::NewDocumentSnapshot(
516-
jni::Env& env, const jni::Object& snapshot) const {
518+
Env& env, const jni::Object& snapshot) const {
517519
return MakePublic<DocumentSnapshot>(env, mutable_this(), snapshot);
518520
}
519521

520-
Query FirestoreInternal::NewQuery(jni::Env& env,
522+
Query FirestoreInternal::NewQuery(Env& env,
521523
const jni::Object& query) const {
522524
return MakePublic<Query>(env, mutable_this(), query);
523525
}
524526

525527
QuerySnapshot FirestoreInternal::NewQuerySnapshot(
526-
jni::Env& env, const jni::Object& snapshot) const {
528+
Env& env, const jni::Object& snapshot) const {
527529
return MakePublic<QuerySnapshot>(env, mutable_this(), snapshot);
528530
}
529531

firestore/src/android/transaction_android.cc

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "firestore/src/android/transaction_android.h"
22

3+
#include <stdexcept>
34
#include <utility>
45

56
#include "app/meta/move.h"
@@ -10,9 +11,11 @@
1011
#include "firestore/src/android/field_value_android.h"
1112
#include "firestore/src/android/set_options_android.h"
1213
#include "firestore/src/android/util_android.h"
14+
#include "firestore/src/common/macros.h"
1315
#include "firestore/src/jni/env.h"
1416
#include "firestore/src/jni/hash_map.h"
1517
#include "firestore/src/jni/loader.h"
18+
#include "Firestore/core/src/util/firestore_exceptions.h"
1619

1720
namespace firebase {
1821
namespace firestore {
@@ -121,9 +124,10 @@ DocumentSnapshot TransactionInternal::Get(const DocumentReference& document,
121124
}
122125

123126
if (!ExceptionInternal::IsFirestoreException(env, exception)) {
124-
// We would only preserve exception if it is not
125-
// FirebaseFirestoreException. The user should decide whether to raise the
126-
// error or let the transaction succeed.
127+
// Only preserve the exception if it is not `FirebaseFirestoreException`.
128+
// For Firestore exceptions, the user decides whether to raise the error
129+
// or let the transaction succeed through the error code/message the
130+
// `TransactionFunction` returns.
127131
PreserveException(env, Move(exception));
128132
}
129133
return DocumentSnapshot{};
@@ -141,9 +145,18 @@ DocumentSnapshot TransactionInternal::Get(const DocumentReference& document,
141145
}
142146

143147
Env TransactionInternal::GetEnv() {
148+
#if FIRESTORE_HAVE_EXCEPTIONS
149+
// If exceptions are enabled, report Java exceptions by translating them to
150+
// their C++ equivalents in the usual way. These will throw out to the
151+
// user-supplied TransactionFunction and ultimately out to
152+
// `TransactionFunctionNativeApply`, below.
153+
return FirestoreInternal::GetEnv();
154+
155+
#else
144156
Env env;
145157
env.SetUnhandledExceptionHandler(ExceptionHandler, this);
146158
return env;
159+
#endif
147160
}
148161

149162
void TransactionInternal::ExceptionHandler(Env& env,
@@ -195,7 +208,32 @@ jobject TransactionInternal::TransactionFunctionNativeApply(
195208
new TransactionInternal(firestore, Object(java_transaction)));
196209

197210
std::string message;
198-
Error code = transaction_function->Apply(transaction, message);
211+
Error code;
212+
213+
#if FIRESTORE_HAVE_EXCEPTIONS
214+
try {
215+
#endif
216+
217+
code = transaction_function->Apply(transaction, message);
218+
219+
#if FIRESTORE_HAVE_EXCEPTIONS
220+
} catch (const FirestoreException& e) {
221+
message = e.what();
222+
code = e.code();
223+
} catch (const std::invalid_argument& e) {
224+
message = e.what();
225+
code = Error::kErrorInvalidArgument;
226+
} catch (const std::logic_error& e) {
227+
message = e.what();
228+
code = Error::kErrorFailedPrecondition;
229+
} catch (const std::exception& e) {
230+
message = std::string("Unknown exception: ") + e.what();
231+
code = Error::kErrorUnknown;
232+
} catch (...) {
233+
message = "Unknown exception";
234+
code = Error::kErrorUnknown;
235+
}
236+
#endif // FIRESTORE_HAVE_EXCEPTIONS
199237

200238
// Verify that `internal_` is not null before using it. It could be set to
201239
// `nullptr` if the `FirestoreInternal` is destroyed during the invocation of

firestore/src/common/field_path.cc

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
#include <unordered_set>
2525
#endif // defined(_STLPORT_VERSION)
2626

27+
#include "app/meta/move.h"
28+
2729
#if defined(__ANDROID__) || defined(FIRESTORE_STUB_BUILD)
2830
#include "firestore/src/android/field_path_portable.h"
2931
#else
@@ -38,11 +40,11 @@ FieldPath::FieldPath() {}
3840

3941
#if !defined(_STLPORT_VERSION)
4042
FieldPath::FieldPath(std::initializer_list<std::string> field_names)
41-
: internal_(new FieldPathInternal{field_names}) {}
43+
: internal_(InternalFromSegments(std::vector<std::string>(field_names))) {}
4244
#endif // !defined(_STLPORT_VERSION)
4345

4446
FieldPath::FieldPath(const std::vector<std::string>& field_names)
45-
: internal_(new FieldPathInternal{std::vector<std::string>{field_names}}) {}
47+
: internal_(InternalFromSegments(field_names)) {}
4648

4749
FieldPath::FieldPath(const FieldPath& path)
4850
: internal_(new FieldPathInternal{*path.internal_}) {}
@@ -78,6 +80,12 @@ FieldPath FieldPath::DocumentId() {
7880
return FieldPath{new FieldPathInternal{FieldPathInternal::KeyFieldPath()}};
7981
}
8082

83+
FieldPath::FieldPathInternal* FieldPath::InternalFromSegments(
84+
std::vector<std::string> field_names) {
85+
return new FieldPathInternal(
86+
FieldPathInternal::FromSegments(Move(field_names)));
87+
}
88+
8189
/* static */
8290
FieldPath FieldPath::FromDotSeparatedString(const std::string& path) {
8391
return FieldPath{

firestore/src/include/firebase/firestore/field_path.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,9 @@ class FieldPath final {
170170

171171
explicit FieldPath(FieldPathInternal* internal);
172172

173+
static FieldPathInternal* InternalFromSegments(
174+
std::vector<std::string> field_names);
175+
173176
static FieldPath FromDotSeparatedString(const std::string& path);
174177

175178
FieldPathInternal* internal_ = nullptr;

firestore/src/ios/user_data_converter_ios.cc

Lines changed: 29 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ using nanopb::ByteString;
3636

3737
using Type = FieldValue::Type;
3838

39+
FIRESTORE_ATTRIBUTE_NORETURN
40+
void ThrowInvalidData(const ParseContext& context, const std::string& message) {
41+
std::string full_message =
42+
"Invalid data. " + message + context.FieldDescription();
43+
SimpleThrowInvalidArgument(full_message);
44+
}
45+
3946
void ParseDelete(ParseContext&& context) {
4047
if (context.data_source() == UserDataSource::MergeSet) {
4148
// No transform to add for a delete, but we need to add it to our field mask
@@ -49,32 +56,16 @@ void ParseDelete(ParseContext&& context) {
4956
!context.path()->empty(),
5057
"FieldValue.Delete() at the top level should have already been "
5158
"handled.");
52-
// TODO(b/147444199): use string formatting.
53-
// ThrowInvalidArgument(
54-
// "FieldValue::Delete() can only appear at the top level of your "
55-
// "update data%s",
56-
// context.FieldDescription());
57-
auto message =
58-
std::string(
59-
"FieldValue::Delete() can only appear at the top level of your "
60-
"update data") +
61-
context.FieldDescription();
62-
SimpleThrowInvalidArgument(message);
59+
ThrowInvalidData(context,
60+
"FieldValue::Delete() can only appear at the top level of "
61+
"your update data");
6362
}
6463

6564
// We shouldn't encounter delete sentinels for queries or non-merge `Set`
6665
// calls.
67-
// TODO(b/147444199): use string formatting.
68-
// ThrowInvalidArgument(
69-
// "FieldValue::Delete() can only be used with Update() and Set() with "
70-
// "merge == true%s",
71-
// context.FieldDescription());
72-
auto message =
73-
std::string(
74-
"FieldValue::Delete() can only be used with Update() and Set() with "
75-
"merge == true") +
76-
context.FieldDescription();
77-
SimpleThrowInvalidArgument(message);
66+
ThrowInvalidData(context,
67+
"FieldValue::Delete() can only be used with Update() and "
68+
"Set() with merge == true");
7869
}
7970

8071
void ParseServerTimestamp(ParseContext&& context) {
@@ -143,7 +134,7 @@ FieldMask CreateFieldMask(const ParseAccumulator& accumulator,
143134
// path.CanonicalString());
144135
auto message =
145136
std::string("Field '") + path.CanonicalString() +
146-
"' is specified in your field mask but missing from your input data.";
137+
"' is specified in your field mask but not in your input data.";
147138
SimpleThrowInvalidArgument(message);
148139
}
149140

@@ -276,7 +267,7 @@ model::FieldValue::Array UserDataConverter::ParseArray(
276267
// disable this validation.
277268
if (context.array_element() &&
278269
context.data_source() != core::UserDataSource::ArrayArgument) {
279-
SimpleThrowInvalidArgument("Nested arrays are not supported");
270+
ThrowInvalidData(context, "Nested arrays are not supported");
280271
}
281272

282273
model::FieldValue::Array result;
@@ -323,21 +314,21 @@ void UserDataConverter::ParseSentinel(const FieldValue& value,
323314
// Sentinels are only supported with writes, and not within arrays.
324315
if (!context.write()) {
325316
// TODO(b/147444199): use string formatting.
326-
// ThrowInvalidArgument("%s can only be used with Update() and Set()%s",
327-
// Describe(value.type()), context.FieldDescription());
328-
auto message = Describe(value.type()) +
329-
" can only be used with Update() and Set()" +
330-
context.FieldDescription();
331-
SimpleThrowInvalidArgument(message);
317+
// ThrowInvalidData(
318+
// context, "%s can only be used with Update() and Set()%s",
319+
// Describe(value.type()), context.FieldDescription());
320+
auto message =
321+
Describe(value.type()) + " can only be used with Update() and Set()";
322+
ThrowInvalidData(context, message);
332323
}
333324

334325
if (!context.path()) {
335326
// TODO(b/147444199): use string formatting.
336-
// ThrowInvalidArgument("%s is not currently supported inside arrays",
337-
// Describe(value.type()));
327+
// ThrowInvalidData(context, "%s is not currently supported inside arrays",
328+
// Describe(value.type()));
338329
auto message =
339330
Describe(value.type()) + " is not currently supported inside arrays";
340-
SimpleThrowInvalidArgument(message);
331+
ThrowInvalidData(context, message);
341332
}
342333

343334
switch (value.type()) {
@@ -406,7 +397,8 @@ model::FieldValue UserDataConverter::ParseScalar(const FieldValue& value,
406397
GetInternal(reference.firestore())->database_id();
407398
if (other != *database_id_) {
408399
// TODO(b/147444199): use string formatting.
409-
// ThrowInvalidArgument(
400+
// ThrowInvalidData(
401+
// context,
410402
// "DocumentReference is for database %s/%s but should be for "
411403
// "database %s/%s%s",
412404
// other.project_id(), other.database_id(),
@@ -415,10 +407,9 @@ model::FieldValue UserDataConverter::ParseScalar(const FieldValue& value,
415407
auto actual_db = other.project_id() + "/" + other.database_id();
416408
auto expected_db =
417409
database_id_->project_id() + "/" + database_id_->database_id();
418-
auto message = std::string("DocumentReference is for database ") +
419-
actual_db + " but should be for database " +
420-
expected_db + context.FieldDescription();
421-
SimpleThrowInvalidArgument(message);
410+
auto message = std::string("Document reference is for database ") +
411+
actual_db + " but should be for database " + expected_db;
412+
ThrowInvalidData(context, message);
422413
}
423414

424415
const model::DocumentKey& key = GetInternal(&reference)->key();

0 commit comments

Comments
 (0)