Skip to content

Commit

Permalink
fix: relax constrain check to allow input containing constrained values
Browse files Browse the repository at this point in the history
Consider a Product constraint like `{categoryId: 1}` and a request
to update (patch) Product with the following data:

    {
      name: 'updated',
      categoryId: 1
    }

Before this change, such request would be incorrectly rejected.

With this change in place, such requests are allowed.
  • Loading branch information
bajtos committed Apr 16, 2019
1 parent 301ccb2 commit ec176db
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

import {expect} from '@loopback/testlab';
import {
FilterBuilder,
Filter,
Where,
constrainFilter,
constrainWhere,
constrainDataObject,
constrainDataObjects,
constrainFilter,
constrainWhere,
Entity,
Filter,
FilterBuilder,
Where,
} from '../../..';

describe('constraint utility functions', () => {
Expand Down Expand Up @@ -87,24 +87,58 @@ describe('constraint utility functions', () => {
});
});

it('throws error when the query changes field in constrain', () => {
it('throws error when the query changes field in constraint', () => {
const input = new Order({id: 1, description: 'order 1'});
const constraint: Partial<Order> = {id: 2};
expect(() => {
constrainDataObject(input, constraint);
}).to.throwError(/Property "id" cannot be changed!/);
});

it('allows constrained fields with the same values', () => {
const input = new Order({id: 2, description: 'order 1'});
const constraint: Partial<Order> = {id: 2};
const result = constrainDataObject(input, constraint);
expect(result).to.deepEqual(
new Order({
id: 2,
description: 'order 1',
}),
);
});
});

describe('constrainDataObjects', () => {
it('constrains array of data objects', () => {
const input = [
new Order({id: 1, description: 'order 1'}),
new Order({id: 2, description: 'order 2'}),
new Order({description: 'order 1'}),
new Order({description: 'order 2'}),
];
const constraint: Partial<Order> = {id: 3};
const result = constrainDataObjects(input, constraint);
expect(result[0]).to.containDeep(Object.assign({}, input[0], constraint));
expect(result[1]).to.containDeep(Object.assign({}, input[1], constraint));
});

it('throws error when the query changes field in constraint', () => {
const input = [new Order({id: 1, description: 'order 1'})];
const constraint: Partial<Order> = {id: 2};
expect(() => {
constrainDataObjects(input, constraint);
}).to.throwError(/Property "id" cannot be changed!/);
});

it('allows constrained fields with the same values', () => {
const input = [new Order({id: 2, description: 'order 1'})];
const constraint: Partial<Order> = {id: 2};
const result = constrainDataObjects(input, constraint);
expect(result).to.deepEqual([
new Order({
id: 2,
description: 'order 1',
}),
]);
});
});

/*---------------HELPERS----------------*/
Expand Down
20 changes: 9 additions & 11 deletions packages/repository/src/repositories/constraint-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {Filter, WhereBuilder, Where, FilterBuilder} from '../query';
import {AnyObject, DataObject} from '../common-types';
import {cloneDeep} from 'lodash';
import {AnyObject, DataObject} from '../common-types';
import {Entity} from '../model';
import {Filter, FilterBuilder, Where, WhereBuilder} from '../query';

/**
* A utility function which takes a filter and enforces constraint(s)
Expand Down Expand Up @@ -55,12 +55,16 @@ export function constrainDataObject<T extends Entity>(
): DataObject<T> {
const constrainedData = cloneDeep(originalData);
for (const c in constraint) {
if (constrainedData.hasOwnProperty(c))
if (constrainedData.hasOwnProperty(c)) {
// Known limitation: === does not work for objects such as ObjectId
if (originalData[c] === constraint[c]) continue;
throw new Error(`Property "${c}" cannot be changed!`);
}
(constrainedData as AnyObject)[c] = constraint[c];
}
return constrainedData;
}

/**
* A utility function which takes an array of model instance data and
* enforces constraint(s) on it
Expand All @@ -71,13 +75,7 @@ export function constrainDataObject<T extends Entity>(
*/
export function constrainDataObjects<T extends Entity>(
originalData: DataObject<T>[],
constraint: Partial<T>,
constraint: DataObject<T>,
): DataObject<T>[] {
const constrainedData = cloneDeep(originalData);
for (let obj of constrainedData) {
for (let prop in constraint) {
(obj as AnyObject)[prop] = constraint[prop];
}
}
return constrainedData;
return originalData.map(obj => constrainDataObject(obj, constraint));
}

0 comments on commit ec176db

Please sign in to comment.