Skip to content
Closed
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
3 changes: 2 additions & 1 deletion packages/rest/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"src/providers/reject.provider.ts",
"src/providers/send.provider.ts",
"src/router/routing-table.ts",
"src/validation/request-body.validator.ts"
"src/validation/request-body.validator.ts",
"src/validation/request-query.validator.ts"
],
"codeSectionDepth": 4
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
post,
Request,
requestBody,
RequestBody,
ValueWithSchema,
RestApplication,
} from '../../..';

Expand Down Expand Up @@ -97,10 +97,10 @@ class MultipartFormDataBodyParser implements BodyParser {
return mediaType.startsWith(FORM_DATA);
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
const storage = multer.memoryStorage();
const upload = multer({storage});
return new Promise<RequestBody>((resolve, reject) => {
return new Promise<ValueWithSchema>((resolve, reject) => {
// tslint:disable-next-line:no-any
upload.any()(request, {} as any, err => {
if (err) reject(err);
Expand Down
4 changes: 2 additions & 2 deletions packages/rest/src/__tests__/unit/body-parser.unit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
JsonBodyParser,
RawBodyParser,
Request,
RequestBody,
ValueWithSchema,
RequestBodyParser,
RequestBodyParserOptions,
StreamBodyParser,
Expand Down Expand Up @@ -254,7 +254,7 @@ describe('body parser', () => {
describe('x-parser extension', () => {
let spec: OperationObject;
let req: Request;
let requestBody: RequestBody;
let requestBody: ValueWithSchema;

it('skips body parsing', async () => {
await loadRequestBodyWithXStream('stream');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('coerce object param - optional', function() {
test(OPTIONAL_ANY_OBJECT, {key: 'value'}, {key: 'value'});
test(OPTIONAL_ANY_OBJECT, undefined, undefined);
test(OPTIONAL_ANY_OBJECT, '', undefined);
test(OPTIONAL_ANY_OBJECT, 'null', null);
test(OPTIONAL_ANY_OBJECT, {key: 'null'}, {key: 'null'});
});

context('nested values are not coerced', () => {
Expand Down
193 changes: 193 additions & 0 deletions packages/rest/src/__tests__/unit/request.query.validator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {expect} from '@loopback/testlab';
import {validateRequestQuery} from '../../validation/request-query.validator';
import {RestHttpErrors} from '../../';
import {aBodySpec} from '../helpers';
import {
ReferenceObject,
SchemaObject,
SchemasObject,
} from '@loopback/openapi-v3-types';

const INVALID_MSG = RestHttpErrors.INVALID_REQUEST_QUERY_MESSAGE;

const PING_SCHEMA = {
properties: {
pageSize: {type: 'integer', minimum: 0, maximum: 100, multipleOf: 5},
pageNumber: {type: 'number', minimum: 10, maximum: 200, multipleOf: 3},
pageBool: {type: 'boolean'},
pageName: {type: 'string', maxLength: 5, minLength: 1, pattern: '[abc]+'},
},
required: ['pageSize'],
};

describe('validateRequestQuery', () => {
it('accepts valid data omitting optional properties', () => {
validateRequestQuery(
{value: {pageSize: 5}, schema: PING_SCHEMA},
aBodySpec(PING_SCHEMA),
);
});

it('rejects data missing a required property', () => {
const details: RestHttpErrors.ValidationErrorDetails[] = [
{
path: '',
code: 'required',
message: "should have required property 'pageSize'",
info: {missingProperty: 'pageSize'},
},
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
description: 'missing required "pageSize"',
},
PING_SCHEMA,
);
});

it('rejects data containing values of a wrong type', () => {
const details: RestHttpErrors.ValidationErrorDetails[] = [
{
path: '.pageBool',
code: 'type',
message: 'should be boolean',
info: {type: 'boolean'},
},
];
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
pageSize: 5,
pageBool: 1111,
},
PING_SCHEMA,
);
});

it('rejects invalid values for number properties', () => {
const details: RestHttpErrors.ValidationErrorDetails[] = [
{
path: '.pageNumber',
code: 'type',
message: 'should be number',
info: {type: 'number'},
},
];
const schema: SchemaObject = {
properties: {
pageNumber: {type: 'number'},
},
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{pageNumber: 'string value'},
schema,
);
});

it('rejects invalid values for number properties', () => {
const details: RestHttpErrors.ValidationErrorDetails[] = [
{
path: '.pageNumber',
code: 'type',
message: 'should be number',
info: {type: 'number'},
},
];
const schema: SchemaObject = {
properties: {
pageNumber: {type: 'number'},
},
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{pageNumber: 'string value'},
schema,
);
});

it('rejects invalid values for number properties', () => {
const details: RestHttpErrors.ValidationErrorDetails[] = [
{
path: '.pageSize',
code: 'type',
message: 'should be number',
info: {type: 'number'},
},
{
path: '.pageNumber',
code: 'type',
message: 'should be number',
info: {type: 'number'},
},
{
path: '.pageBool',
code: 'type',
message: 'should be boolean',
info: {type: 'boolean'},
},
{
path: '.pageName',
code: 'type',
message: 'should be string',
info: {type: 'string'},
},
];
const schema: SchemaObject = {
properties: {
pageSize: {type: 'number'},
pageNumber: {type: 'number'},
pageBool: {type: 'boolean'},
pageName: {type: 'string'},
},
};
verifyValidationRejectsInputWithError(
INVALID_MSG,
'VALIDATION_FAILED',
details,
{
pageSize: 'string value',
pageNumber: 'string value',
pageBool: 1111,
pageName: 123,
},
schema,
);
});
});

// ----- HELPERS ----- /

function verifyValidationRejectsInputWithError(
expectedMessage: string,
expectedCode: string,
expectedDetails: RestHttpErrors.ValidationErrorDetails[] | undefined,
query: object | null,
schema: SchemaObject | ReferenceObject,
schemas?: SchemasObject,
required?: boolean,
) {
try {
validateRequestQuery(
{value: query, schema},
aBodySpec(schema, {required}),
schemas,
);
throw new Error(
"expected Function { name: 'validateRequestQuery' } to throw exception",
);
} catch (err) {
expect(err.message).to.equal(expectedMessage);
expect(err.code).to.equal(expectedCode);
expect(err.details).to.deepEqual(expectedDetails);
}
}
4 changes: 2 additions & 2 deletions packages/rest/src/body-parsers/body-parser.json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
invokeBodyParserMiddleware,
builtinParsers,
} from './body-parser.helpers';
import {BodyParser, RequestBody} from './types';
import {BodyParser, ValueWithSchema} from './types';
import {sanitizeJsonParse} from '../parse-json';

export class JsonBodyParser implements BodyParser {
Expand All @@ -34,7 +34,7 @@ export class JsonBodyParser implements BodyParser {
return !!is(mediaType, '*/json', '*/*+json');
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
let body = await invokeBodyParserMiddleware(this.jsonParser, request);
// https://github.com/expressjs/body-parser/blob/master/lib/types/json.js#L71-L76
const contentLength = request.get('content-length');
Expand Down
4 changes: 2 additions & 2 deletions packages/rest/src/body-parsers/body-parser.raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
invokeBodyParserMiddleware,
builtinParsers,
} from './body-parser.helpers';
import {BodyParser, RequestBody} from './types';
import {BodyParser, ValueWithSchema} from './types';

/**
* Parsing the request body into Buffer
Expand All @@ -35,7 +35,7 @@ export class RawBodyParser implements BodyParser {
return !!is(mediaType, 'application/octet-stream');
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
const body = await invokeBodyParserMiddleware(this.rawParser, request);
return {value: body};
}
Expand Down
4 changes: 2 additions & 2 deletions packages/rest/src/body-parsers/body-parser.stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// License text available at https://opensource.org/licenses/MIT

import {Request} from '../types';
import {BodyParser, RequestBody} from './types';
import {BodyParser, ValueWithSchema} from './types';
import {builtinParsers} from './body-parser.helpers';

/**
Expand All @@ -21,7 +21,7 @@ export class StreamBodyParser implements BodyParser {
return false;
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
return {value: request};
}
}
4 changes: 2 additions & 2 deletions packages/rest/src/body-parsers/body-parser.text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
invokeBodyParserMiddleware,
builtinParsers,
} from './body-parser.helpers';
import {BodyParser, RequestBody} from './types';
import {BodyParser, ValueWithSchema} from './types';

export class TextBodyParser implements BodyParser {
name = builtinParsers.text;
Expand All @@ -37,7 +37,7 @@ export class TextBodyParser implements BodyParser {
return !!is(mediaType, 'text/*');
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
const body = await invokeBodyParserMiddleware(this.textParser, request);
return {value: body};
}
Expand Down
6 changes: 3 additions & 3 deletions packages/rest/src/body-parsers/body-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
import {
BodyParser,
BodyParserFunction,
RequestBody,
ValueWithSchema,
REQUEST_BODY_PARSER_TAG,
} from './types';

Expand All @@ -45,7 +45,7 @@ export class RequestBodyParser {
async loadRequestBodyIfNeeded(
operationSpec: OperationObject,
request: Request,
): Promise<RequestBody> {
): Promise<ValueWithSchema> {
const {requestBody, customParser} = await this._matchRequestBodySpec(
operationSpec,
request,
Expand Down Expand Up @@ -78,7 +78,7 @@ export class RequestBodyParser {
operationSpec: OperationObject,
request: Request,
) {
const requestBody: RequestBody = {
const requestBody: ValueWithSchema = {
value: undefined,
};
if (!operationSpec.requestBody) return {requestBody};
Expand Down
4 changes: 2 additions & 2 deletions packages/rest/src/body-parsers/body-parser.urlencoded.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
invokeBodyParserMiddleware,
builtinParsers,
} from './body-parser.helpers';
import {BodyParser, RequestBody} from './types';
import {BodyParser, ValueWithSchema} from './types';

export class UrlEncodedBodyParser implements BodyParser {
name = builtinParsers.urlencoded;
Expand All @@ -32,7 +32,7 @@ export class UrlEncodedBodyParser implements BodyParser {
return !!is(mediaType, 'urlencoded');
}

async parse(request: Request): Promise<RequestBody> {
async parse(request: Request): Promise<ValueWithSchema> {
const body = await invokeBodyParserMiddleware(
this.urlencodedParser,
request,
Expand Down
6 changes: 3 additions & 3 deletions packages/rest/src/body-parsers/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {Request} from '../types';
/**
* Request body with metadata
*/
export type RequestBody = {
export type ValueWithSchema = {
/**
* Parsed value of the request body
*/
Expand Down Expand Up @@ -46,13 +46,13 @@ export interface BodyParser {
* Parse the request body
* @param request http request
*/
parse(request: Request): Promise<RequestBody>;
parse(request: Request): Promise<ValueWithSchema>;
}

/**
* Plain function for body parsing
*/
export type BodyParserFunction = (request: Request) => Promise<RequestBody>;
export type BodyParserFunction = (request: Request) => Promise<ValueWithSchema>;

/**
* Binding tag for request body parser extensions
Expand Down
Loading