Skip to content

Commit

Permalink
Merge 1a7c4e9 into b018f1f
Browse files Browse the repository at this point in the history
  • Loading branch information
talyssonoc committed Mar 25, 2018
2 parents b018f1f + 1a7c4e9 commit 25bc98f
Show file tree
Hide file tree
Showing 13 changed files with 221 additions and 135 deletions.
15 changes: 8 additions & 7 deletions .eslintrc
Expand Up @@ -9,13 +9,14 @@
"mocha"
],
"rules": {
"comma-spacing": [2, { "before": false, "after": true }],
"indent": [2, 2],
"linebreak-style": [2, "unix"],
"comma-spacing": ["error", { "before": false, "after": true }],
"complexity": ["error", 3],
"indent": ["error", 2],
"linebreak-style": ["error", "unix"],
"object-shorthand": ["error", "always", { "avoidQuotes": true, "avoidExplicitReturnArrows": true }],
"mocha/no-exclusive-tests": 2,
"mocha/no-skipped-tests": 2,
"quotes": [2, "single"],
"semi": [2, "always"]
"mocha/no-exclusive-tests": "error",
"mocha/no-skipped-tests": "error",
"quotes": ["error", "single"],
"semi": ["error", "always"]
}
}
3 changes: 1 addition & 2 deletions src/attributes/decorator.js
Expand Up @@ -22,8 +22,7 @@ function attributesDecorator(schema, schemaOptions = {}) {
const instance = Reflect.construct(target, constructorArgs, newTarget);
const passedAttributes = constructorArgs[0] || {};

instance.attributes = Initializer.initialize(passedAttributes, schema, instance, Initializer.natives);
instance.attributes = Initializer.initialize(passedAttributes, schema, instance, Initializer.functions);
Initializer.initialize(passedAttributes, schema, instance);

return instance;
}
Expand Down
21 changes: 13 additions & 8 deletions src/attributes/descriptors.js
@@ -1,8 +1,7 @@
const { isObject } = require('lodash');
const Errors = require('../errors');
const { ATTRIBUTES } = require('../symbols');

const OBJECT_TYPE = 'object';

