Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit schema with all model properties optional + allow partial updates via PATCH #3199

Merged
merged 5 commits into from
Jun 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions docs/site/Controller-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,17 @@ export class TodoController {
},
})
async updateAll(
@requestBody() data: Todo,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>
@param.query.object('where', getWhereSchemaFor(Todo)) where?: Where<Todo>,
): Promise<number> {
return await this.todoRepository.updateAll(data, where);
return await this.todoRepository.updateAll(todo, where);
}

@get('/todos/{id}', {
Expand All @@ -196,9 +203,16 @@ export class TodoController {
})
async updateById(
@param.path.number('id') id: number,
@requestBody() data: Todo,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>,
): Promise<void> {
await this.todoRepository.updateById(id, data);
await this.todoRepository.updateById(id, todo);
}

@del('/todos/{id}', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,14 @@ export class TodoListTodoController {
})
async patch(
@param.path.number('id') id: number,
@requestBody() todo: Partial<Todo>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>
@param.query.object('where', getWhereSchemaFor(Todo)) where?: Where<Todo>,
): Promise<Count> {
return await this.todoListRepo.todos(id).patch(todo, where);
Expand Down
19 changes: 17 additions & 2 deletions examples/express-composition/src/controllers/note.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
del,
get,
getFilterSchemaFor,
getModelSchemaRef,
getWhereSchemaFor,
param,
patch,
Expand Down Expand Up @@ -84,7 +85,14 @@ export class NoteController {
},
})
async updateAll(
@requestBody() note: Note,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Note, {partial: true}),
},
},
})
note: Partial<Note>,
@param.query.object('where', getWhereSchemaFor(Note)) where?: Where<Note>,
): Promise<Count> {
return await this.noteRepository.updateAll(note, where);
Expand All @@ -111,7 +119,14 @@ export class NoteController {
})
async updateById(
@param.path.number('id') id: number,
@requestBody() note: Note,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Note, {partial: true}),
},
},
})
note: Partial<Note>,
): Promise<void> {
await this.noteRepository.updateById(id, note);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import {
del,
get,
getModelSchemaRef,
getWhereSchemaFor,
param,
patch,
Expand Down Expand Up @@ -71,7 +72,14 @@ export class TodoListTodoController {
})
async patch(
@param.path.number('id') id: number,
@requestBody() todo: Partial<Todo>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>,
@param.query.object('where', getWhereSchemaFor(Todo)) where?: Where<Todo>,
): Promise<Count> {
return await this.todoListRepo.todos(id).patch(todo, where);
Expand Down
22 changes: 18 additions & 4 deletions examples/todo-list/src/controllers/todo-list.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,18 @@ export class TodoListController {
},
})
async updateAll(
@requestBody() obj: Partial<TodoList>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TodoList, {partial: true}),
},
},
})
todoList: Partial<TodoList>,
@param.query.object('where', getWhereSchemaFor(TodoList))
where?: Where<TodoList>,
): Promise<Count> {
return await this.todoListRepository.updateAll(obj, where);
return await this.todoListRepository.updateAll(todoList, where);
}

@get('/todo-lists/{id}', {
Expand Down Expand Up @@ -124,9 +131,16 @@ export class TodoListController {
})
async updateById(
@param.path.number('id') id: number,
@requestBody() obj: TodoList,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(TodoList, {partial: true}),
},
},
})
todoList: Partial<TodoList>,
): Promise<void> {
await this.todoListRepository.updateById(id, obj);
await this.todoListRepository.updateById(id, todoList);
}

