Skip to content

Commit c306261

Browse files
Rejecting invalid nested map updates
1 parent b1aa368 commit c306261

File tree

3 files changed

+50
-26
lines changed

3 files changed

+50
-26
lines changed

src/document.js

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ class DocumentSnapshot {
680680
* This functions turns { foo.bar : foobar } into { foo { bar : foobar }}
681681
*
682682
* @private
683-
* @param {Map.<string|FieldPath, *>} data - The field/value map to expand.
683+
* @param {Map.<FieldPath, *>} data - The field/value map to expand.
684684
* @returns {DocumentData} The expanded JavaScript object.
685685
*/
686686
static expandMap(data) {
@@ -701,30 +701,17 @@ class DocumentSnapshot {
701701
target[key] = {};
702702
merge(target[key], value, path, pos + 1);
703703
}
704-
} else if (isPlainObject(target[key])) {
705-
if (isLast) {
706-
// The existing object has deeper nesting that the value we are trying
707-
// to merge.
708-
throw new Error(
709-
`Field "${new FieldPath(path)}" has conflicting definitions.`
710-
);
711-
} else {
712-
merge(target[key], value, path, pos + 1);
713-
}
714704
} else {
715-
// We are trying to merge an object with a primitive.
716-
throw new Error(
717-
`Field "${new FieldPath(
718-
path.slice(0, pos + 1)
719-
)}" has conflicting definitions.`
720-
);
705+
validate.isPlainObject(target[key]);
706+
assert(!isLast, "Can't merge current value into a nested map");
707+
merge(target[key], value, path, pos + 1);
721708
}
722709
}
723710

724711
let res = {};
725712

726713
data.forEach((value, key) => {
727-
let components = FieldPath.fromArgument(key).toArray();
714+
let components = key.toArray();
728715
merge(res, value, components, 0);
729716
});
730717

src/write-batch.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,8 @@ class WriteBatch {
370370
}
371371
}
372372

373+
validate.isUpdateMap('dataOrField', updateMap);
374+
373375
let documentMask = DocumentMask.fromMap(updateMap);
374376
let expandedObject = DocumentSnapshot.expandMap(updateMap);
375377
let document = new DocumentSnapshot(
@@ -523,6 +525,34 @@ class WriteBatch {
523525
}
524526
}
525527

528+
/*!
529+
* Validates that the update data does not contain any ambiguous field
530+
* definitions (such as 'a.b' and 'a').
531+
*
532+
* @param {Map.<FieldPath, *>} data - An update map with field/value pairs.
533+
* @returns {boolean} 'true' if the input is a valid update map.
534+
*/
535+
function validateUpdateMap(data) {
536+
if (!is.instance(data, Map)) {
537+
throw new Error('Input is not a map.');
538+
}
539+
540+
const fields = [];
541+
data.forEach((value, key) => {
542+
fields.push(key);
543+
});
544+
545+
fields.sort((left, right) => left.compareTo(right));
546+
547+
for (let i = 1; i < fields.length; ++i) {
548+
if (fields[i - 1].isPrefixOf(fields[i])) {
549+
throw new Error(`Field "${fields[i - 1]}" has conflicting definitions.`);
550+
}
551+
}
552+
553+
return true;
554+
}
555+
526556
module.exports = (
527557
FirestoreType,
528558
DocumentReferenceType,
@@ -540,6 +570,7 @@ module.exports = (
540570
FieldPath: FieldPath.validateFieldPath,
541571
Precondition: document.validatePrecondition,
542572
SetOptions: document.validateSetOptions,
573+
UpdateMap: validateUpdateMap,
543574
});
544575
return {
545576
WriteBatch,

test/document.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1305,47 +1305,53 @@ describe('update document', function() {
13051305
foo: 'foobar',
13061306
'foo.bar': 'foobar',
13071307
});
1308-
}, /Field "foo" has conflicting definitions\./);
1308+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
13091309

13101310
assert.throws(() => {
13111311
firestore.doc('collectionId/documentId').update({
13121312
foo: 'foobar',
13131313
'foo.bar.foobar': 'foobar',
13141314
});
1315-
}, /Field "foo" has conflicting definitions\./);
1315+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
13161316

13171317
assert.throws(() => {
13181318
firestore.doc('collectionId/documentId').update({
13191319
'foo.bar': 'foobar',
13201320
foo: 'foobar',
13211321
});
1322-
}, /Field "foo" has conflicting definitions\./);
1322+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
13231323

13241324
assert.throws(() => {
13251325
firestore.doc('collectionId/documentId').update({
13261326
'foo.bar': 'foobar',
13271327
'foo.bar.foo': 'foobar',
13281328
});
1329-
}, /Field "foo.bar" has conflicting definitions\./);
1329+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo.bar" has conflicting definitions\./);
13301330

13311331
assert.throws(() => {
13321332
firestore.doc('collectionId/documentId').update({
13331333
'foo.bar': {foo: 'foobar'},
13341334
'foo.bar.foo': 'foobar',
13351335
});
1336-
}, /Field "foo.bar.foo" has conflicting definitions\./);
1336+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo.bar" has conflicting definitions\./);
13371337

13381338
assert.throws(() => {
13391339
firestore
13401340
.doc('collectionId/documentId')
13411341
.update('foo.bar', 'foobar', 'foo', 'foobar');
1342-
}, /Field "foo" has conflicting definitions\./);
1342+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
13431343

13441344
assert.throws(() => {
13451345
firestore
13461346
.doc('collectionId/documentId')
1347-
.update('foo', 'foobar', 'foo.bar', 'foobar');
1348-
}, /Field "foo" has conflicting definitions\./);
1347+
.update('foo', {foobar: 'foobar'}, 'foo.bar', {foobar: 'foobar'});
1348+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
1349+
1350+
assert.throws(() => {
1351+
firestore
1352+
.doc('collectionId/documentId')
1353+
.update('foo', {foobar: 'foobar'}, 'foo.bar', {foobar: 'foobar'});
1354+
}, /Argument "dataOrField" is not a valid UpdateMap. Field "foo" has conflicting definitions\./);
13491355
});
13501356

13511357
it('with valid field paths', function() {

0 commit comments

Comments
 (0)