exports.attributeDescriptorFor = function attributeDescriptorFor(attributeName, schema) {
return {
enumerable: true,
Expand All @@ -23,15 +22,11 @@ exports.attributesDescriptorFor = function attributesDescriptorFor(schema) {
},

set(newAttributes) {
if(!newAttributes || typeof newAttributes !== OBJECT_TYPE) {
if(!isObject(newAttributes)) {
throw Errors.nonObjectAttributes();
}

const attributes = Object.create(null);

for(let attrName in schema) {
attributes[attrName] = schema[attrName].coerce(newAttributes[attrName]);
}
const attributes = coerceAttributes(newAttributes, schema);

Object.defineProperty(this, ATTRIBUTES, {
configurable: true,
Expand All @@ -40,3 +35,13 @@ exports.attributesDescriptorFor = function attributesDescriptorFor(schema) {
}
};
};

function coerceAttributes(newAttributes, schema) {
const attributes = Object.create(null);

for(let attrName in schema) {
attributes[attrName] = schema[attrName].coerce(newAttributes[attrName]);
}

return attributes;
}
47 changes: 26 additions & 21 deletions src/attributes/initializer.js
@@ -1,37 +1,42 @@
const { isFunction } = require('lodash');

const natives = Object.assign({}, {
getValue(attr) {
return attr.default;
const nativesInitializer = Object.assign({}, {
getValue(attrDescriptor) {
return attrDescriptor.default;
},
shouldInitialize(attr) {
return !isFunction(attr.default);
shouldInitialize(attrDescriptor) {
return !isFunction(attrDescriptor.default);
}
});

const functions = Object.assign({}, {
getValue(attr, instance) {
return attr.default(instance);
const derivedInitializer = Object.assign({}, {
getValue(attrDescriptor, instance) {
return attrDescriptor.default(instance);
},
shouldInitialize(attr) {
return isFunction(attr.default);
shouldInitialize(attrDescriptor) {
return isFunction(attrDescriptor.default);
}
});

function initialize(attributes, schema, instance, Initializer) {
for(let attr in schema) {
if (!Initializer.shouldInitialize(schema[attr])) {
continue;
}
function initialize(attributes, schema, instance) {
instance.attributes = initializeWith(nativesInitializer, attributes, schema, instance);
instance.attributes = initializeWith(derivedInitializer, attributes, schema, instance);

return attributes;
}

attributes[attr] = (
attributes[attr] === undefined
? Initializer.getValue(schema[attr], instance)
: attributes[attr]
);
function initializeWith(Initializer, attributes, schema, instance) {
for(let attrName in schema) {
const value = attributes[attrName];

if(value === undefined && Initializer.shouldInitialize(schema[attrName])) {
attributes[attrName] = Initializer.getValue(schema[attrName], instance);
}
}

return attributes;
}

module.exports = { initialize, natives, functions };


module.exports = { initialize, nativesInitializer, derivedInitializer };
60 changes: 41 additions & 19 deletions src/coercion/array.js
Expand Up @@ -2,30 +2,52 @@ const Errors = require('../errors');
const getType = require('../typeResolver');

module.exports = function arrayCoercionFor(typeDescriptor, itemTypeDescriptor) {
return function coerceArray(value) {
if(value === undefined) {
return function coerceArray(rawValue) {
if(rawValue === undefined) {
return;
}

if(value === null || (value.length === undefined && !value[Symbol.iterator])) {
throw Errors.arrayOrIterable();
}

if(Array.isArray(value)) {
value = value.slice();
} else {
if(value[Symbol.iterator]) {
value = Array(...value);
}
}
validateIfIterable(rawValue);

const type = getType(typeDescriptor);
const coercedValue = new type();
const items = extractItems(rawValue);

for(let i = 0; i < value.length; i++) {
coercedValue.push(itemTypeDescriptor.coerce(value[i]));
}
const instance = createInstance(typeDescriptor);

return coercedValue;
return fillInstance(instance, items, itemTypeDescriptor);
};
};

function validateIfIterable(value) {
if(!isIterable(value)) {
throw Errors.arrayOrIterable();
}
}

function isIterable(value) {
return value != null && (value.length != null || value[Symbol.iterator]);
}

function extractItems(iterable) {
if(!Array.isArray(iterable) && iterable[Symbol.iterator]) {
return Array(...iterable);
}

return iterable;
}

function createInstance(typeDescriptor) {
const type = getType(typeDescriptor);
return new type();
}

function fillInstance(instance, items, itemTypeDescriptor) {
for(let i = 0; i < items.length; i++) {
instance.push(coerceItem(itemTypeDescriptor, items[i]));
}

return instance;
}

function coerceItem(itemTypeDescriptor, item) {
return itemTypeDescriptor.coerce(item);
}
3 changes: 3 additions & 0 deletions src/coercion/boolean.js
@@ -1,5 +1,8 @@
const { isBoolean } = require('lodash');

module.exports = {
type: Boolean,
test: isBoolean,
coerce(value) {
return Boolean(value);
}
Expand Down
6 changes: 5 additions & 1 deletion src/coercion/generic.js
Expand Up @@ -8,10 +8,14 @@ module.exports = function genericCoercionFor(typeDescriptor) {

const type = getType(typeDescriptor);

if(value instanceof type) {
if(!needsCoercion(value, type)) {
return value;
}

return new type(value);
};
};

function needsCoercion(value, type) {
return !(value instanceof type);
}
22 changes: 17 additions & 5 deletions src/coercion/index.js
Expand Up @@ -12,8 +12,16 @@ exports.for = function coercionFor(typeDescriptor, itemTypeDescriptor) {
return arrayCoercionFor(typeDescriptor, itemTypeDescriptor);
}

const coercion = types.find((c) => c.type === typeDescriptor.type);
const coercion = getCoercion(typeDescriptor);

return createCoercionFunction(coercion, typeDescriptor);
};

function getCoercion(typeDescriptor) {
return types.find((c) => c.type === typeDescriptor.type);
}

function createCoercionFunction(coercion, typeDescriptor) {
if(!coercion) {
return genericCoercionFor(typeDescriptor);
}
Expand All @@ -23,10 +31,14 @@ exports.for = function coercionFor(typeDescriptor, itemTypeDescriptor) {
return;
}

if(coercion.test && coercion.test(value)) {
return value;
if(needsCoercion(value, coercion)) {
return coercion.coerce(value);
}

return coercion.coerce(value);
return value;
};
};
}

function needsCoercion(value, coercion) {
return !coercion.test(value);
}
1 change: 0 additions & 1 deletion src/errors.js
Expand Up @@ -2,7 +2,6 @@ module.exports = {
classAsSecondParam: (ErroneousPassedClass) => new Error(`You passed the structure class as the second parameter of attributes(). The expected usage is \`attributes(schema)(${ ErroneousPassedClass.name || 'StructureClass' })\`.`),
nonObjectAttributes: () => new TypeError('#attributes can\'t be set to a non-object.'),
arrayOrIterable: () => new TypeError('Value must be iterable or array-like.'),
missingType: (attributeName) => new Error(`Missing type for attribute: ${ attributeName }.`),
missingDynamicType: (attributeName) => new Error(`Missing dynamic type for attribute: ${ attributeName }.`),
invalidType: (attributeName) => new TypeError(`Attribute type must be a constructor or the name of a dynamic type: ${ attributeName }.`)
};
41 changes: 27 additions & 14 deletions src/serialization/serialize.js
Expand Up @@ -7,33 +7,46 @@ function serialize(structure) {
}

const schema = structure[SCHEMA];

return serializeStructure(structure, schema);
}

function getTypeSchema(typeDescriptor) {
return getType(typeDescriptor)[SCHEMA];
}

function serializeStructure(structure, schema) {
const serializedStructure = Object.create(null);

for(let attrName in schema) {
let attribute = structure[attrName];

if(attribute == null) {
continue;
if(attribute != null) {
serializedStructure[attrName] = serializeAttribute(attribute, attrName, schema);
}
}

let serializedValue;
return serializedStructure;
}

if(schema[attrName].itemType && getTypeSchema(schema[attrName].itemType)) {
serializedValue = attribute.map(serialize);
} else if(getTypeSchema(schema[attrName])) {
serializedValue = serialize(attribute);
} else {
serializedValue = attribute;
}
function serializeAttribute(attribute, attrName, schema) {
if(isArrayType(schema, attrName)) {
return attribute.map(serialize);
}

serializedStructure[attrName] = serializedValue;
if(isNestedSchema(schema, attrName)) {
return serialize(attribute);
}

return serializedStructure;
return attribute;
}

function getTypeSchema(typeDescriptor) {
return getType(typeDescriptor)[SCHEMA];
function isArrayType(schema, attrName) {
return schema[attrName].itemType && getTypeSchema(schema[attrName].itemType);
}

function isNestedSchema(schema, attrName) {
return getTypeSchema(schema[attrName]);
}

module.exports = serialize;

0 comments on commit 25bc98f

Please sign in to comment.