Skip to content

Commit

Permalink
fix: return error response if invalid key passed in where clause
Browse files Browse the repository at this point in the history
Signed-off-by: Muhammad Aaqil <[email protected]>
  • Loading branch information
aaqilniz committed Aug 11, 2024
1 parent 581bf5a commit e47f1a4
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import {
InclusionResolver,
} from '../../../relations';
import {CrudConnectorStub} from '../crud-connector.stub';
import {AnyObject} from '@loopback/filter/src/types';
const TransactionClass = require('loopback-datasource-juggler').Transaction;

describe('legacy loopback-datasource-juggler', () => {
Expand Down Expand Up @@ -286,6 +287,20 @@ describe('DefaultCrudRepository', () => {
await repo.find(filter);
expect(filter).to.deepEqual(originalFilter);
});
it('invalid key in where clause throws error', async () => {
const repo = new DefaultCrudRepository(Note, ds);
const filter = {where: {noTtile: 't1'}};
await repo.createAll([
{title: 't1', content: 'c1'},
{title: 't2', content: 'c2'},
]);
// let response = await (repo as AnyObject).find(filter);
await expect((repo as AnyObject).find(filter)).to.be.rejectedWith({
code: 'INVALID_WHERE_FILTER',
statusCode: 400,
message: 'Invalid "filter.include" entries: "noTtile"',
});
});
});

describe('findOne', () => {
Expand Down
62 changes: 62 additions & 0 deletions packages/repository/src/relations/relation.helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
Options,
Where,
} from '..';
import {ModelProperties} from 'loopback-datasource-juggler';
const debug = debugFactory('loopback:repository:relation-helpers');

/**
Expand Down Expand Up @@ -178,6 +179,67 @@ export async function includeRelatedModels<

return result;
}

/**
* Validate if where clause as properties that exists in the table
*
* @param targetRepository - The target repository where the related model instances are found
* @param where - Where clause from url
*/
export async function validateWhere<
Target extends Entity,
TargetRelations extends object,
>(
targetRepository: EntityCrudRepository<Target, unknown, TargetRelations>,
where: Where,
): Promise<Error | undefined> {
const definition = targetRepository.entityClass.definition;
const invalidWhereFieldsSet = validateWhereClause(where, definition);
const invalidWhereFields = Array.from(invalidWhereFieldsSet);
if (invalidWhereFields.length) {
const msg =
'Invalid "filter.include" entries: ' +
invalidWhereFields
.map(invalidWhereField => JSON.stringify(invalidWhereField))
.join('; ');
const err = new Error(msg);
Object.assign(err, {
code: 'INVALID_WHERE_FILTER',
statusCode: 400,
});
return err;
}
}
/**
* A wrapper function to validate where clause
*
* @param where - Where clause from url
* @param definition - Model Definition
* @param invalidWhereFields - set of invalid fields from where clause
*/
function validateWhereClause(
where: Where,
definition: ModelProperties,
invalidWhereFields = new Set<string>(),
): Set<string> {
const {properties} = definition;
for (const key in where) {
if (key === 'and' || key === 'or') {
const clauses = (where as AnyObject)[key];
if (Array.isArray(clauses)) {
for (let i = 0, n = clauses.length; i < n; i++) {
validateWhereClause(clauses[i], properties, invalidWhereFields);
}
}
} else {
const p = properties[key];
if (p == null) {
invalidWhereFields.add(key);
}
}
}
return invalidWhereFields;
}
/**
* Checks if the resolver of the inclusion relation is registered
* in the inclusionResolver of the target repository
Expand Down
17 changes: 17 additions & 0 deletions packages/repository/src/repositories/legacy-juggler-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {
createHasOneRepositoryFactory,
createReferencesManyAccessor,
includeRelatedModels,
validateWhere,
} from '../relations';
import {IsolationLevel, Transaction} from '../transaction';
import {isTypeResolver, resolveType} from '../type-resolver';
Expand Down Expand Up @@ -518,6 +519,11 @@ export class DefaultCrudRepository<
filter?: Filter<T>,
options?: Options,
): Promise<(T & Relations)[]> {
const where = filter?.where;
if (where) {
const validWhere = await this.validateWhere(where);
if (validWhere) throw validWhere;
}
const include = filter?.include;
const models = await ensurePromise(
this.modelClass.find(this.normalizeFilter(filter), options),
Expand Down Expand Up @@ -775,6 +781,17 @@ export class DefaultCrudRepository<
return includeRelatedModels<T, Relations>(this, entities, include, options);
}

/**
* Returns model instances that include related models of this repository
* that have a registered resolver.
*
* @param entities - An array of entity instances or data
* @param include -Inclusion filter
* @param options - Options for the operations
*/
protected async validateWhere(where: Where): Promise<Error | undefined> {
return validateWhere<T, Relations>(this, where);
}
/**
* This function works as a persist hook.
* It converts an entity from the CRUD operations' caller
Expand Down

0 comments on commit e47f1a4

Please sign in to comment.