Skip to content

Commit

Permalink
Merge pull request #41 from alt3/code-climate
Browse files Browse the repository at this point in the history
Improve code climate maintainability
  • Loading branch information
bravo-kernel committed Nov 10, 2019
2 parents 04a18a7 + 583961e commit 4332565
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 63 deletions.
16 changes: 8 additions & 8 deletions examples/generate.js
Expand Up @@ -19,6 +19,12 @@ const schemaManager = new JsonSchemaManager({
disableComments: false,
});

const pageIntro = `
These schemas were automatically generated on ${moment().format('YYYY-MM-DD')}
using [these Sequelize models](../test/models) and the most recent version of
sequelize-to-json-schemas. To confirm that these are indeed all valid schemas use:
`;

// ----------------------------------------------------------------------------
// JSON Schema Draft-07
// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -46,10 +52,7 @@ let fullSchema = {
};

let markdown = `# JSON Schema Draft-07
These schemas were automatically generated on ${moment().format('YYYY-MM-DD')}
using [these Sequelize models](../test/models) and the most recent version of
sequelize-to-json-schemas. To confirm that these are indeed all valid schemas use:
${pageIntro}
- [JSON Schema Validator](https://www.jsonschemavalidator.net/)
- [ajv](https://github.com/epoberezkin/ajv)
Expand Down Expand Up @@ -141,10 +144,7 @@ fullSchema.components.schemas = {
};

markdown = `# OpenAPI 3.0
These schemas were automatically generated on ${moment().format('YYYY-MM-DD')}
using [these Sequelize models](../test/models/) and the most recent version of
sequelize-to-json-schemas. To confirm that these are indeed all valid schemas use:
${pageIntro}
- [Swagger Editor](https://editor.swagger.io/)
- [Online Swagger & OpenAPI Validator](https://apidevtools.org/swagger-parser/online)
Expand Down
70 changes: 15 additions & 55 deletions lib/schema-manager.js
Expand Up @@ -2,6 +2,7 @@ const { capitalize, omit, pick } = require('./utils/lodash-natives');

const StrategyInterface = require('./strategy-interface');
const TypeMapper = require('./type-mapper');
const { checkTypeOptional, checkTypeRequired } = require('./utils/type-checks');

/**
* Merged constructor options.
Expand Down Expand Up @@ -140,15 +141,8 @@ class SchemaManager {
options.baseUri += '/'; // eslint-disable-line no-param-reassign
}

if (typeof options.absolutePaths !== 'boolean') {
throw new TypeError(
"SchemaManager configuration option 'absolutePaths' not of type 'boolean'",
);
}

if (typeof options.disableComments !== 'boolean') {
throw new TypeError("Model option 'disableComments' not of type 'boolean'");
}
checkTypeRequired('absolutePaths', options.absolutePaths, 'boolean');
checkTypeRequired('disableComments', options.disableComments, 'boolean');

_options.set(this, options);
}
Expand All @@ -161,37 +155,18 @@ class SchemaManager {
* @returns {null}
*/
_verifyModelOptions(options) {
if (options.title && typeof options.title !== 'string') {
throw new TypeError("Model option 'title' not of type 'string'");
}

if (options.description && typeof options.description !== 'string') {
throw new TypeError("Model option 'description' not of type 'string'");
}

if (!Array.isArray(options.include)) {
throw new TypeError("Model option 'exclude' not of type 'array'");
}

if (!Array.isArray(options.exclude)) {
throw new TypeError("Model option option 'exclude' not of type 'array'");
}
checkTypeOptional('title', options.title, 'string');
checkTypeOptional('description', options.description, 'string');
checkTypeRequired('include', options.include, 'array');
checkTypeRequired('exclude', options.exclude, 'array');

if (options.include.length > 0 && options.exclude.length > 0) {
throw new Error("Model options 'include' and 'exclude' are mutually exclusive");
}

if (typeof options.associations !== 'boolean') {
throw new TypeError("Model option 'associations' not of type 'boolean'");
}

if (!Array.isArray(options.includeAssociations)) {
throw new TypeError("Model option 'includeAssociations' not of type 'array'");
}

if (!Array.isArray(options.excludeAssociations)) {
throw new TypeError("Model option option 'excludeAssociations' not of type 'array'");
}
checkTypeRequired('associations', options.associations, 'boolean');
checkTypeRequired('includeAssociations', options.includeAssociations, 'array');
checkTypeRequired('excludeAssociations', options.excludeAssociations, 'array');

if (options.includeAssociations.length > 0 && options.excludeAssociations.length > 0) {
throw new Error(
Expand Down Expand Up @@ -525,11 +500,7 @@ class SchemaManager {
return null;
}

if (typeof description !== 'string') {
throw new TypeError(
`User defined 'description' property for sequelize attribute '${attributeName}' must be of type 'string'`,
);
}
checkTypeRequired('description', description, 'string');

return {
description,
Expand All @@ -555,11 +526,7 @@ class SchemaManager {
return null;
}

if (typeof comment !== 'string') {
throw new TypeError(
`User defined 'comment' property for sequelize attribute '${attributeName}' must be of type 'string'`,
);
}
checkTypeRequired('comment', comment, 'string');

return _strategy.get(this).getPropertyComment(comment);
}
Expand Down Expand Up @@ -587,22 +554,15 @@ class SchemaManager {
}

if (readOnly) {
if (typeof readOnly !== 'boolean') {
throw new TypeError(
`Custom property 'readOnly' for sequelize attribute '${attributeName}' must be of type 'boolean'`,
);
}
checkTypeRequired('readOnly', readOnly, 'boolean');

return {
readOnly: true,
};
}

if (typeof writeOnly !== 'boolean') {
throw new TypeError(
`Custom property 'writeOnly' for sequelize attribute '${attributeName}' must be of type 'boolean'`,
);
}
// still here so writeOnly
checkTypeRequired('writeOnly', writeOnly, 'boolean');

return {
writeOnly: true,
Expand Down
61 changes: 61 additions & 0 deletions lib/utils/type-checks.js
@@ -0,0 +1,61 @@
/**
* DRY function for checking if passed value is of expected type.
*
* @param {string} name Name of setting to check
* @param {object} value Value to check
* @param {string} type Type to match
* @param {string} errorPrefix String to prepend thrown error with
* @returns {bool}
* @throws {TypeError}
*/
function checkType(name, value, type, errorPrefix) {
if (type === 'array' && !Array.isArray(value)) {
throw new TypeError(`${errorPrefix} configuration setting '${name}' not of type '${type}'`);
}

// eslint-disable-next-line valid-typeof
if (type !== 'array' && typeof value !== type) {
throw new TypeError(`${errorPrefix} configuration setting '${name}' not of type '${type}'`);
}

return true;
}

/**
* Checks if optional value is of expected type.
*
* @param {string} name Object to check
* @param {object} value Object to check
* @param {string} type Property type
* @returns {bool}
* @throws {TypeError}
*/
const checkTypeOptional = function(name, value, type) {
if (value === null) {
return true;
}

return checkType(name, value, type, 'Optional');
};

/**
* Checks if required value is of expected type.
*
* @param {string} name Object to check
* @param {object} value Object to check
* @param {string} type Property type
* @returns {bool}
* @throws {TypeError}
*/
const checkTypeRequired = function(name, value, type) {
if (value === null) {
throw new TypeError(`Required configuration setting '${name}' is missing`);
}

return checkType(name, value, type, 'Required');
};

module.exports = {
checkTypeOptional,
checkTypeRequired,
};
86 changes: 86 additions & 0 deletions test/utils/type-checks.test.js
@@ -0,0 +1,86 @@
/**
* Please note that `valiate()` is being tested through the two methods.
*/
const { checkTypeOptional, checkTypeRequired } = require('../../lib/utils/type-checks');

describe('input-validation', function() {
// --------------------------------------------------------------------------
// test checkTypeOptional()
// --------------------------------------------------------------------------
describe('checkTypeOptional', function() {
it(`returns true for null values`, function() {
expect(checkTypeOptional('name', null, 'string')).toBe(true);
});

it(`returns true when passed value matches type string`, function() {
expect(checkTypeOptional('name', 'some-string', 'string')).toBe(true);
});

it(`returns true when passed value matches type booelan`, function() {
expect(checkTypeOptional('name', true, 'boolean')).toBe(true);
});

it(`returns true when passed value matches type array`, function() {
expect(checkTypeOptional('name', [1, 2, 3], 'array')).toBe(true);
});

it('throws an error for mismatching string type', function() {
expect(() => {
checkTypeOptional('name', 123, 'string');
}).toThrow("Optional configuration setting 'name' not of type 'string'");
});

it('throws an error for mismatching boolean type', function() {
expect(() => {
checkTypeOptional('name', 123, 'boolean');
}).toThrow("Optional configuration setting 'name' not of type 'boolean'");
});

it('throws an error for mismatching array type', function() {
expect(() => {
checkTypeOptional('name', 123, 'array');
}).toThrow("Optional configuration setting 'name' not of type 'array'");
});
});

// --------------------------------------------------------------------------
// test checkTypeOptional()
// --------------------------------------------------------------------------
describe('checkTypeRequired', function() {
it('throws an error if value is missing', function() {
expect(() => {
checkTypeRequired('name', null, 'string');
}).toThrow("Required configuration setting 'name' is missing");
});

it(`returns true when passed value matches type string`, function() {
expect(checkTypeRequired('name', 'some-string', 'string')).toBe(true);
});

it(`returns true when passed value matches type booelan`, function() {
expect(checkTypeRequired('name', true, 'boolean')).toBe(true);
});

it(`returns true when passed value matches type array`, function() {
expect(checkTypeRequired('name', [1, 2, 3], 'array')).toBe(true);
});

it('throws an error for mismatching string type', function() {
expect(() => {
checkTypeRequired('name', 123, 'string');
}).toThrow("Required configuration setting 'name' not of type 'string'");
});

it('throws an error for mismatching boolean type', function() {
expect(() => {
checkTypeRequired('name', 123, 'boolean');
}).toThrow("Required configuration setting 'name' not of type 'boolean'");
});

it('throws an error for mismatching array type', function() {
expect(() => {
checkTypeRequired('name', 123, 'array');
}).toThrow("Required configuration setting 'name' not of type 'array'");
});
});
});

0 comments on commit 4332565

Please sign in to comment.