Skip to content

Commit

Permalink
fixup!: apply feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
jannyHou committed Jun 28, 2020
1 parent 1c19c6a commit f3d89d4
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 13 deletions.
5 changes: 3 additions & 2 deletions packages/rest/src/coercion/coerce-parameter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RequestBodyValidationOptions,
RestHttpErrors,
validateValueAgainstSchema,
ValueValidationOptions,
} from '../';
import {parseJson} from '../parse-json';
import {
Expand All @@ -40,7 +41,7 @@ const debug = debugModule('loopback:rest:coercion');
export async function coerceParameter(
data: string | undefined | object,
spec: ParameterObject,
options?: RequestBodyValidationOptions,
options?: ValueValidationOptions,
) {
const schema = extractSchemaFromSpec(spec);

Expand Down Expand Up @@ -184,7 +185,7 @@ async function coerceObject(
data,
schema,
{},
{...options, coerceTypes: true},
{...options, coerceTypes: true, position: 'parameter'},
);
}

Expand Down
11 changes: 11 additions & 0 deletions packages/rest/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,17 @@ export type AjvKeyword = KeywordDefinition & {name: string};
*/
export type AjvFormat = FormatDefinition & {name: string};

/**
* Options for any value validation using AJV
*/
export interface ValueValidationOptions extends RequestBodyValidationOptions {
/**
* Where the data comes from. It can be 'body', 'path', 'header',
* 'query', 'cookie', etc...
*/
position?: string;
}

/**
* Options for request body validation using AJV
*/
Expand Down
45 changes: 34 additions & 11 deletions packages/rest/src/validation/request-body.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import debugModule from 'debug';
import _ from 'lodash';
import util from 'util';
import {HttpErrors, RequestBody, RestHttpErrors} from '..';
import {RequestBodyValidationOptions, SchemaValidatorCache} from '../types';
import {
RequestBodyValidationOptions,
SchemaValidatorCache,
ValueValidationOptions,
} from '../types';
import {AjvFactoryProvider} from './ajv-factory.provider';

const toJsonSchema = require('@openapi-contrib/openapi-schema-to-json-schema');
Expand Down Expand Up @@ -66,7 +70,10 @@ export async function validateRequestBody(
if (!schema) return;

options = {coerceTypes: !!body.coercionRequired, ...options};
await validateValueAgainstSchema(body.value, schema, globalSchemas, options);
await validateValueAgainstSchema(body.value, schema, globalSchemas, {
...options,
position: 'body',
});
}

/**
Expand Down Expand Up @@ -117,10 +124,10 @@ function getKeyForOptions(options: RequestBodyValidationOptions) {
*/
export async function validateValueAgainstSchema(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: any,
value: any,
schema: SchemaObject | ReferenceObject,
globalSchemas: SchemasObject = {},
options: RequestBodyValidationOptions = {},
options: ValueValidationOptions = {},
) {
let validate: ajv.ValidateFunction | undefined;

Expand All @@ -145,10 +152,10 @@ export async function validateValueAgainstSchema(

let validationErrors: ajv.ErrorObject[] = [];
try {
const validationResult = await validate(body);
// When body is optional & values is empty / null, ajv returns null
const validationResult = await validate(value);
// When value is optional & values is empty / null, ajv returns null
if (validationResult || validationResult === null) {
debug('Request body passed AJV validation.');
debug('Value passed AJV validation.');
return;
}
} catch (error) {
Expand All @@ -158,8 +165,8 @@ export async function validateValueAgainstSchema(
/* istanbul ignore if */
if (debug.enabled) {
debug(
'Invalid request body: %s. Errors: %s',
util.inspect(body, {depth: null}),
'Invalid value: %s. Errors: %s',
util.inspect(value, {depth: null}),
util.inspect(validationErrors),
);
}
Expand All @@ -168,7 +175,24 @@ export async function validateValueAgainstSchema(
validationErrors = options.ajvErrorTransformer(validationErrors);
}

const error = RestHttpErrors.invalidRequestBody();
// Throw invalid request body error
if (options.position === 'body') {
const error = RestHttpErrors.invalidRequestBody();
addErrorDetails(error, validationErrors);
throw error;
}

// Throw invalid value error
const error = new HttpErrors.BadRequest('Invalid value.');
addErrorDetails(error, validationErrors);
throw error;
}

function addErrorDetails(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
error: any,
validationErrors: ajv.ErrorObject[],
) {
error.details = _.map(validationErrors, e => {
return {
path: e.dataPath,
Expand All @@ -177,7 +201,6 @@ export async function validateValueAgainstSchema(
info: e.params,
};
});
throw error;
}

/**
Expand Down

0 comments on commit f3d89d4

Please sign in to comment.