@del('/todo-lists/{id}', {
Expand Down
9 changes: 8 additions & 1 deletion examples/todo-list/src/controllers/todo.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,14 @@ export class TodoController {
})
async updateTodo(
@param.path.number('id') id: number,
@requestBody() todo: Todo,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>,
): Promise<void> {
await this.todoRepo.updateById(id, todo);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ describe('TodoApplication', () => {

it('updates the todo by ID ', async () => {
const updatedTodo = givenTodo({
title: 'DO SOMETHING AWESOME',
isComplete: true,
});
await client
Expand Down
10 changes: 9 additions & 1 deletion examples/todo/src/controllers/todo.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
del,
get,
getFilterSchemaFor,
getModelSchemaRef,
param,
patch,
post,
Expand Down Expand Up @@ -103,7 +104,14 @@ export class TodoController {
})
async updateTodo(
@param.path.number('id') id: number,
@requestBody() todo: Todo,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(Todo, {partial: true}),
},
},
})
todo: Partial<Todo>,
): Promise<void> {
await this.todoRepo.updateById(id, todo);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
param,
get,
getFilterSchemaFor,
getModelSchemaRef,
getWhereSchemaFor,
patch,
put,
Expand Down Expand Up @@ -78,7 +79,14 @@ export class <%= className %>Controller {
},
})
async updateAll(
@requestBody() <%= modelVariableName %>: <%= modelName %>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(<%= modelName %>, {partial: true}),
},
},
})
<%= modelVariableName %>: <%= modelName %>,
@param.query.object('where', getWhereSchemaFor(<%= modelName %>)) where?: Where<<%= modelName %>>,
): Promise<Count> {
return await this.<%= repositoryNameCamel %>.updateAll(<%= modelVariableName %>, where);
Expand All @@ -105,7 +113,14 @@ export class <%= className %>Controller {
})
async updateById(
@param.path.<%= idType %>('id') id: <%= idType %>,
@requestBody() <%= modelVariableName %>: <%= modelName %>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(<%= modelName %>, {partial: true}),
},
},
})
<%= modelVariableName %>: <%= modelName %>,
): Promise<void> {
await this.<%= repositoryNameCamel %>.updateById(id, <%= modelVariableName %>);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import {
del,
get,
getModelSchemaRef,
getWhereSchemaFor,
param,
patch,
Expand Down Expand Up @@ -69,7 +70,14 @@ export class <%= controllerClassName %> {
})
async patch(
@param.path.<%= sourceModelPrimaryKeyType %>('id') id: <%= sourceModelPrimaryKeyType %>,
@requestBody() <%= targetModelRequestBody %>: Partial<<%= targetModelClassName %>>,
@requestBody({
content: {
'application/json': {
schema: getModelSchemaRef(<%= targetModelClassName %>, {partial: true}),
},
},
})
<%= targetModelRequestBody %>: Partial<<%= targetModelClassName %>>,
@param.query.object('where', getWhereSchemaFor(<%= targetModelClassName %>)) where?: Where<<%= targetModelClassName %>>,
): Promise<Count> {
return await this.<%= paramSourceRepository %>.<%= relationPropertyName %>(id).patch(<%= targetModelRequestBody %>, where);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ function checkRestCrudContents() {
/'200': {/,
/description: 'ProductReview PATCH success count'/,
/content: {'application\/json': {schema: CountSchema}},\s{1,}},\s{1,}},\s{1,}}\)/,
/async updateAll\(\s{1,}\@requestBody\(\) productReview: ProductReview,\s{1,} @param\.query\.object\('where', getWhereSchemaFor\(ProductReview\)\) where\?: Where<ProductReview>(|,\s+)\)/,
/async updateAll\(\s+\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(ProductReview, {partial: true}\),\s+},\s+},\s+}\)\s+productReview: ProductReview,\s{1,} @param\.query\.object\('where', getWhereSchemaFor\(ProductReview\)\) where\?: Where<ProductReview>(|,\s+)\)/,
];
patchUpdateAllRegEx.forEach(regex => {
assert.fileContent(expectedFile, regex);
Expand All @@ -312,7 +312,7 @@ function checkRestCrudContents() {
/responses: {/,
/'204': {/,
/description: 'ProductReview PATCH success'/,
/async updateById\(\s{1,}\@param.path.number\('id'\) id: number,\s{1,}\@requestBody\(\) productReview: ProductReview,\s+\)/,
/async updateById\(\s+\@param.path.number\('id'\) id: number,\s+\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(ProductReview, {partial: true}\),\s+},\s+},\s+}\)\s+productReview: ProductReview,\s+\)/,
];
patchUpdateByIdRegEx.forEach(regex => {
assert.fileContent(expectedFile, regex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,8 @@ context('check if the controller file created ', () => {
/description: 'Customer.Order PATCH success count',\n/,
/content: { 'application\/json': { schema: CountSchema } },\n/,
/},\n {4}},\n {2}}\)\n {2}async patch\(\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}\@requestBody\(\) order: Partial<Order>,\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}/,
/\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(Order, {partial: true}\),\s+},\s+},\s+}\)\s+order: Partial<Order>,\n/,
/\@param\.query\.object\('where', getWhereSchemaFor\(Order\)\) where\?: Where<Order>,\n/,
/\): Promise<Count> {\n/,
/return await this\.customerRepository\.orders\(id\).patch\(order, where\);\n {2}}\n/,
Expand All @@ -505,7 +506,8 @@ context('check if the controller file created ', () => {
/description: 'CustomerClass.OrderClass PATCH success count',\n/,
/content: { 'application\/json': { schema: CountSchema } },\n/,
/},\n {4}},\n {2}}\)\n {2}async patch\(\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}\@requestBody\(\) orderClass: Partial<OrderClass>,\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}/,
/\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClass, {partial: true}\),\s+},\s+},\s+}\)\s+orderClass: Partial<OrderClass>,\n/,
/\@param\.query\.object\('where', getWhereSchemaFor\(OrderClass\)\) where\?: Where<OrderClass>,\n/,
/\): Promise<Count> {\n/,
/return await this\.customerClassRepository\.orderClasses\(id\)\.patch\(orderClass, where\);\n {2}}\n/,
Expand All @@ -516,7 +518,8 @@ context('check if the controller file created ', () => {
/description: 'CustomerClassType.OrderClassType PATCH success count',\n/,
/content: { 'application\/json': { schema: CountSchema } },\n/,
/},\n {4}},\n {2}}\)\n {2}async patch\(\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}\@requestBody\(\) orderClassType: Partial<OrderClassType>,\n/,
/\@param\.path\.number\('id'\) id: number,\n {4}/,
/\@requestBody\({\s+content: {\s+'application\/json': {\s+schema: getModelSchemaRef\(OrderClassType, {partial: true}\),\s+},\s+},\s+}\)\s+orderClassType: Partial<OrderClassType>,\n/,
/\@param\.query\.object\('where', getWhereSchemaFor\(OrderClassType\)\) where\?: Where<OrderClassType>,\n/,
/\): Promise<Count> {\n/,
/return await this\.customerClassTypeRepository\.orderClassTypes\(id\).patch\(orderClassType, where\);\n {2}}\n/,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -910,5 +910,27 @@ describe('build-schema', () => {
title: 'Category',
});
});

