Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(repository): extract helper rejectNavigationalPropertiesInData
Extract the code for rejecting navigational properites into a standalone
helper function that can be used from other repository implementations
too.

Signed-off-by: Miroslav Bajtoš <mbajtoss@gmail.com>
  • Loading branch information
bajtos committed May 11, 2020
1 parent fcace9e commit 4cc8eba
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 24 deletions.
39 changes: 39 additions & 0 deletions packages/repository/src/__tests__/unit/model/model.unit.ts
Expand Up @@ -5,9 +5,11 @@

import {expect} from '@loopback/testlab';
import {
BelongsToDefinition,
Entity,
HasManyDefinition,
ModelDefinition,
rejectNavigationalPropertiesInData,
RelationType,
STRING,
} from '../../../';
Expand Down Expand Up @@ -457,4 +459,41 @@ describe('model', () => {
firstName: 'Test User',
});
});

describe('rejectNavigationalPropertiesInData', () => {
class Order extends Entity {
static definition = new ModelDefinition('Order')
.addProperty('id', {type: 'string', id: true})
.addRelation(<BelongsToDefinition>{
name: 'customer',
type: RelationType.belongsTo,
targetsMany: false,
source: Order,
target: () => User,
keyFrom: 'customerId',
});

id: string;
customerId: string;

customer?: User;
}

it('accepts data with no navigational properties', () => {
rejectNavigationalPropertiesInData(Order, {id: '1'});
});

it('rejects data with a navigational property', () => {
expect(() =>
rejectNavigationalPropertiesInData(Order, {
id: '1',
customer: {
id: '2',
email: 'test@example.com',
firstName: 'a customer',
},
}),
).to.throw(/Navigational properties are not allowed in model data/);
});
});
});
36 changes: 35 additions & 1 deletion packages/repository/src/model.ts
Expand Up @@ -3,7 +3,7 @@
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

import {AnyObject, DataObject, Options} from './common-types';
import {AnyObject, DataObject, Options, PrototypeOf} from './common-types';
import {JsonSchema} from './index';
import {RelationMetadata} from './relations';
import {TypeResolver} from './type-resolver';
Expand Down Expand Up @@ -393,3 +393,37 @@ export class Event {
export type EntityData = DataObject<Entity>;

export type EntityResolver<T extends Entity> = TypeResolver<T, typeof Entity>;

/**
* Check model data for navigational properties linking to related models.
* Throw a descriptive error if any such property is found.
*
* @param modelClass Model constructor, e.g. `Product`.
* @param entityData Model instance or a plain-data object,
* e.g. `{name: 'pen'}`.
*/
export function rejectNavigationalPropertiesInData<M extends typeof Entity>(
modelClass: M,
data: DataObject<PrototypeOf<M>>,
) {
const def = modelClass.definition;
const props = def.properties;

for (const r in def.relations) {
const relName = def.relations[r].name;
if (!(relName in data)) continue;

let msg =
'Navigational properties are not allowed in model data ' +
`(model "${modelClass.modelName}" property "${relName}"), ` +
'please remove it.';

if (relName in props) {
msg +=
' The error might be invoked by belongsTo relations, please make' +
' sure the relation name is not the same as the property name.';
}

throw new Error(msg);
}
}
35 changes: 12 additions & 23 deletions packages/repository/src/repositories/legacy-juggler-bridge.ts
Expand Up @@ -11,12 +11,18 @@ import {
Command,
Count,
DataObject,
DeepPartial,
NamedParameters,
Options,
PositionalParameters,
} from '../common-types';
import {EntityNotFoundError} from '../errors';
import {Entity, Model, PropertyType} from '../model';
import {
Entity,
Model,
PropertyType,
rejectNavigationalPropertiesInData,
} from '../model';
import {Filter, FilterExcludingWhere, Inclusion, Where} from '../query';
import {
BelongsToAccessor,
Expand Down Expand Up @@ -109,8 +115,8 @@ export class DefaultCrudRepository<

/**
* Constructor of DefaultCrudRepository
* @param entityClass - Legacy entity class
* @param dataSource - Legacy data source
* @param entityClass - LoopBack 4 entity class
* @param dataSource - Legacy juggler data source
*/
constructor(
// entityClass should have type "typeof T", but that's not supported by TSC
Expand Down Expand Up @@ -590,27 +596,10 @@ export class DefaultCrudRepository<
const data: AnyObject =
typeof entity.toJSON === 'function' ? entity.toJSON() : {...entity};
*/
const data: DeepPartial<R> = new this.entityClass(entity);

rejectNavigationalPropertiesInData(this.entityClass, data);

const data: AnyObject = new this.entityClass(entity);

const def = this.entityClass.definition;
const props = def.properties;
for (const r in def.relations) {
const relName = def.relations[r].name;
if (relName in data) {
let invalidNameMsg = '';
if (relName in props) {
invalidNameMsg =
` The error might be invoked by belongsTo relations, please make sure the relation name is not the same as` +
` the property name.`;
}
throw new Error(
`Navigational properties are not allowed in model data (model "${this.entityClass.modelName}"` +
` property "${relName}"), please remove it.` +
invalidNameMsg,
);
}
}
return data;
}

Expand Down

0 comments on commit 4cc8eba

Please sign in to comment.