From 03e80483a02875fd965d54318d26c68bd42980d0 Mon Sep 17 00:00:00 2001 From: jannyHou Date: Tue, 10 Sep 2019 16:41:05 -0400 Subject: [PATCH] feat(rest-crud): introduct id required option --- .../default-model-crud-rest.acceptance.ts | 54 +++++++++++++++ .../rest-crud/src/crud-rest.controller.ts | 66 ++++++++++++------- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts index 3167ff0a746b..b16f3cdb17b3 100644 --- a/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts +++ b/packages/rest-crud/src/__tests__/acceptance/default-model-crud-rest.acceptance.ts @@ -44,6 +44,22 @@ describe('CrudRestController for a simple Product model', () => { } } + @model() + class ProductWithId extends Entity { + @property({id: true, required: true}) + id: number; + + @property({required: true}) + name: string; + + @property() + description?: string; + + constructor(data: Partial) { + super(data); + } + } + let app: RestApplication; let repo: EntityCrudRepository; let client: Client; @@ -84,6 +100,31 @@ describe('CrudRestController for a simple Product model', () => { .send({id: 1, name: 'a name'}) .expect(422); }); + + it('creates a new Product - with id required', async () => { + const response = await client + .post('/productsWithId') + .send({id: 1, name: 'A pen'}) + // FIXME: POST should return 201 + // See https://github.com/strongloop/loopback-next/issues/788 + .expect(200); + + const created = response.body; + expect(created).to.containEql({id: 1, name: 'A pen'}); + expect(created) + .to.have.property('id') + .of.type('number'); + + const found = (await repo.find())[0]; + expect(toJSON(found)).to.deepEqual(created); + }); + + it('rejects request without `id` value', async () => { + await client + .post('/productsWithId') + .send({name: 'a name'}) + .expect(422); + }); }); describe('find', () => { @@ -294,14 +335,27 @@ describe('CrudRestController for a simple Product model', () => { 'id' >(Product, {basePath: '/products'}); + const CrudRestWithIdRequiredController = defineCrudRestController< + ProductWithId, + typeof Product.prototype.id, + 'id' + >(ProductWithId, {basePath: '/productsWithId', idRequired: true}); + class ProductController extends CrudRestController { constructor() { super(repo); } } + class ProductWithIdController extends CrudRestWithIdRequiredController { + constructor() { + super(repo); + } + } + app = new RestApplication({rest: givenHttpServerConfig()}); app.controller(ProductController); + app.controller(ProductWithIdController); await app.start(); client = createRestAppClient(app); diff --git a/packages/rest-crud/src/crud-rest.controller.ts b/packages/rest-crud/src/crud-rest.controller.ts index 4ad0dd34105c..a1708fe4dc45 100644 --- a/packages/rest-crud/src/crud-rest.controller.ts +++ b/packages/rest-crud/src/crud-rest.controller.ts @@ -70,11 +70,11 @@ export interface CrudRestController< */ readonly repository: EntityCrudRepository; - /** - * Implementation of the endpoint `POST /`. - * @param data Model data - */ - create(data: Omit): Promise; + // /** + // * Implementation of the endpoint `POST /`. + // * @param data Model data + // */ + // create(data: Omit): Promise; } /** @@ -99,6 +99,7 @@ export interface CrudRestControllerOptions { * The base path where to "mount" the controller. */ basePath: string; + idRequired?: boolean; } /** @@ -142,26 +143,12 @@ export function defineCrudRestController< }; @api({basePath: options.basePath, paths: {}}) - class CrudRestControllerImpl + class CrudRestControllerWithoutCreateImpl implements CrudRestController { constructor( public readonly repository: EntityCrudRepository, ) {} - @post('/', { - ...response.model(200, `${modelName} instance created`, modelCtor), - }) - async create( - @body(modelCtor, {exclude: modelCtor.getIdProperties() as (keyof T)[]}) - data: Omit, - ): Promise { - return this.repository.create( - // FIXME(bajtos) Improve repository API to support this use case - // with no explicit type-casts required - data as DataObject, - ); - } - @get('/', { ...response.array(200, `Array of ${modelName} instances`, modelCtor, { includeRelations: true, @@ -254,8 +241,43 @@ export function defineCrudRestController< } } - // See https://github.com/microsoft/TypeScript/issues/14607 - return CrudRestControllerImpl; + if (options && options.idRequired) { + class CrudRestControllerImpl extends CrudRestControllerWithoutCreateImpl { + @post('/', { + ...response.model(200, `${modelName} instance created`, modelCtor), + }) + async create( + @body(modelCtor) + data: T, + ): Promise { + return this.repository.create( + // FIXME(bajtos) Improve repository API to support this use case + // with no explicit type-casts required + data as DataObject, + ); + } + } + // See https://github.com/microsoft/TypeScript/issues/14607 + return CrudRestControllerImpl; + } else { + class CrudRestControllerImpl extends CrudRestControllerWithoutCreateImpl { + @post('/', { + ...response.model(200, `${modelName} instance created`, modelCtor), + }) + async create( + @body(modelCtor, {exclude: modelCtor.getIdProperties() as (keyof T)[]}) + data: Omit, + ): Promise { + return this.repository.create( + // FIXME(bajtos) Improve repository API to support this use case + // with no explicit type-casts required + data as DataObject, + ); + } + } + // See https://github.com/microsoft/TypeScript/issues/14607 + return CrudRestControllerImpl; + } } function getIdSchema(