Skip to content

Commit

Permalink
Initial support for OpenAPI 3.0 (#183)
Browse files Browse the repository at this point in the history
* Initial support for OpenAPI 3.0

* ecmaVersion updated to 2018, lint error fixed

* more lint errors
  • Loading branch information
sprootshift authored Aug 4, 2020
1 parent d0532f6 commit cc09e61
Show file tree
Hide file tree
Showing 11 changed files with 1,068 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": ["eslint:recommended", "eslint-config-hapi"],
"parserOptions": {
"ecmaVersion": 2017,
"ecmaVersion": 2018,
"sourceType": "module"
},
"env": {
Expand Down
17 changes: 15 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ const register = async function (server, options, next) {
let { docs, docspath } = validation.value;
const spec = await Parser.validate(api);

// Cannot use conflicting url pathnames, so opting to mount the first url pathname
if (spec.openapi) {
spec.basePath = new URL(Hoek.reach(spec, ['servers', 0, 'url'])).pathname;
}
spec.basePath = Utils.unsuffix(Utils.prefix(spec.basePath || '/', '/'), '/');

//Expose plugin api
Expand Down Expand Up @@ -111,8 +115,17 @@ const register = async function (server, options, next) {
});
}
}
if (spec.securityDefinitions) {
for (const [name, security] of Object.entries(spec.securityDefinitions)) {

let securitySchemes;
if (spec.swagger && spec.securityDefinitions) {
securitySchemes = spec.securityDefinitions;
}
if (spec.openapi && spec.components && spec.components.securitySchemes) {
securitySchemes = spec.components.securitySchemes;
}

if (securitySchemes) {
for (const [name, security] of Object.entries(securitySchemes)) {
if (security['x-hapi-auth-strategy']) {
const strategy = require(Path.resolve(Path.join(basedir, security['x-hapi-auth-strategy'])));

Expand Down
14 changes: 10 additions & 4 deletions lib/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ const create = async function (server, { api, basedir, cors, vhost, handlers, ex

const skipValidation = options.payload && options.payload.parse === false;

if (operation.parameters && !skipValidation) {
if ((operation.parameters || operation.requestBody) && !skipValidation) {
const allowUnknownProperties = xoptions.validate && xoptions.validate.options && xoptions.validate.options.allowUnknown === true;
const v = validator.makeAll(operation.parameters, operation.consumes || api.consumes, allowUnknownProperties);
const v = validator.makeAll(operation.parameters, operation.requestBody, operation.consumes || api.consumes, api.openapi, allowUnknownProperties);
options.validate = v.validate;
options.ext = {
onPreAuth: { method: v.routeExt }
Expand All @@ -83,7 +83,7 @@ const create = async function (server, { api, basedir, cors, vhost, handlers, ex

if (outputvalidation && operation.responses) {
options.response = {};
options.response.status = validator.makeResponseValidator(operation.responses);
options.response.status = validator.makeResponseValidator(operation.responses, api.openapi);
}

if (operation.security === undefined && api.security) {
Expand All @@ -95,7 +95,13 @@ const create = async function (server, { api, basedir, cors, vhost, handlers, ex
const securitySchemes = Object.keys(secdef);

for (const securityDefinitionName of securitySchemes) {
const securityDefinition = api.securityDefinitions[securityDefinitionName];
let securityDefinition;
if (api.swagger) {
securityDefinition = api.securityDefinitions[securityDefinitionName];
}
if (api.openapi) {
securityDefinition = api.components.securitySchemes[securityDefinitionName];
}

Hoek.assert(securityDefinition, 'Security scheme not defined.');

Expand Down
59 changes: 48 additions & 11 deletions lib/validators.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,17 @@ const refineType = function (type, format) {
}
};

const enjoi = Enjoi.defaults({ extensions, refineType });
const refineSchema = function (joiSchema, jsonSchema) {
if (jsonSchema.nullable) {
return joiSchema.allow(null);
}
return joiSchema;
};

const create = function (options = {}) {
const enjoi = Enjoi.defaults({ extensions, refineType, refineSchema });

const makeValidator = function (parameter, consumes, allowUnknownProperties = false) {
const create = function (options = {}) {
const makeValidator = function (parameter, consumes, openapi, allowUnknownProperties = false) {
const coerce = coercion(parameter, consumes);

let schema;
Expand All @@ -52,11 +58,11 @@ const create = function (options = {}) {
}
}
else {
const template = {
let template = {
required: parameter.required,
enum: parameter.enum,
type: parameter.type,
schema: parameter.schema,
schema: undefined,
items: parameter.items,
properties: parameter.properties,
pattern: parameter.pattern,
Expand All @@ -74,6 +80,12 @@ const create = function (options = {}) {
multipleOf: parameter.multipleOf
};

if (openapi) {
template = { ...template, ...parameter.schema };
} else {
template.schema = parameter.schema;
}

schema = enjoi.schema(template);
}

Expand Down Expand Up @@ -120,14 +132,26 @@ const create = function (options = {}) {
};
};

const makeResponseValidator = function (responses) {
const makeResponseValidator = function (responses, openapi) {
const schemas = {};

for (const [code, response] of Object.entries(responses)) {
if (!isNaN(code)) {
if (response.schema) {
const schema = enjoi.schema(response.schema);
if (response.schema.type === 'array') {
let schemaDesc;
if (openapi && response.content) {
for (const mediaType of Object.keys(response.content)) {
// Applying first available schema to all media types
if (response.content[mediaType].schema) {
schemaDesc = response.content[mediaType].schema;
break;
}
}
} else {
schemaDesc = response.schema;
}
if (schemaDesc) {
const schema = enjoi.schema(schemaDesc);
if (schemaDesc === 'array') {
schema.single(true);
}
schemas[code] = schema;
Expand All @@ -137,7 +161,7 @@ const create = function (options = {}) {
return schemas;
};

const makeAll = function (parameters = [], consumes, allowUnknownProperties = false) {
const makeAll = function (parameters = [], requestBody, consumes, openapi, allowUnknownProperties = false) {
const routeExt = [];
const validate = {};
const formValidators = {};
Expand All @@ -153,8 +177,21 @@ const create = function (options = {}) {
return this.parameter.type === 'file' ? result.value : result;
};

if (openapi && requestBody && requestBody.content) {
consumes = Object.keys(requestBody.content);
for (const mediaType of consumes) {
// Applying first available schema to all media types
if (requestBody.content[mediaType].schema) {
const parameter = { in: 'body', schema: requestBody.content[mediaType].schema, name: 'body' };
const validator = makeValidator(parameter, consumes, openapi, allowUnknownProperties);
validate.payload = validator.validate;
break;
}
}
}

for (const parameter of parameters) {
const validator = makeValidator(parameter, consumes, allowUnknownProperties);
const validator = makeValidator(parameter, consumes, openapi, allowUnknownProperties);

switch (validator.parameter.in) {
case 'header':
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"@hapi/hoek": "^9.0.4",
"@hapi/joi": "^17.1.0",
"dot-prop": "^5.2.0",
"enjoi": "^7.0.0",
"enjoi": "git://github.com/sprootshift/enjoi",
"js-yaml": "^3.11.0",
"merge-object-files": "^2.0.0",
"swagger-parser": "^9.0.1"
Expand Down
13 changes: 12 additions & 1 deletion test/fixtures/lib/stub-auth-token-scheme.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ const register = function (server, options) {
});
};

module.exports = { register, name: 'stub-auth-token' };
const buildValidateFunc = function (allowedToken) {
return async function (token) {

if (token === allowedToken) {
return { credentials: { scope: [ 'api1:read' ] }, artifacts: { }};
}

return {};
}
};

module.exports = { register, name: 'stub-auth-token', buildValidateFunc};
Loading

0 comments on commit cc09e61

Please sign in to comment.