Skip to content

Commit

Permalink
Add injection
Browse files Browse the repository at this point in the history
  • Loading branch information
Gnucki committed Apr 18, 2018
1 parent a74ede8 commit 81ea3eb
Show file tree
Hide file tree
Showing 19 changed files with 302 additions and 98 deletions.
68 changes: 41 additions & 27 deletions src/Injector.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
'use strict';

const BadDefinitionError = require('./errors/BadDefinitionError');
const BadDependencyError = require('./errors/BadDependencyError');
const MissingDependencyError = require('./errors/MissingDependencyError');
const NotDefinedDependencyError = require('./errors/NotDefinedDependencyError');
const UnexpectedError = require('./errors/UnexpectedError');
const BadDefinitionError = require('./errors/BadDefinition');
const BadDependencyError = require('./errors/BadDependency');
const DependencyError = require('./errors/Dependency');
const MissingDependencyError = require('./errors/MissingDependency');
const NotDefinedDependencyError = require('./errors/NotDefinedDependency');
const UnexpectedError = require('./errors/Unexpected');

const validator = Symbol('validator');
const injectionDefiners = Symbol('injectionDefiners');
Expand Down Expand Up @@ -94,6 +95,8 @@ class Injector {
* @param {string} property - The property name.
* @param {*} dependency - The dependency.
* @returns {*} The injected object.
* @throws {DependencyError} On error related to a dependency.
* @throws {UnexpectedError} On unexpected error.
*/
inject(object, property, dependency) {
return this[injectDependencies](object, {[property]: dependency});
Expand All @@ -105,7 +108,8 @@ class Injector {
* @param {Object<*>} dependencies - The dependencies indexed by property name.
* @param {boolean} validate - Whether or not to validate dependencies.
* @returns {*} The injected object.
* @throws {Error} On validation failure.
* @throws {DependencyError} On error related to a dependency.
* @throws {UnexpectedError} On unexpected error.
*/
injectSet(object, dependencies, validate) {
const injectedObject = this[injectDependencies](object, dependencies);
Expand All @@ -120,7 +124,8 @@ class Injector {
/**
* Validate dependencies.
* @param {Object} object - The object.
* @throws {Error} On validation failure.
* @throws {DependencyError} On error related to a dependency.
* @throws {UnexpectedError} On unexpected error.
*/
validate(object) {
const needed = this[getDependenciesDefinitions](object);
Expand All @@ -133,19 +138,33 @@ class Injector {
const propertyDefinition = this[getDependencyDefinition](object, property);
const propertyName = this[getTargetProperty](propertyDefinition, property);

if (!(propertyName in object)) {
throw new MissingDependencyError(property);
}

Object.keys(this[validationProcessors]).forEach((key) => {
// Retrieve dependency values to validate.
const values = Object.keys(this[validationProcessors]).reduce((list, key) => {
try {
this[validationProcessors][key].validate(
object[propertyName],
return this[validationProcessors][key].getValues(
list,
propertyDefinition[key]
);
} catch (error) {
if (error.expectedType || error.expectedValues) {
throw new BadDependencyError(error);
if (error instanceof MissingDependencyError) {
throw new DependencyError(property, error);
}
throw new UnexpectedError(error);
}
}, [object[propertyName]]);

// Validate dependency values.
Object.keys(this[validationProcessors]).forEach((key) => {
try {
values.forEach((value) => {
this[validationProcessors][key].validate(
value,
propertyDefinition[key]
);
});
} catch (error) {
if (error instanceof BadDependencyError) {
throw new DependencyError(property, error);
}
throw new UnexpectedError(error);
}
Expand Down Expand Up @@ -174,9 +193,7 @@ class Injector {
* @param {Object} object - The object.
* @param {string} property - The property name.
* @returns {Object|null} The definitions or null if no one is defined.
* @throws {NotDefinedDependencyError} On not defined dependency.
* @throws {BadDefinitionError} On bad definition.
* @throws {UnexpectedError} On unexpected error.
* @throws {DependencyError} On error related to a dependency.
* @protected
*/
[getDependencyDefinition](object, property) {
Expand All @@ -188,18 +205,15 @@ class Injector {
}

if (needed && !(property in needed)) {
throw new NotDefinedDependencyError(property, Object.keys(needed));
throw new NotDefinedDependencyError(Object.keys(needed));
}

return this[definitionValidator].validate(needed[property]);
} catch (error) {
if (error.expectedType || error.expectedValues) {
throw new BadDefinitionError(error);
}
if (error instanceof NotDefinedDependencyError) {
throw error;
}
throw new UnexpectedError(error);
const forwardedError = error instanceof NotDefinedDependencyError
? error
: new BadDefinitionError(error);
throw new DependencyError(property, forwardedError);
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/ValidationProcessor.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
'use strict';

const BadDependencyError = require('./errors/BadDependency');
const MissingDependencyError = require('./errors/MissingDependency');

const throwBadDependencyError = Symbol('throwBadDependencyError');
const throwMissingDependencyError = Symbol('throwMissingDependencyError');

/**
* @class ValidationProcessor
* @abstract
Expand All @@ -22,6 +28,27 @@ class ValidationProcessor {
validate(value, definition) { // eslint-disable-line no-unused-vars
throw new Error('\'validate\' method must be implemented');
}

/**
* Throw a bad dependency error.
* @param {Error|string} error - The original error or message.
* @throws BadDependencyError
*/
[throwBadDependencyError](error) {
const err = typeof error === 'string' ? new Error(error) : error;
throw new BadDependencyError(err);
}

/**
* Throw a missing dependency error.
* @throws MissingDependencyError
*/
[throwMissingDependencyError]() {
throw new MissingDependencyError();
}
}

ValidationProcessor.throwBadDependencyError = throwBadDependencyError;
ValidationProcessor.throwMissingDependencyError = throwMissingDependencyError;

module.exports = ValidationProcessor;
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const error = Symbol('error');

/**
* @class errors/BadDefinitionError
* @class BadDefinitionError
*/
module.exports = class BadDefinitionError extends TypeError {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const error = Symbol('error');

/**
* @class errors/BadDependencyError
* @class BadDependencyError
*/
module.exports = class BadDependencyError extends TypeError {
/**
Expand Down
29 changes: 29 additions & 0 deletions src/errors/Dependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const error = Symbol('error');

/**
* @class DependencyError
*/
module.exports = class DependencyError extends Error {
/**
* Constructor.
* @constructs
* @param {string} name - The dependency name.
* @param {Error} originalError - The original validation error.
*/
constructor(name, originalError) {
super(`[${name}]: ${originalError.message}`);

this.name = 'DependencyError';
this[error] = originalError;
}

/**
* Original error.
* @type {Error}
*/
get error() {
return this[error];
}
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
'use strict';

/**
* @class errors/MissingDependencyError
* @class MissingDependencyError
*/
module.exports = class MissingDependencyError extends Error {
/**
* Constructor.
* @constructs
* @param {string} name - The dependency name.
*/
constructor(name) {
super(`'${name}' dependency has not been injected`);
constructor() {
super('Dependency has not been injected');

this.name = 'MissingDependencyError';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
'use strict';

/**
* @class errors/NotDefinedDependencyError
* @class NotDefinedDependencyError
*/
module.exports = class NotDefinedDependencyError extends Error {
/**
* Constructor.
* @constructs
* @param {string} name - The dependency name.
* @param {Array<string>} neededDependencies - The names of the needed dependencies.
*/
constructor(name, neededDependencies) {
super(`'${name}' dependency is not defined in the list of needed dependencies ['${neededDependencies.join('\', \'')}']`);
constructor(neededDependencies) {
super(`Dependency is not defined in the list of needed dependencies ['${neededDependencies.join('\', \'')}']`);

this.name = 'NotDefinedDependencyError';
}
Expand Down
File renamed without changes.
10 changes: 5 additions & 5 deletions src/validation-processors/Optional.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ class OptionalValidationProcessor extends ValidationProcessor {
}

/** @inheritdoc */
validate(value, definition) {
if (value === null || value === undefined) {
throw Error('...');
getValues(values, definition) {
const value = values[0];
if (!definition && values.length <= 1 && (value === null || value === undefined)) {
this[ValidationProcessor.throwMissingDependencyError]();
}

return definition || name;
return values;
}
}

Expand Down
17 changes: 14 additions & 3 deletions test/fixtures/typeValidationProcessor.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
'use strict';

const BadDependencyError = require('../../src/errors/BadDependency');
const MissingDependencyError = require('../../src/errors/MissingDependency');

module.exports = {
schema: {
type: 'string'
},
getValues: function getValues(values) {
if (values[0] === 'failed') {
throw new Error('failed');
}
if (values[0] === undefined) {
throw new MissingDependencyError();
}
return values;
},
validate: function validate(value, definition) {
if (!value) {
if (value === 'unexpected') {
throw new Error('unexpected');
}
if (typeof value !== definition) { // eslint-disable-line valid-typeof
const error = new TypeError('bad type');
error.expectedType = definition;
const error = new BadDependencyError(new Error('bad type'));
throw error;
}
}
Expand Down

0 comments on commit 81ea3eb

Please sign in to comment.