Skip to content

Commit d5e88f2

Browse files
authored
Add custom error for strict mode (#95)
* Allow custom error class for strict mode * Cover custom error class with tests * Make linter happy * Bump minor * Move validation error class decision
1 parent 6de520a commit d5e88f2

File tree

9 files changed

+184
-51
lines changed

9 files changed

+184
-51
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 1.6.0 - 2019-08-27
2+
Enhancements:
3+
* Allow custom error class to static mode
4+
15
## 1.5.0 - 2019-07-08
26
Enhancements:
37
* Add `buildStrict` static method

dist/structure.js

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,13 @@ return /******/ (function(modules) { // webpackBootstrap
8787
var Serialization = __webpack_require__(33);
8888
var Validation = __webpack_require__(6);
8989
var Initialization = __webpack_require__(18);
90+
var StrictMode = __webpack_require__(36);
9091
var Errors = __webpack_require__(23);
9192

9293
var _require = __webpack_require__(15),
9394
SCHEMA = _require.SCHEMA;
9495

95-
var _require2 = __webpack_require__(36),
96+
var _require2 = __webpack_require__(38),
9697
attributeDescriptorFor = _require2.attributeDescriptorFor,
9798
attributesDescriptorFor = _require2.attributesDescriptorFor;
9899

@@ -117,19 +118,7 @@ return /******/ (function(modules) { // webpackBootstrap
117118
}
118119
});
119120

120-
function buildStrict(constructorArgs) {
121-
var instance = new WrapperClass(constructorArgs);
122-
123-
var _instance$validate = instance.validate(),
124-
valid = _instance$validate.valid,
125-
errors = _instance$validate.errors;
126-
127-
if (!valid) throw Errors.invalidAttributes(errors);
128-
129-
return instance;
130-
}
131-
132-
WrapperClass.buildStrict = buildStrict;
121+
define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(WrapperClass, schemaOptions));
133122

134123
if (WrapperClass[SCHEMA]) {
135124
schema = Object.assign({}, WrapperClass[SCHEMA], schema);
@@ -852,12 +841,6 @@ return /******/ (function(modules) { // webpackBootstrap
852841

853842
'use strict';
854843

855-
function invalidAttributes(errors) {
856-
var error = new Error('Invalid Attributes');
857-
error.details = errors;
858-
return error;
859-
}
860-
861844
module.exports = {
862845
classAsSecondParam: function classAsSecondParam(ErroneousPassedClass) {
863846
return new Error('You passed the structure class as the second parameter of attributes(). The expected usage is `attributes(schema)(' + (ErroneousPassedClass.name || 'StructureClass') + ')`.');
@@ -874,7 +857,9 @@ return /******/ (function(modules) { // webpackBootstrap
874857
invalidType: function invalidType(attributeName) {
875858
return new TypeError('Attribute type must be a constructor or the name of a dynamic type: ' + attributeName + '.');
876859
},
877-
invalidAttributes: invalidAttributes
860+
invalidAttributes: function invalidAttributes(errors, StructureValidationError) {
861+
return new StructureValidationError(errors);
862+
}
878863
};
879864

880865
/***/ },
@@ -1217,6 +1202,64 @@ return /******/ (function(modules) { // webpackBootstrap
12171202

12181203
'use strict';
12191204

1205+
var Errors = __webpack_require__(23);
1206+
var DefaultValidationError = __webpack_require__(37);
1207+
1208+
exports.buildStrictDescriptorFor = function buildStrictDescriptorFor(StructureClass, schemaOptions) {
1209+
var StructureValidationError = schemaOptions.strictValidationErrorClass || DefaultValidationError;
1210+
1211+
return {
1212+
value: function buildStrict(constructorArgs) {
1213+
var instance = new StructureClass(constructorArgs);
1214+
1215+
var _instance$validate = instance.validate(),
1216+
valid = _instance$validate.valid,
1217+
errors = _instance$validate.errors;
1218+
1219+
if (!valid) {
1220+
throw Errors.invalidAttributes(errors, StructureValidationError);
1221+
}
1222+
1223+
return instance;
1224+
}
1225+
};
1226+
};
1227+
1228+
/***/ },
1229+
/* 37 */
1230+
/***/ function(module, exports) {
1231+
1232+
'use strict';
1233+
1234+
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
1235+
1236+
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
1237+
1238+
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
1239+
1240+
var DefautValidationError = function (_Error) {
1241+
_inherits(DefautValidationError, _Error);
1242+
1243+
function DefautValidationError(errors) {
1244+
_classCallCheck(this, DefautValidationError);
1245+
1246+
var _this = _possibleConstructorReturn(this, (DefautValidationError.__proto__ || Object.getPrototypeOf(DefautValidationError)).call(this, 'Invalid Attributes'));
1247+
1248+
_this.details = errors;
1249+
return _this;
1250+
}
1251+
1252+
return DefautValidationError;
1253+
}(Error);
1254+
1255+
module.exports = DefautValidationError;
1256+
1257+
/***/ },
1258+
/* 38 */
1259+
/***/ function(module, exports, __webpack_require__) {
1260+
1261+
'use strict';
1262+
12201263
var _require = __webpack_require__(9),
12211264
isObject = _require.isObject;
12221265

docs/strict-mode.md

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,43 @@ var user = User.buildStrict({
2121
// { message: '"name" is required', path: 'name' },
2222
// { message: '"age" must be a number', path: 'age' }
2323
// ]
24-
```
24+
```
25+
26+
## Custom error
27+
28+
Normally `buildStrict` will throw a default `Error` when attributes are invalid but you can customize the error class that will be used passing a `strictValidationErrorClass` to the _second_ parameter of the `attributes` function.
29+
30+
The value of `strictValidationErrorClass` should be a class that accepts an array of erros in the constructor.
31+
32+
```js
33+
const { attributes } = require('structure');
34+
35+
class InvalidBookError extends Error {
36+
constructor(errors) {
37+
super('Wait, this book is not right');
38+
this.code = 'INVALID_BOOK';
39+
this.errors = errors;
40+
}
41+
}
42+
43+
const Book = attributes({
44+
name: {
45+
type: String,
46+
required: true
47+
},
48+
year: Number
49+
}, {
50+
strictValidationErrorClass: InvalidBookError
51+
})(class Book {});
52+
53+
var book = Book.buildStrict({
54+
year: 'Twenty'
55+
});
56+
57+
// InvalidBookError: Wait, this book is not right
58+
// code: 'INVALID_BOOK'
59+
// details: [
60+
// { message: '"name" is required', path: 'name' },
61+
// { message: '"year" must be a number', path: 'year' }
62+
// ]
63+
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "structure",
3-
"version": "1.5.0",
3+
"version": "1.6.0",
44
"description": "A simple schema/attributes library built on top of modern JavaScript",
55
"main": "src/index.js",
66
"browser": "dist/structure.js",

src/attributes/decorator.js

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const Schema = require('../schema');
22
const Serialization = require('../serialization');
33
const Validation = require('../validation');
44
const Initialization = require('../initialization');
5+
const StrictMode = require('../strictMode');
56
const Errors = require('../errors');
67
const { SCHEMA } = require('../symbols');
78
const {
@@ -28,16 +29,7 @@ function attributesDecorator(schema, schemaOptions = {}) {
2829
}
2930
});
3031

31-
function buildStrict(constructorArgs){
32-
const instance = new WrapperClass(constructorArgs);
33-
34-
const {valid, errors} = instance.validate();
35-
if(!valid) throw Errors.invalidAttributes(errors);
36-
37-
return instance;
38-
}
39-
40-
WrapperClass.buildStrict = buildStrict;
32+
define(WrapperClass, 'buildStrict', StrictMode.buildStrictDescriptorFor(WrapperClass, schemaOptions));
4133

4234
if(WrapperClass[SCHEMA]) {
4335
schema = Object.assign({}, WrapperClass[SCHEMA], schema);

src/errors/DefaultValidationError.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
class DefautValidationError extends Error {
2+
constructor(errors) {
3+
super('Invalid Attributes');
4+
this.details = errors;
5+
}
6+
}
7+
8+
module.exports = DefautValidationError;
Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,8 @@
1-
function invalidAttributes(errors){
2-
let error = new Error('Invalid Attributes');
3-
error.details = errors;
4-
return error;
5-
}
6-
71
module.exports = {
82
classAsSecondParam: (ErroneousPassedClass) => new Error(`You passed the structure class as the second parameter of attributes(). The expected usage is \`attributes(schema)(${ ErroneousPassedClass.name || 'StructureClass' })\`.`),
93
nonObjectAttributes: () => new TypeError('#attributes can\'t be set to a non-object.'),
104
arrayOrIterable: () => new TypeError('Value must be iterable or array-like.'),
115
missingDynamicType: (attributeName) => new Error(`Missing dynamic type for attribute: ${ attributeName }.`),
126
invalidType: (attributeName) => new TypeError(`Attribute type must be a constructor or the name of a dynamic type: ${ attributeName }.`),
13-
invalidAttributes
7+
invalidAttributes: (errors, StructureValidationError) => new StructureValidationError(errors)
148
};

src/strictMode/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const Errors = require('../errors');
2+
const DefaultValidationError = require('../errors/DefaultValidationError');
3+
4+
exports.buildStrictDescriptorFor = function buildStrictDescriptorFor(StructureClass, schemaOptions) {
5+
const StructureValidationError = schemaOptions.strictValidationErrorClass || DefaultValidationError;
6+
7+
return {
8+
value: function buildStrict(constructorArgs) {
9+
const instance = new StructureClass(constructorArgs);
10+
11+
const { valid, errors } = instance.validate();
12+
13+
if (!valid) {
14+
throw Errors.invalidAttributes(errors, StructureValidationError);
15+
}
16+
17+
return instance;
18+
}
19+
};
20+
};

test/unit/instanceAndUpdate.spec.js

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -143,15 +143,48 @@ describe('instantiating a structure', () => {
143143

144144
describe('instantiating a structure with buildStrict', () => {
145145
context('when object is invalid', () => {
146-
it('throw an error', () => {
147-
let errorDetails = [{
148-
message: '"password" is required',
149-
path: 'password'
150-
}];
151-
152-
expect(() => {
153-
User.buildStrict();
154-
}).to.throw(Error, 'Invalid Attributes').with.property('details').that.deep.equals(errorDetails);
146+
context('when using default error class', () => {
147+
it('throws a default error', () => {
148+
let errorDetails = [{
149+
message: '"password" is required',
150+
path: 'password'
151+
}];
152+
153+
expect(() => {
154+
User.buildStrict();
155+
}).to.throw(Error, 'Invalid Attributes').with.property('details').that.deep.equals(errorDetails);
156+
});
157+
});
158+
159+
context('when using custom error class', () => {
160+
var UserWithCustomError;
161+
var InvalidUser;
162+
163+
beforeEach(() => {
164+
InvalidUser = class InvalidUser extends Error {
165+
constructor(errors) {
166+
super('There is something wrong with this user');
167+
this.errors = errors;
168+
}
169+
};
170+
171+
UserWithCustomError = attributes({
172+
name: {
173+
type: String,
174+
minLength: 3
175+
}
176+
}, {
177+
strictValidationErrorClass: InvalidUser
178+
})(class UserWithCustomError {});
179+
});
180+
181+
it('throws a custom error', () => {
182+
expect(() => {
183+
UserWithCustomError.buildStrict({
184+
name: 'JJ'
185+
});
186+
}).to.throw(InvalidUser, 'There is something wrong with this user');
187+
});
155188
});
156189
});
157190

@@ -160,8 +193,8 @@ describe('instantiating a structure', () => {
160193
const user = User.buildStrict({
161194
password: 'My password'
162195
});
163-
164-
expect(user.password).to.equal('My password');
196+
197+
expect(user.password).to.equal('My password');
165198
});
166199
});
167200
});

0 commit comments

Comments
 (0)