diff --git a/spec/ParseQuery.spec.js b/spec/ParseQuery.spec.js index 826bbaa811..d5d1fc473f 100644 --- a/spec/ParseQuery.spec.js +++ b/spec/ParseQuery.spec.js @@ -3348,6 +3348,122 @@ describe('Parse.Query testing', () => { } ); }); + it('exclude keys', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with select same key', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + keys: 'foo', + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBeUndefined(); + }); + + it('exclude keys with select different key', async () => { + const obj = new TestObject({ foo: 'baz', hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + keys: 'foo,hello', + excludeKeys: 'foo', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].foo).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with include same key', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ child: pointer, hello: 'world' }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + include: 'child', + excludeKeys: 'child', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child).toBeUndefined(); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with include different key', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ + child1: pointer, + child2: pointer, + hello: 'world', + }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + include: 'child1,child2', + excludeKeys: 'child1', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child1).toBeUndefined(); + expect(response.data.results[0].child2.objectId).toEqual(pointer.id); + expect(response.data.results[0].hello).toBe('world'); + }); + + it('exclude keys with includeAll', async () => { + const pointer = new TestObject(); + await pointer.save(); + const obj = new TestObject({ + child1: pointer, + child2: pointer, + hello: 'world', + }); + await obj.save(); + + const response = await request({ + url: Parse.serverURL + '/classes/TestObject', + qs: { + includeAll: true, + excludeKeys: 'child1', + where: JSON.stringify({ objectId: obj.id }), + }, + headers: masterKeyHeaders, + }); + expect(response.data.results[0].child).toBeUndefined(); + expect(response.data.results[0].child2.objectId).toEqual(pointer.id); + expect(response.data.results[0].hello).toBe('world'); + }); it('select keys with each query', function(done) { const obj = new TestObject({ foo: 'baz', bar: 1 }); diff --git a/src/RestQuery.js b/src/RestQuery.js index 443668c374..0ff3c63f23 100644 --- a/src/RestQuery.js +++ b/src/RestQuery.js @@ -13,6 +13,7 @@ const AlwaysSelectedKeys = ['objectId', 'createdAt', 'updatedAt', 'ACL']; // count // include // keys +// excludeKeys // redirectClassNameForKey // readPreference // includeReadPreference @@ -102,6 +103,13 @@ function RestQuery( this.keys = Array.from(new Set(keys)); break; } + case 'excludeKeys': { + const exclude = restOptions.excludeKeys + .split(',') + .filter(k => AlwaysSelectedKeys.indexOf(k) < 0); + this.excludeKeys = Array.from(new Set(exclude)); + break; + } case 'count': this.doCount = true; break; @@ -184,6 +192,9 @@ RestQuery.prototype.execute = function(executeOptions) { .then(() => { return this.handleIncludeAll(); }) + .then(() => { + return this.handleExcludeKeys(); + }) .then(() => { return this.runFind(executeOptions); }) @@ -705,6 +716,24 @@ RestQuery.prototype.handleIncludeAll = function() { }); }; +// Updates property `this.keys` to contain all keys but the ones unselected. +RestQuery.prototype.handleExcludeKeys = function() { + if (!this.excludeKeys) { + return; + } + if (this.keys) { + this.keys = this.keys.filter(k => !this.excludeKeys.includes(k)); + return; + } + return this.config.database + .loadSchema() + .then(schemaController => schemaController.getOneSchema(this.className)) + .then(schema => { + const fields = Object.keys(schema.fields); + this.keys = fields.filter(k => !this.excludeKeys.includes(k)); + }); +}; + // Augments this.response with data at the paths provided in this.include. RestQuery.prototype.handleInclude = function() { if (this.include.length == 0) { diff --git a/src/Routers/ClassesRouter.js b/src/Routers/ClassesRouter.js index 8c6094fe72..056a8df207 100644 --- a/src/Routers/ClassesRouter.js +++ b/src/Routers/ClassesRouter.js @@ -165,6 +165,7 @@ export class ClassesRouter extends PromiseRouter { 'order', 'count', 'keys', + 'excludeKeys', 'include', 'includeAll', 'redirectClassNameForKey', @@ -200,6 +201,9 @@ export class ClassesRouter extends PromiseRouter { if (typeof body.keys == 'string') { options.keys = body.keys; } + if (typeof body.excludeKeys == 'string') { + options.excludeKeys = body.excludeKeys; + } if (body.include) { options.include = String(body.include); }