it('emits all properties as optional when the option "partial" is set', () => {
@model()
class Product extends Entity {
@property({id: true, required: true})
id: number;

@property({required: true})
name: string;

@property()
optionalDescription: string;
}

const originalSchema = getJsonSchema(Product);
expect(originalSchema.required).to.deepEqual(['id', 'name']);
expect(originalSchema.title).to.equal('Product');

const partialSchema = getJsonSchema(Product, {partial: true});
expect(partialSchema.required).to.equal(undefined);
expect(partialSchema.title).to.equal('ProductPartial');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@loopback/repository';
import {expect} from '@loopback/testlab';
import {
buildModelCacheKey,
getNavigationalPropertyForRelation,
metaToJsonProperty,
stringTypeToWrapper,
Expand Down Expand Up @@ -214,4 +215,30 @@ describe('build-schema', () => {
).to.throw(/targetsMany attribute missing for Test/);
});
});

describe('buildModelCacheKey', () => {
it('returns "modelOnly" when no options were provided', () => {
const key = buildModelCacheKey();
expect(key).to.equal('modelOnly');
});

it('returns "modelWithRelations" when a single option "includeRelations" is set', () => {
const key = buildModelCacheKey({includeRelations: true});
expect(key).to.equal('modelWithRelations');
});

it('returns "partial" when a single option "partial" is set', () => {
const key = buildModelCacheKey({partial: true});
expect(key).to.equal('partial');
});

it('returns concatenated option names otherwise', () => {
const key = buildModelCacheKey({
// important: object keys are defined in reverse order
partial: true,
includeRelations: true,
});
expect(key).to.equal('includeRelations+partial');
});
});
});
Loading