Skip to content

Commit

Permalink
Merge 285574d into b4f3e3a
Browse files Browse the repository at this point in the history
  • Loading branch information
kobik committed Jun 24, 2020
2 parents b4f3e3a + 285574d commit f010abd
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 128 deletions.
20 changes: 8 additions & 12 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict';

const get = require('lodash.get');
const Ajv = require('ajv');
const SwaggerParser = require('swagger-parser');
const { URL } = require('url');

const { defaultFormatsValidators } = require('./validators/formatValidators.js');
Expand All @@ -14,23 +12,21 @@ const schemaUtils = require('./utils/schemaUtils');
const sourceResolver = require('./utils/sourceResolver');
const Validators = require('./validators/index');
const createContentTypeHeaders = require('./utils/createContentTypeHeaders');
const { loadSchemaAsync, loadSchema } = require('./utils/schemaLoaders.js');

const DEFAULT_OPTIONS = {
buildRequests: true,
buildResponses: true
};

function buildSchema(swaggerPath, options) {
return Promise.all([
SwaggerParser.dereference(swaggerPath),
SwaggerParser.parse(swaggerPath)
]).then(function ([dereferencedJsonSchema, jsonSchema]) {
return buildValidations(jsonSchema, dereferencedJsonSchema, options);
});
async function buildSchema(swaggerPath, options) {
const { jsonSchema, dereferencedSchema } = await loadSchemaAsync(swaggerPath, options);

return buildValidations(jsonSchema, dereferencedSchema, options);
}

function buildSchemaSync(pathOrSchema, options) {
const { jsonSchema, dereferencedSchema } = schemaUtils.getSchemas(pathOrSchema, options);
const { jsonSchema, dereferencedSchema } = loadSchema(pathOrSchema, options);

return buildValidations(jsonSchema, dereferencedSchema, options);
}
Expand Down Expand Up @@ -102,7 +98,7 @@ function buildRequestValidator(referenced, dereferenced, currentPath, currentMet
const requestSchema = {};
let localParameters = [];
const pathParameters = dereferenced.paths[currentPath].parameters || [];
const isOpenApi3 = schemaUtils.isOpenApi3(dereferenced);
const isOpenApi3 = schemaUtils.getOAIVersion(dereferenced) === 3;
const parameters = dereferenced.paths[currentPath][currentMethod].parameters || [];
if (isOpenApi3) {
requestSchema.body = oai3.buildRequestBodyValidation(dereferenced, referenced, currentPath, currentMethod, options);
Expand Down Expand Up @@ -136,7 +132,7 @@ function buildRequestValidator(referenced, dereferenced, currentPath, currentMet

function buildResponseValidator(referenced, dereferenced, currentPath, currentMethod, options) {
const responsesSchema = {};
const isOpenApi3 = schemaUtils.isOpenApi3(dereferenced);
const isOpenApi3 = schemaUtils.getOAIVersion(dereferenced) === 3;
const responses = get(dereferenced, `paths[${currentPath}][${currentMethod}].responses`);
if (responses) {
Object
Expand Down
37 changes: 37 additions & 0 deletions src/utils/fileLoaders.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const fs = require('fs');
const path = require('path');
const jsyaml = require('js-yaml');

const cwd = process.cwd();

const file = (refValue, options) => {
let refPath = refValue;
const baseFolder = options.baseFolder ? path.resolve(cwd, options.baseFolder) : cwd;

if (refPath.indexOf('file:') === 0) {
refPath = refPath.substring(5);
} else {
refPath = path.resolve(baseFolder, refPath);
}

const filePath = getRefFilePath(refPath);
const filePathLowerCase = filePath.toLowerCase();

const data = fs.readFileSync(filePath, 'utf8');
if (filePathLowerCase.endsWith('.json')) {
return JSON.parse(data);
} else if (filePathLowerCase.endsWith('.yml') || filePathLowerCase.endsWith('.yaml')) {
return jsyaml.load(data);
}
};

function getRefFilePath(refPath) {
const hashIndex = refPath.indexOf('#');
if (hashIndex > 0) {
return refPath.substring(0, hashIndex);
} else {
return refPath;
}
}

module.exports = { file };
89 changes: 64 additions & 25 deletions src/utils/schemaLoaders.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,77 @@
const fs = require('fs');
const path = require('path');
const jsyaml = require('js-yaml');
const fs = require('fs');

const cwd = process.cwd();
const { getOAIVersion } = require('./schemaUtils');

const file = (refValue, options) => {
let refPath = refValue;
const baseFolder = options.baseFolder ? path.resolve(cwd, options.baseFolder) : cwd;
async function loadSchemaAsync(swaggerPath) {
const SwaggerParser = require('swagger-parser');

if (refPath.indexOf('file:') === 0) {
refPath = refPath.substring(5);
} else {
refPath = path.resolve(baseFolder, refPath);
const [dereferencedSchema, jsonSchema] = await Promise.all([
SwaggerParser.dereference(swaggerPath),
SwaggerParser.parse(swaggerPath)
]);

return { dereferencedSchema, jsonSchema };
}

function loadSchema(pathOrSchema, options) {
const schemaValidators = require('./schemaValidators');

const jsonSchema = getJsonSchema(pathOrSchema);
const basePath = getSchemaBasePath(pathOrSchema, options);
const dereferencedSchema = dereference(basePath, jsonSchema);

if (getOAIVersion(dereferencedSchema) === 3) {
const validationResult = schemaValidators.getOAI3Validator().validate(dereferencedSchema);
if (validationResult.errors && validationResult.errors.length > 0) {
const error = new Error('Invalid OpenAPI 3 schema');
error.errors = validationResult.errors;
throw error;
}
}

const filePath = getRefFilePath(refPath);
const filePathLowerCase = filePath.toLowerCase();
return { jsonSchema, dereferencedSchema };
}

var data = fs.readFileSync(filePath, 'utf8');
if (filePathLowerCase.endsWith('.json')) {
return JSON.parse(data);
} else if (filePathLowerCase.endsWith('.yml') || filePathLowerCase.endsWith('.yaml')) {
return jsyaml.load(data);
function dereference(basePath, jsonSchema) {
const deref = require('json-schema-deref-sync');
const schemaLoaders = require('./fileLoaders');

const dereferencedSchema = deref(jsonSchema, {
baseFolder: basePath,
failOnMissing: true,
loaders: schemaLoaders
});

return dereferencedSchema;
}

function getJsonSchema(pathOrSchema) {
if (typeof pathOrSchema === 'string') {
// file path
const yaml = require('js-yaml');

const fileContents = fs.readFileSync(pathOrSchema);
const jsonSchema = yaml.load(fileContents, 'utf8');
return jsonSchema;
} else {
// json schema
return pathOrSchema;
}
};
}

function getRefFilePath(refPath) {
let filePath = refPath;
const hashIndex = filePath.indexOf('#');
if (hashIndex > 0) {
filePath = refPath.substring(0, hashIndex);
function getSchemaBasePath(pathOrSchema, options = {}) {
// always return basePath from options if exists
if (options.basePath) {
return options.basePath;
}

return filePath;
// in case a path to definitions file was given
if (typeof pathOrSchema === 'string') {
const fullPath = path.resolve(pathOrSchema).split(path.sep);
fullPath.pop();
return fullPath.join(path.sep);
}
}

module.exports = { file };
module.exports = { loadSchema, loadSchemaAsync };
76 changes: 12 additions & 64 deletions src/utils/schemaUtils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
const fs = require('fs');
const yaml = require('js-yaml');
const path = require('path');
const deref = require('json-schema-deref-sync');

const schemaLoaders = require('./schemaLoaders');
const schemaValidators = require('./schemaValidators');

const { readOnly, writeOnly, validationTypes, allDataTypes } = require('./common');

const DEFAULT_REQUEST_CONTENT_TYPE = 'application/json';
Expand Down Expand Up @@ -120,15 +112,6 @@ function addNullableSupport(dereferencedSchema, propName) {
}
}

/**
* returns true if given dereferenced schema object is an openapi version 3.x.x
*
* @param {object} dereferencedSchema
*/
function isOpenApi3(dereferencedSchema) {
return dereferencedSchema.openapi ? dereferencedSchema.openapi.startsWith('3.') : false;
}

/**
* returns the type of the given dereferenced schema object (anyOf, oneOf, allOf)
*
Expand All @@ -146,51 +129,17 @@ function getSchemaType(dereferencedSchema) {
}
}

function getSchemas(pathOrSchema, options) {
const jsonSchema = getJsonSchema(pathOrSchema);
const basePath = getSchemaBasePath(pathOrSchema, options);
const dereferencedSchema = deref(jsonSchema, {
baseFolder: basePath,
failOnMissing: true,
loaders: schemaLoaders
});

if (isOpenApi3(dereferencedSchema)) {
const validationResult = schemaValidators.openApi3Validator.validate(dereferencedSchema);
if (validationResult.errors && validationResult.errors.length > 0) {
const error = new Error('Invalid OpenAPI 3 schema');
error.errors = validationResult.errors;
throw error;
}
}

return { jsonSchema, dereferencedSchema };
}

function getJsonSchema(pathOrSchema) {
if (typeof pathOrSchema === 'string') {
// file path
const fileContents = fs.readFileSync(pathOrSchema);
const jsonSchema = yaml.load(fileContents, 'utf8');
return jsonSchema;
} else {
// json schema
return pathOrSchema;
}
}

function getSchemaBasePath(pathOrSchema, options = {}) {
// always return basePath from options if exists
if (options.basePath) {
return options.basePath;
}
/**
* returns true if given dereferenced schema object is an openapi version 3.x.x
*
* @param {object} dereferencedSchema
*/
function getOAIVersion(dereferencedSchema) {
const version = dereferencedSchema.openapi && dereferencedSchema.openapi.split('.')[0];

// in case a path to definitions file was given
if (typeof pathOrSchema === 'string') {
const fullPath = path.resolve(pathOrSchema).split(path.sep);
fullPath.pop();
return fullPath.join(path.sep);
}
return version
? parseInt(version)
: undefined;
}

module.exports = {
Expand All @@ -199,6 +148,5 @@ module.exports = {
getAllResponseContentTypes,
addOAI3Support,
getSchemaType,
isOpenApi3,
getSchemas
};
getOAIVersion
};
19 changes: 14 additions & 5 deletions src/utils/schemaValidators.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
const OpenAPISchemaValidator = require('openapi-schema-validator').default;
const openApi3Validator = new OpenAPISchemaValidator({
version: 3,
version3Extensions: {

let validator;

function getOAI3Validator() {
if (!validator) {
validator = new OpenAPISchemaValidator({
version: 3,
version3Extensions: {
}
});
}
});

return validator;
}

module.exports = {
openApi3Validator
getOAI3Validator
};
22 changes: 22 additions & 0 deletions test/openapi3/general/general-oai3-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,33 @@ const yaml = require('js-yaml');
const fs = require('fs');

const schemaValidatorGenerator = require('../../../src');
const schemaLoaders = require('../../../src/utils/schemaLoaders');

const { expect } = chai;

describe('oai3 - general tests', () => {
describe('sync', () => {
describe('getSchemas', () => {
it('Validates OpenAPI specification to be valid', (done) => {
try {
schemaLoaders.loadSchema('test/utils/specs/openApi3SpecInvalid.yaml');
} catch (err) {
expect(err.message).to.equal('Invalid OpenAPI 3 schema');
expect(err.errors).to.deep.equal([
{
keyword: 'required',
dataPath: '',
schemaPath: '#/required',
params: {
missingProperty: 'info'
},
message: "should have required property 'info'"
}
]);
done();
}
});
});
describe('loading yaml with discriminator with allOf', () => {
it('fail to load with relevant error', () => {
const swaggerPath = path.join(__dirname, 'pets-discriminator-allOf.yaml');
Expand Down
22 changes: 0 additions & 22 deletions test/utils/schemaUtils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,6 @@ const schemaUtils = require('../../src/utils/schemaUtils');
const { expect } = require('chai');

describe('schemaUtils', () => {
describe('getSchemas', () => {
it('Validates OpenAPI specification to be valid', (done) => {
try {
schemaUtils.getSchemas('test/utils/specs/openApi3SpecInvalid.yaml');
} catch (err) {
expect(err.message).to.equal('Invalid OpenAPI 3 schema');
expect(err.errors).to.deep.equal([
{
keyword: 'required',
dataPath: '',
schemaPath: '#/required',
params: {
missingProperty: 'info'
},
message: "should have required property 'info'"
}
]);
done();
}
});
});

describe('getAllResponseContentTypes', () => {
it('correctly handles no responses', () => {
const result = schemaUtils.getAllResponseContentTypes([]);
Expand Down

0 comments on commit f010abd

Please sign in to comment.