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

Add API request validation #3

Merged
merged 7 commits into from Jan 13, 2021
Merged

Conversation

Dizzzmas
Copy link
Contributor

@Dizzzmas Dizzzmas commented Jan 12, 2021

Main changes

Due to quite a few changes to Yeoman generators in this PR it's a bit lengthy.

Main concepts regarding request validation are represented primarily in:

  • generators/app/templates/packages/backend/src/domain/game.ts
  • generators/app/templates/packages/backend/src/api/game/crud.ts
  • generators/app/templates/packages/backend/src/util/serialization.ts

What does this PR do?

Request bodies will now be validated using class-validator and lambda-middleware class-validator

We can't add validation as decorators. Instead we need to take an existing function and generate the Lambda handler from it.
My idea is to have all the functions for API in domain and the actual API views have only exports of modified functions.

Here's how an API view would look like with this setup:

import { Game, PaginatedResponse } from "demo-core"
import { GameSchemaLite } from "demo-core"
import { applyErrorHandlingAndValidation } from "../../util/serialization";
import { getById, create, updateById, deleteById, list } from "../../domain/game";


export const createHandler = applyErrorHandlingAndValidation<Game>(GameSchemaLite, create)

export const listHandler = applyErrorHandlingAndValidation<Game[]>(Game, list)

export const getByIdHandler = applyErrorHandlingAndValidation<Game>(Game, getById)

export const updateByIdHandler = applyErrorHandlingAndValidation<Game>(GameSchemaLite, updateById)

export const deleteByIdHandler = applyErrorHandlingAndValidation<Game>(Game, deleteById)

The PR also includes related changes to Yeoman generator and Jest tests.

@@ -1,12 +1,8 @@
import { db } from "../../db"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When trying to use absolute paths. Deployed code was failing with unable to find module error.

Hence backend uses relative paths now

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -89,7 +90,7 @@ describe('Test entity API', () => {
entityToUpdate = await repo.createQueryBuilder("game").getOneOrFail()

expect(entityToUpdate.name).not.toEqual(updatedEntity.name)
await updateById({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game
await updateByIdHandler({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type casting with as is kinda ugly here, but had to use it because of type signatures for lambdas looking like this:

(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2<Game>>

When testing we don't need and don't have things featured on APIGatewayProxyEventV2 and APIGatewayProxyResultV2 interfaces.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can updateByIdHandler have a return type of Game?
Should we have a helper that builds a fake APIGatewayProxyEventV2 object so we don't need to type cast?

@@ -1,44 +1,39 @@
listGames:
handler: src/api/game/crud.list
handler: src/api/game/crud.listHandler
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case you wonder what those Game models and API endpoints are, they're just examples the project is gonna get generated with.

I think it's gonna turn out helpful for whoever's using our template.

@Dizzzmas Dizzzmas changed the title Add API request validation to templates Add API request validation Jan 12, 2021
@Dizzzmas Dizzzmas self-assigned this Jan 12, 2021
Copy link
Member

@revmischa revmischa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice progress! Let's try to think about how to keep things simple like this, without requiring too much boilerplate or repeating ourselves for CRUD. It's a tricky balance.
Are there no existing libraries or frameworks we can be leveraging here?
If not, this would be a great project to try to promote as its own framework

@@ -89,7 +90,7 @@ describe('Test entity API', () => {
entityToUpdate = await repo.createQueryBuilder("game").getOneOrFail()

expect(entityToUpdate.name).not.toEqual(updatedEntity.name)
await updateById({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game
await updateByIdHandler({ pathParameters: { gameId: entityToUpdate.id }, body: JSON.stringify(updatedEntity) } as unknown as APIGatewayProxyEventV2) as Game
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can updateByIdHandler have a return type of Game?
Should we have a helper that builds a fake APIGatewayProxyEventV2 object so we don't need to type cast?


return {
items: games,
paginationData: getPaginationData(totalCount, pagesData),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like putting pagination metadata in headers
Don't name identifiers with "data"


const apiName = process.env.REACT_APP_USE_APP_WITHOUT_AUTH ? undefined : process.env.REACT_APP_API_NAME // name of the API Gateway API

const myInit = {
// OPTIONAL
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit confused what this is

generators/model/templates/domain.ts Show resolved Hide resolved
export const create = async (event: { body: <%= modelSchemaName %> }): Promise < APIGatewayProxyResultV2 <<%= capitalizedModelName %>>> => {

const conn = await db.getConnection()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think we need all these newlines

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add ESLint and Prettier this week, which will hopefully handle spacing

@revmischa revmischa requested review from a team and Jorjon and removed request for a team January 12, 2021 13:42
@valeriikundas valeriikundas self-requested a review January 13, 2021 14:16
Copy link

@valeriikundas valeriikundas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job!

generators/app/templates/packages/backend/index.d.ts Outdated Show resolved Hide resolved
generators/model/index.js Outdated Show resolved Hide resolved
generators/model/index.js Outdated Show resolved Hide resolved
generators/model/index.js Show resolved Hide resolved
@Dizzzmas Dizzzmas merged commit 86d7cb7 into main Jan 13, 2021
@Dizzzmas Dizzzmas deleted the api-serializaion-and-validation branch January 13, 2021 16:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants