Skip to content

Commit

Permalink
feat: request body validation
Browse files Browse the repository at this point in the history
  • Loading branch information
sahachide committed Apr 24, 2021
1 parent 21e44b5 commit 690a1ce
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 30 deletions.
35 changes: 34 additions & 1 deletion src/http/Context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { IncomingMessage, ServerResponse } from 'http'
import type { IncomingParams, ParsedBody, QueryString } from '../types/interfaces'
import type {
IncomingParams,
ParsedBody,
QueryString,
RequestValidationError,
Route,
} from '../types/interfaces'

import { BodyParser } from './BodyParser'
import { Cookie } from './Cookie'
Expand All @@ -17,12 +23,15 @@ export class Context {
res: Response
error: ResponseError
}
private requestBodyValidationErrors: RequestValidationError[] = []
private isBuild: boolean = false
private isReqBodyValid: boolean = true

public async build(
request: IncomingMessage,
response: ServerResponse,
params: IncomingParams,
route: Route,
): Promise<void> {
if (this.isBuild) {
return
Expand All @@ -39,6 +48,22 @@ export class Context {
body = await bodyParser.parse(request)
}

if (typeof route.validationSchema !== 'undefined') {
const validationResult = route.validationSchema.validate(body.fields)

if (validationResult.error) {
this.isReqBodyValid = false
this.requestBodyValidationErrors = validationResult.error.details.map((error) => {
return {
message: error.message,
path: error.path,
}
})
} else {
body.fields = validationResult.value as JsonObject
}
}

const cookie = config.web?.cookie?.enable ? new Cookie(request.headers) : null
const req = new Request(request, body, params)
const res = new Response(response, req, cookie)
Expand Down Expand Up @@ -88,4 +113,12 @@ export class Context {
get error(): ResponseError {
return this.container.error
}

get isValid(): boolean {
return this.isReqBodyValid
}

get validationErrors(): RequestValidationError[] {
return this.requestBodyValidationErrors
}
}
29 changes: 11 additions & 18 deletions src/http/IncomingRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class IncomingRequest {
requestConfig: RequestConfig,
securityProviders: SecurityProviders,
): Promise<void> {
const context = await this.buildContext(req, res, params)
const context = await this.buildContext(req, res, params, route)
const authentication = await this.authenticate(route.authProvider, context, securityProviders)

if (authentication.isAuth) {
Expand All @@ -29,11 +29,15 @@ export class IncomingRequest {
}
}

await this.validate(context, route)
if (context.isValid) {
const handler = factory.build(context, requestConfig, route)

const handler = factory.build(context, requestConfig, route)

await handler.run()
await handler.run()
} else {
context.error.badData('Bad Data', {
errors: context.validationErrors,
})
}
} else if (authentication.securityProvider) {
await authentication.securityProvider.forbidden(context)
} else {
Expand All @@ -45,26 +49,15 @@ export class IncomingRequest {
req: IncomingMessage,
res: ServerResponse,
params: IncomingParams,
route: Route,
): Promise<Context> {
const context = new Context()

await context.build(req, res, params)
await context.build(req, res, params, route)

return context
}

protected async validate(context: Context, route: Route): Promise<void> {
if (typeof route.validationSchema === 'undefined') {
return
}

const test = route.validationSchema.validate(context.req.body)
// console.log(context.req.body)

// console.log(test)
// console.log(test.error.details)
}

protected async authenticate(
authProvider: unknown,
context: Context,
Expand Down
22 changes: 13 additions & 9 deletions src/types/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,6 @@ export interface ControllerDeclaration {
module: Class
routes: Route[]
}

export interface Route {
method: HTTPMethod
path: string
controllerMethod?: string
authProvider?: string
validationSchema?: ValidationSchema
}

export interface CommonJSZenModule<T> {
[key: string]: Class<T>
}
Expand Down Expand Up @@ -249,6 +240,11 @@ export interface RegistryFactories {
email: EmailFactory
}

export interface RequestValidationError {
message: string
path: (string | number)[]
}

export interface RequestConfigController {
type: REQUEST_TYPE.CONTROLLER
controllerKey: string
Expand All @@ -269,6 +265,14 @@ export interface RequestConfigSecurity {
provider: SecurityProvider
}

export interface Route {
method: HTTPMethod
path: string
controllerMethod?: string
authProvider?: string
validationSchema?: ValidationSchema
}

// ---- S

export interface SecurityProviderAuthorizeResponse {
Expand Down
2 changes: 1 addition & 1 deletion src/types/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export type Email = EmailFactory

export type EmailTemplates = Map<string, string>

export type ErrorResponseData = JsonObject | JsonArray
export type ErrorResponseData = Record<string, unknown> | JsonArray

// ---- F
// ---- G
Expand Down
28 changes: 27 additions & 1 deletion test/fixtures/testapp/src/controller/RequestTestController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,16 @@
import { Context, Controller, get, params, query, request, Request } from '../../../../../src'
import {
Context,
Controller,
get,
params,
post,
query,
request,
Request,
validate,
validation,
body,
} from '../../../../../src'

import type { QueryString } from '../../../../../src/types/interfaces'

Expand Down Expand Up @@ -71,4 +83,18 @@ export default class extends Controller {
setterTests,
}
}

@post('/request-test-validate')
@validation(
validate.object({
foo: validate.string().required().alphanum().min(3).max(30),
bar: validate.string().required().alphanum().min(3).max(30),
}),
)
public validationTest(
@body
{ foo, bar }: { foo: string; bar: string },
) {
return { foo, bar }
}
}
28 changes: 28 additions & 0 deletions test/http/request.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,32 @@ describe('Request', () => {
accept: 'application/json',
})
})

it('validates a POST request body', async () => {
const body = {
foo: 'bar',
bar: 'baz',
}

await supertest(app.nodeServer)
.post('/request-test-validate')
.send(body)
.set('Accept', 'application/json')
.expect(201)
.expect('Content-Type', /json/)
.expect(body)
})

it('validates a POST request body (fail)', async () => {
const body = {
foo: 'bar',
}

await supertest(app.nodeServer)
.post('/request-test-validate')
.send(body)
.set('Accept', 'application/json')
.expect(422)
.expect('Content-Type', /json/)
})
})

0 comments on commit 690a1ce

Please sign in to comment.