Skip to content

Commit

Permalink
fix: Remove validation error handler option error from various meth…
Browse files Browse the repository at this point in the history
…ods of `Parse.Object` (#2445)

BREAKING CHANGE: Removes the error handler option `error` from `Parse.Object.set`, `Parse.Object.setACL`, `Parse.Object.unset`, `Parse.Role.setName` and instead throws on validation error. Previously, if the `error` option was set, the handler was invoked if a validation error occurred on `Parse.Object.set`, and if no handler was set, an error was thrown on `Parse.Object.save`. The new behavior is that an error is thrown at `Parse.Object.set`. For example, instead of using `Parse.Object.set(key, value, { error: ... })` wrap `Parse.Object.set(key, value)` into a `try/catch` block.
  • Loading branch information
dplewis authored Feb 16, 2025
1 parent fa34827 commit 52ddaee
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 106 deletions.
4 changes: 3 additions & 1 deletion integration/test/ParseACLTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ describe('Parse.ACL', () => {

it('acl must be valid', () => {
const user = new Parse.User();
assert.equal(user.setACL(`Ceci n'est pas un ACL.`), false);
expect(() => user.setACL(`Ceci n'est pas un ACL.`)).toThrow(
new Parse.Error(Parse.Error.OTHER_CAUSE, 'ACL must be a Parse ACL.')
);
});

it('can refresh object with acl', async () => {
Expand Down
63 changes: 45 additions & 18 deletions integration/test/ParseObjectTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -546,24 +546,20 @@ describe('Parse Object', () => {
});
});

it('cannot create invalid key names', done => {
it('cannot create invalid key names', async () => {
const error = new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: "foo^bar"`);
const item = new Parse.Object('Item');
assert(!item.set({ 'foo^bar': 'baz' }));
item.save({ 'foo^bar': 'baz' }).catch(e => {
assert.equal(e.code, Parse.Error.INVALID_KEY_NAME);
done();
});
expect(() => {
item.set({ 'foo^bar': 'baz' });
}).toThrow(error);
await expectAsync(item.save({ 'foo^bar': 'baz' })).toBeRejectedWith(error);
});

it('cannot use invalid key names in multiple sets', () => {
const item = new Parse.Object('Item');
assert(
!item.set({
foobar: 'baz',
'foo^bar': 'baz',
})
);
assert(!item.get('foobar'));
expect(() => {
item.set({ foobar: 'baz', 'foo^bar': 'baz' });
}).toThrow(new Parse.Error(Parse.Error.INVALID_KEY_NAME, `Invalid key name: "foo^bar"`));
});

it('can unset fields', done => {
Expand Down Expand Up @@ -1135,12 +1131,43 @@ describe('Parse Object', () => {
parent.set('children', [child1, child2]);
parent.set('bastard', child3);

expect(parent.save).toThrow();
let results = await new Parse.Query(Child).find();
assert.equal(results.length, 0);

await parent.save(null, { cascadeSave: true });
results = await new Parse.Query(Child).find();
const results = await new Parse.Query(Child).find();
assert.equal(results.length, 3);

parent.set('dead', true);
child1.set('dead', true);
await parent.save(null);
const rob = await new Parse.Query(Child).equalTo('name', 'rob').first();
expect(rob.get('dead')).toBe(true);

parent.set('lastname', 'stark');
child3.set('lastname', 'stark');
await parent.save(null, { cascadeSave: false });
const john = await new Parse.Query(Child).doesNotExist('lastname').first();
expect(john.get('lastname')).toBeUndefined();
});

it('can skip cascade (default true) saving as per request', async () => {
const Parent = Parse.Object.extend('Parent');
const Child = Parse.Object.extend('Child');

const parent = new Parent();
const child1 = new Child();
const child2 = new Child();
const child3 = new Child();

child1.set('name', 'rob');
child2.set('name', 'sansa');
child3.set('name', 'john');
parent.set('children', [child1, child2]);
parent.set('bastard', child3);

// cascadeSave option default true
await parent.save(null, {
/* cascadeSave: true */
});
const results = await new Parse.Query(Child).find();
assert.equal(results.length, 3);

parent.set('dead', true);
Expand Down
4 changes: 3 additions & 1 deletion src/ParseInstallation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class ParseInstallation extends ParseObject {
constructor(attributes?: AttributeMap) {
super('_Installation');
if (attributes && typeof attributes === 'object') {
if (!this.set(attributes)) {
try {
this.set(attributes || {});
} catch (_) {
throw new Error("Can't create an invalid Installation");
}
}
Expand Down
83 changes: 39 additions & 44 deletions src/ParseObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,12 @@ class ParseObject {
options = attributes as any;
}
}
if (toSet && !this.set(toSet, options)) {
throw new Error("Can't create an invalid Parse Object");
if (toSet) {
try {
this.set(toSet, options);
} catch (_) {
throw new Error("Can't create an invalid Parse Object");
}
}
}

Expand Down Expand Up @@ -733,9 +737,9 @@ class ParseObject {
* @param {(string|object)} value The value to give it.
* @param {object} options A set of options for the set.
* The only supported option is <code>error</code>.
* @returns {(ParseObject|boolean)} true if the set succeeded.
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
set(key: any, value?: any, options?: any): ParseObject | boolean {
set(key: any, value?: any, options?: any): this {
let changes = {};
const newOps = {};
if (key && typeof key === 'object') {
Expand Down Expand Up @@ -804,12 +808,9 @@ class ParseObject {

// Validate changes
if (!options.ignoreValidation) {
const validation = this.validate(newValues);
if (validation) {
if (typeof options.error === 'function') {
options.error(this, validation);
}
return false;
const validationError = this.validate(newValues);
if (validationError) {
throw validationError;
}
}

Expand All @@ -831,9 +832,9 @@ class ParseObject {
*
* @param {string} attr The string name of an attribute.
* @param options
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
unset(attr: string, options?: { [opt: string]: any }): ParseObject | boolean {
unset(attr: string, options?: { [opt: string]: any }): this {
options = options || {};
options.unset = true;
return this.set(attr, null, options);
Expand All @@ -845,9 +846,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param amount {Number} The amount to increment by (optional).
* @returns {(ParseObject|boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
increment(attr: string, amount?: number): ParseObject | boolean {
increment(attr: string, amount?: number): this {
if (typeof amount === 'undefined') {
amount = 1;
}
Expand All @@ -863,9 +864,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param amount {Number} The amount to decrement by (optional).
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
decrement(attr: string, amount?: number): ParseObject | boolean {
decrement(attr: string, amount?: number): this {
if (typeof amount === 'undefined') {
amount = 1;
}
Expand All @@ -881,9 +882,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param item {} The item to add.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
add(attr: string, item: any): ParseObject | boolean {
add(attr: string, item: any): this {
return this.set(attr, new AddOp([item]));
}

Expand All @@ -893,9 +894,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param items {Object[]} The items to add.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
addAll(attr: string, items: Array<any>): ParseObject | boolean {
addAll(attr: string, items: Array<any>): this {
return this.set(attr, new AddOp(items));
}

Expand All @@ -906,9 +907,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param item {} The object to add.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
addUnique(attr: string, item: any): ParseObject | boolean {
addUnique(attr: string, item: any): this {
return this.set(attr, new AddUniqueOp([item]));
}

Expand All @@ -919,9 +920,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param items {Object[]} The objects to add.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
addAllUnique(attr: string, items: Array<any>): ParseObject | boolean {
addAllUnique(attr: string, items: Array<any>): this {
return this.set(attr, new AddUniqueOp(items));
}

Expand All @@ -931,9 +932,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param item {} The object to remove.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
remove(attr: string, item: any): ParseObject | boolean {
remove(attr: string, item: any): this {
return this.set(attr, new RemoveOp([item]));
}

Expand All @@ -943,9 +944,9 @@ class ParseObject {
*
* @param attr {String} The key.
* @param items {Object[]} The object to remove.
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
removeAll(attr: string, items: Array<any>): ParseObject | boolean {
removeAll(attr: string, items: Array<any>): this {
return this.set(attr, new RemoveOp(items));
}

Expand Down Expand Up @@ -1099,7 +1100,7 @@ class ParseObject {
}
for (const key in attrs) {
if (!/^[A-Za-z][0-9A-Za-z_.]*$/.test(key)) {
return new ParseError(ParseError.INVALID_KEY_NAME);
return new ParseError(ParseError.INVALID_KEY_NAME, `Invalid key name: "${key}"`);
}
}
return false;
Expand All @@ -1124,10 +1125,10 @@ class ParseObject {
*
* @param {Parse.ACL} acl An instance of Parse.ACL.
* @param {object} options
* @returns {(ParseObject | boolean)} Whether the set passed validation.
* @returns {Parse.Object} Returns the object, so you can chain this call.
* @see Parse.Object#set
*/
setACL(acl: ParseACL, options?: any): ParseObject | boolean {
setACL(acl: ParseACL, options?: any): this {
return this.set('ACL', acl, options);
}

Expand All @@ -1154,9 +1155,9 @@ class ParseObject {
/**
* Clears all attributes on a model
*
* @returns {(ParseObject | boolean)}
* @returns {Parse.Object} Returns the object, so you can chain this call.
*/
clear(): ParseObject | boolean {
clear(): this {
const attributes = this.attributes;
const erasable = {};
let readonly = ['createdAt', 'updatedAt'];
Expand Down Expand Up @@ -1320,7 +1321,7 @@ class ParseObject {
* @returns {Promise} A promise that is fulfilled when the save
* completes.
*/
save(
async save(
arg1: undefined | string | { [attr: string]: any } | null,
arg2: SaveOptions | any,
arg3?: SaveOptions
Expand All @@ -1337,17 +1338,9 @@ class ParseObject {
attrs[arg1] = arg2;
options = arg3;
}

options = options || {};
if (attrs) {
let validationError;
options.error = (_, validation) => {
validationError = validation;
};
const success = this.set(attrs, options);
if (!success) {
return Promise.reject(validationError);
}
this.set(attrs, options);
}
const saveOptions = ParseObject._getRequestOptions(options);
const controller = CoreManager.getObjectController();
Expand Down Expand Up @@ -1985,7 +1978,9 @@ class ParseObject {
}

if (attributes && typeof attributes === 'object') {
if (!this.set(attributes || {}, options)) {
try {
this.set(attributes || {}, options);
} catch (_) {
throw new Error("Can't create an invalid Parse Object");
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/ParseSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ class ParseSession extends ParseObject {
constructor(attributes?: any) {
super('_Session');
if (attributes && typeof attributes === 'object') {
if (!this.set(attributes || {})) {
try {
this.set(attributes || {});
} catch (_) {
throw new Error("Can't create an invalid Session");
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/ParseUser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ class ParseUser extends ParseObject {
constructor(attributes?: AttributeMap) {
super('_User');
if (attributes && typeof attributes === 'object') {
if (!this.set(attributes || {})) {
try {
this.set(attributes || {});
} catch (_) {
throw new Error("Can't create an invalid Parse User");
}
}
Expand Down
Loading

0 comments on commit 52ddaee

Please sign in to comment.