diff --git a/.gitignore b/.gitignore index b21036c4..fff0713e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,6 @@ dist /coverage .coveralls.yml .nyc_output -secrets.zip \ No newline at end of file +secrets.zip +jest +junk \ No newline at end of file diff --git a/README.md b/README.md index 49f8e9fc..3b14daef 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ new OpenApiValidator({ ```javascript app.use((err, req, res, next) => { // format error - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, errors: err.errors, }); @@ -138,7 +138,7 @@ app.post('/v1/pets/:id/photos', function(req, res, next) { // 6. Create an Express error handler app.use((err, req, res, next) => { // 7. Customize errors - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, errors: err.errors, }); diff --git a/package-lock.json b/package-lock.json index 81c1fd3f..51cd7f4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5187,9 +5187,9 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typescript": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.6.4.tgz", - "integrity": "sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.2.tgz", + "integrity": "sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index 245cd622..faf55090 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,6 @@ "supertest": "^4.0.2", "ts-node": "^8.3.0", "tsc": "^1.20150623.0", - "typescript": "^3.6.4" + "typescript": "^3.7.2" } } diff --git a/src/framework/base.path.ts b/src/framework/base.path.ts index fd26972d..ebc16dba 100644 --- a/src/framework/base.path.ts +++ b/src/framework/base.path.ts @@ -26,7 +26,7 @@ export default class BasePath { for (const variable in server.variables) { if (server.variables.hasOwnProperty(variable)) { const v = server.variables[variable]; - const enums = v.enum || []; + const enums = v.enum ?? []; if (enums.length === 0 && v.default) enums.push(v.default); this.variables[variable] = { diff --git a/src/framework/index.ts b/src/framework/index.ts index 4d644903..33567982 100644 --- a/src/framework/index.ts +++ b/src/framework/index.ts @@ -60,7 +60,7 @@ export default class OpenAPIFramework implements IOpenAPIFramework { ? getBasePathsFromServers(this.apiDoc.servers) : [ new BasePath({ - url: (this.apiDoc.basePath || '').replace(/\/$/, ''), + url: (this.apiDoc.basePath ?? '').replace(/\/$/, ''), }), ]; diff --git a/src/framework/openapi.schema.validator.ts b/src/framework/openapi.schema.validator.ts index 8817b880..7353fa71 100644 --- a/src/framework/openapi.schema.validator.ts +++ b/src/framework/openapi.schema.validator.ts @@ -14,7 +14,7 @@ export class OpenAPISchemaValidator { if (!ver) throw Error('version missing from OpenAPI specification'); if (ver != 3) throw Error('OpenAPI v3 specification version is required'); - const schema = merge({}, openapi3Schema, extensions || {}); + const schema = merge({}, openapi3Schema, extensions ?? {}); v.addSchema(schema); this.validator = v.compile(schema); } diff --git a/src/framework/openapi.spec.loader.ts b/src/framework/openapi.spec.loader.ts index 0e2d2741..842898fd 100644 --- a/src/framework/openapi.spec.loader.ts +++ b/src/framework/openapi.spec.loader.ts @@ -13,8 +13,8 @@ export class OpenApiSpecLoader { load() { const framework = this.createFramework(this.opts); - const apiDoc = framework.apiDoc || {}; - const bps = framework.basePaths || []; + const apiDoc = framework.apiDoc ?? {}; + const bps = framework.basePaths ?? []; const basePaths = bps.reduce((acc, bp) => { bp.all().forEach(path => acc.add(path)); return acc; @@ -52,10 +52,10 @@ export class OpenApiSpecLoader { continue; } const schemaParameters = new Set(); - (schema.parameters || []).forEach(parameter => + (schema.parameters ?? []).forEach(parameter => schemaParameters.add(parameter), ); - ((methods as any).parameters || []).forEach(parameter => + ((methods as any).parameters ?? []).forEach(parameter => schemaParameters.add(parameter), ); schema.parameters = Array.from(schemaParameters); diff --git a/src/index.ts b/src/index.ts index 0365b6a2..141526ca 100644 --- a/src/index.ts +++ b/src/index.ts @@ -42,7 +42,7 @@ export class OpenApiValidator { this.installMultipartMiddleware(); const components = this.context.apiDoc.components; - if (components && components.securitySchemes) { + if (components?.securitySchemes) { this.installSecurityMiddleware(); } @@ -76,7 +76,7 @@ export class OpenApiValidator { ) => { if (req.openapi.pathParams) { // override path params - req.params[name] = req.openapi.pathParams[name] || req.params[name]; + req.params[name] = req.openapi.pathParams[name] ?? req.params[name]; } next(); }, diff --git a/src/middlewares/openapi.multipart.ts b/src/middlewares/openapi.multipart.ts index a68a5fe8..92ed2bb6 100644 --- a/src/middlewares/openapi.multipart.ts +++ b/src/middlewares/openapi.multipart.ts @@ -50,13 +50,7 @@ function isValidContentType(req: Request): boolean { } function isMultipart(req: OpenApiRequest): boolean { - return ( - req.openapi && - req.openapi.schema && - req.openapi.schema.requestBody && - req.openapi.schema.requestBody.content && - req.openapi.schema.requestBody.content['multipart/form-data'] - ); + return req?.openapi?.schema?.requestBody?.content?.['multipart/form-data']; } function error(req: OpenApiRequest, err: Error) { @@ -67,7 +61,7 @@ function error(req: OpenApiRequest, err: Error) { // HACK // TODO improve multer error handling const missingField = /Multipart: Boundary not found/i.test( - err.message || '', + err.message ?? '', ); if (missingField) { return validationError(400, req.path, 'multipart file(s) required'); diff --git a/src/middlewares/openapi.request.validator.ts b/src/middlewares/openapi.request.validator.ts index 376ecf5e..cc650273 100644 --- a/src/middlewares/openapi.request.validator.ts +++ b/src/middlewares/openapi.request.validator.ts @@ -49,7 +49,7 @@ export class RequestValidator { // cache middleware by combining method, path, and contentType // TODO contentType could have value not_provided - const contentType = extractContentType(req) || 'not_provided'; + const contentType = extractContentType(req) ?? 'not_provided'; const key = `${req.method}-${req.path}-${contentType}`; if (!this._middlewareCache[key]) { @@ -71,13 +71,13 @@ export class RequestValidator { let requestBody = pathSchema.requestBody; - if (requestBody && requestBody.hasOwnProperty('$ref')) { + if (requestBody?.hasOwnProperty('$ref')) { const id = requestBody.$ref.replace(/^.+\//i, ''); requestBody = this._apiDocs.components.requestBodies[id]; } let body = this.requestBodyToSchema(path, contentType, requestBody); - let requiredAdds = requestBody && requestBody.required ? ['body'] : []; + let requiredAdds = requestBody?.required ? ['body'] : []; const schema = { // $schema: "http://json-schema.org/draft-04/schema#", @@ -100,7 +100,7 @@ export class RequestValidator { Object.keys(req.openapi.pathParams).length > 0; if (shouldUpdatePathParams) { - req.params = req.openapi.pathParams || req.params; + req.params = req.openapi.pathParams ?? req.params; } req.schema = schema; @@ -111,7 +111,7 @@ export class RequestValidator { * https://swagger.io/docs/specification/describing-parameters/#schema-vs-content */ parameters.parseJson.forEach(item => { - if (req[item.reqField] && req[item.reqField][item.name]) { + if (req[item.reqField]?.[item.name]) { req[item.reqField][item.name] = JSON.parse( req[item.reqField][item.name], ); @@ -125,7 +125,7 @@ export class RequestValidator { * filter=foo%20bar%20baz */ parameters.parseArray.forEach(item => { - if (req[item.reqField] && req[item.reqField][item.name]) { + if (req[item.reqField]?.[item.name]) { req[item.reqField][item.name] = req[item.reqField][item.name].split( item.delimiter, ); @@ -137,8 +137,7 @@ export class RequestValidator { */ parameters.parseArrayExplode.forEach(item => { if ( - req[item.reqField] && - req[item.reqField][item.name] && + req[item.reqField]?.[item.name] && !(req[item.reqField][item.name] instanceof Array) ) { req[item.reqField][item.name] = [req[item.reqField][item.name]]; @@ -156,7 +155,7 @@ export class RequestValidator { next(); } else { // TODO look into Ajv async errors plugins - const errors = augmentAjvErrors([...(validator.errors || [])]); + const errors = augmentAjvErrors([...(validator.errors ?? [])]); const err = ajvErrorsToValidatorError(400, errors); const message = this.ajv.errorsText(errors, { dataVar: 'request' }); throw ono(err, message); @@ -190,7 +189,7 @@ export class RequestValidator { : `unsupported media type ${contentType}`; throw validationError(415, path, msg); } - return content.schema || {}; + return content.schema ?? {}; } return {}; } @@ -203,7 +202,7 @@ export class RequestValidator { const securityKey = Object.keys(sec)[0]; return securitySchema[securityKey]; }) - .filter(sec => sec && sec.in && sec.in === 'query') + .filter(sec => sec?.in === 'query') .map(sec => sec.name) : []; } @@ -242,7 +241,7 @@ export class RequestValidator { } let parameterSchema = parameter.schema; - if (parameter.content && parameter.content[TYPE_JSON]) { + if (parameter.content?.[TYPE_JSON]) { parameterSchema = parameter.content[TYPE_JSON].schema; parseJson.push({ name, reqField }); } @@ -252,11 +251,7 @@ export class RequestValidator { throw validationError(400, path, message); } - if ( - parameter.schema && - parameter.schema.type === 'array' && - !parameter.explode - ) { + if (parameter.schema?.type === 'array' && !parameter.explode) { const delimiter = arrayDelimiter[parameter.style]; if (!delimiter) { const message = `Parameter 'style' has incorrect value '${parameter.style}' for [${parameter.name}]`; @@ -265,11 +260,7 @@ export class RequestValidator { parseArray.push({ name, reqField, delimiter }); } - if ( - parameter.schema && - parameter.schema.type === 'array' && - parameter.explode - ) { + if (parameter.schema?.type === 'array' && parameter.explode) { parseArrayExplode.push({ name, reqField }); } diff --git a/src/middlewares/openapi.response.validator.ts b/src/middlewares/openapi.response.validator.ts index 7ad6cc63..2d48c146 100644 --- a/src/middlewares/openapi.response.validator.ts +++ b/src/middlewares/openapi.response.validator.ts @@ -28,7 +28,7 @@ export class ResponseValidator { public validate() { return mung.json((body, req: any, res) => { if (req.openapi) { - const responses = req.openapi.schema && req.openapi.schema.responses; + const responses = req.openapi.schema?.responses; const validators = this._getOrBuildValidator(req, responses); const statusCode = res.statusCode; const path = req.path; @@ -45,7 +45,7 @@ export class ResponseValidator { return this.buildValidators(responses); } - const contentType = extractContentType(req) || 'not_provided'; + const contentType = extractContentType(req) ?? 'not_provided'; const key = `${req.method}-${req.path}-${contentType}`; let validators = this.validatorsCache[key]; @@ -100,10 +100,7 @@ export class ResponseValidator { * @returns a map of validators */ private buildValidators(responses) { - const canValidate = r => - typeof r.content === 'object' && - r.content[TYPE_JSON] && - r.content[TYPE_JSON].schema; + const canValidate = r => typeof r.content === 'object' && r.content[TYPE_JSON]?.schema; const schemas = {}; for (const entry of Object.entries(responses)) { @@ -122,7 +119,7 @@ export class ResponseValidator { properties: { response: schema, }, - components: this.spec.components || {}, + components: this.spec.components ?? {}, }; } diff --git a/src/middlewares/openapi.security.ts b/src/middlewares/openapi.security.ts index 1613beb3..ea9f5783 100644 --- a/src/middlewares/openapi.security.ts +++ b/src/middlewares/openapi.security.ts @@ -46,7 +46,7 @@ export function security( // use the local security object or fallbac to api doc's security or undefined const securities: OpenAPIV3.SecurityRequirementObject[] = - req.openapi.schema.security || context.apiDoc.security; + req.openapi.schema.security ?? context.apiDoc.security; const path: string = req.openapi.openApiRoute; @@ -84,7 +84,7 @@ export function security( if (success) next(); else throw firstError; } catch (e) { - const message = (e && e.error && e.error.message) || 'unauthorized'; + const message = e?.error?.message || 'unauthorized'; const err = validationError(e.status, path, message); next(err); } @@ -117,9 +117,7 @@ class SecuritySchemes { } const securityKey = Object.keys(s)[0]; const scheme: any = this.securitySchemes[securityKey]; - const handler = - (this.securityHandlers && this.securityHandlers[securityKey]) || - fallbackHandler; + const handler = this.securityHandlers?.[securityKey] ?? fallbackHandler; const scopesTmp = s[securityKey]; const scopes = Array.isArray(scopesTmp) ? scopesTmp : []; @@ -154,7 +152,7 @@ class SecuritySchemes { } catch (e) { return { success: false, - status: e.status || 401, + status: e.status ?? 401, error: e, }; } diff --git a/src/middlewares/util.ts b/src/middlewares/util.ts index d5193e8d..b5d54b78 100644 --- a/src/middlewares/util.ts +++ b/src/middlewares/util.ts @@ -27,7 +27,7 @@ const _validationError = ( { path, message, - ...({ errors } || {}), + ...({ errors } ?? {}), }, ], }); @@ -52,7 +52,7 @@ export function augmentAjvErrors( errors.forEach(e => { if (e.keyword === 'enum') { const params: any = e.params; - const allowedEnumValues = params && params.allowedValues; + const allowedEnumValues = params?.allowedValues; e.message = !!allowedEnumValues ? `${e.message}: ${allowedEnumValues.join(', ')}` : e.message; @@ -68,15 +68,9 @@ export function ajvErrorsToValidatorError( status, errors: errors.map(e => { const params: any = e.params; - const required = - params && - params.missingProperty && - e.dataPath + '.' + params.missingProperty; - const additionalProperty = - params && - params.additionalProperty && - e.dataPath + '.' + params.additionalProperty; - const path = required || additionalProperty || e.dataPath || e.schemaPath; + const required = params?.missingProperty && e.dataPath + '.' + params.missingProperty; + const additionalProperty = params?.additionalProperty && e.dataPath + '.' + params.additionalProperty; + const path = required ?? additionalProperty ?? e.dataPath ?? e.schemaPath; return { path, message: e.message, diff --git a/test/common/app.ts b/test/common/app.ts index 203ff02e..03701584 100644 --- a/test/common/app.ts +++ b/test/common/app.ts @@ -37,7 +37,7 @@ export async function createApp( if (useRoutes) { // Register error handler app.use((err, req, res, next) => { - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, errors: err.errors, }); diff --git a/test/one.of.spec.ts b/test/one.of.spec.ts index 6a019c4a..367d796a 100644 --- a/test/one.of.spec.ts +++ b/test/one.of.spec.ts @@ -22,9 +22,9 @@ describe(packageJson.name, () => { res.json(req.body); }); app.use((err, req, res, next) => { - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, - code: err.status || 500, + code: err.status ?? 500, }); }); }, diff --git a/test/read.only.spec.ts b/test/read.only.spec.ts index 5bd76537..54ba3ec0 100644 --- a/test/read.only.spec.ts +++ b/test/read.only.spec.ts @@ -34,7 +34,7 @@ describe(packageJson.name, () => { body.created_at = new Date().toISOString(); body.reviews = body.reviews.map(r => ({ id: 99, - rating: r.rating || 2, + rating: r.rating ?? 2, })); res.json(body); }), diff --git a/test/response.validation.options.spec.ts b/test/response.validation.options.spec.ts index f06d52a6..bd621129 100644 --- a/test/response.validation.options.spec.ts +++ b/test/response.validation.options.spec.ts @@ -36,9 +36,9 @@ describe(packageJson.name, () => { res.json(req.body); }); app.use((err, req, res, next) => { - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, - code: err.status || 500, + code: err.status ?? 500, }); }); }, diff --git a/test/response.validation.spec.ts b/test/response.validation.spec.ts index 0852da44..28dc7822 100644 --- a/test/response.validation.spec.ts +++ b/test/response.validation.spec.ts @@ -40,9 +40,9 @@ describe(packageJson.name, () => { res.json(req.body); }); app.use((err, req, res, next) => { - res.status(err.status || 500).json({ + res.status(err.status ?? 500).json({ message: err.message, - code: err.status || 500, + code: err.status ?? 500, }); }); }, diff --git a/test/write.only.spec.ts b/test/write.only.spec.ts index 9f152257..c8618d8f 100644 --- a/test/write.only.spec.ts +++ b/test/write.only.spec.ts @@ -29,7 +29,7 @@ describe(packageJson.name, () => { body.created_at = new Date().toISOString(); body.reviews = body.reviews.map(r => ({ ...(excludeWriteOnly ? {} : { role_x: 'admin' }), - rating: r.rating || 2, + rating: r.rating ?? 2, })); if (excludeWriteOnly) {