Skip to content

Commit 09b0c76

Browse files
authored
feat: add ValidationError type extending TypeError (#151)
This change adds a `ValidationError` type that extends `TypeError`. Any time a `CloudEvent` cannot be received and created with the given input, this error will be thrown. Tests have all been updated to check for the error type. Signed-off-by: Lance Ball <lball@redhat.com>
1 parent b5a6673 commit 09b0c76

19 files changed

+155
-189
lines changed

Diff for: lib/bindings/http/http_receiver.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const V03Binary = require("./receiver_binary_0_3.js");
22
const V03Structured = require("./receiver_structured_0_3.js");
33
const V1Binary = require("./receiver_binary_1.js");
44
const V1Structured = require("./receiver_structured_1.js");
5+
const ValidationError = require("../../validation_error.js");
56
const {
67
SPEC_V03,
78
SPEC_V1,
@@ -63,7 +64,7 @@ function getMode(headers) {
6364
if (headers[BINARY_HEADERS_1.ID]) {
6465
return "binary";
6566
}
66-
throw new TypeError("no cloud event detected");
67+
throw new ValidationError("no cloud event detected");
6768
}
6869

6970
function getVersion(mode, headers, body) {

Diff for: lib/bindings/http/receiver_binary.js

+7-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ const { HEADER_CONTENT_TYPE, MIME_JSON, DEFAULT_SPEC_VERSION_HEADER } =
22
require("./constants.js");
33
const Commons = require("./commons.js");
44
const CloudEvent = require("../../cloudevent.js");
5+
const ValidationError = require("../../validation_error.js");
56

67
const {
78
isDefinedOrThrow,
@@ -10,15 +11,12 @@ const {
1011

1112
function validateArgs(payload, attributes) {
1213
Array.of(payload)
13-
.filter((p) => isDefinedOrThrow(p,
14-
{ message: "payload is null or undefined" }))
15-
.filter((p) => isStringOrObjectOrThrow(p,
16-
{ message: "payload must be an object or a string" }))
14+
.filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined")))
15+
.filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or a string")))
1716
.shift();
1817

1918
Array.of(attributes)
20-
.filter((a) => isDefinedOrThrow(a,
21-
{ message: "attributes is null or undefined" }))
19+
.filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined")))
2220
.shift();
2321
}
2422

@@ -58,22 +56,18 @@ BinaryHTTPReceiver.prototype.check = function(payload, headers) {
5856
const contentTypeHeader = sanityHeaders[HEADER_CONTENT_TYPE];
5957
const noContentType = !this.allowedContentTypes.includes(contentTypeHeader);
6058
if (contentTypeHeader && noContentType) {
61-
const err = new TypeError("invalid content type");
62-
err.errors = [sanityHeaders[HEADER_CONTENT_TYPE]];
63-
throw err;
59+
throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
6460
}
6561

6662
this.requiredHeaders
6763
.filter((required) => !sanityHeaders[required])
6864
.forEach((required) => {
69-
throw new TypeError(`header '${required}' not found`);
65+
throw new ValidationError(`header '${required}' not found`);
7066
});
7167

7268
if (sanityHeaders[DEFAULT_SPEC_VERSION_HEADER] !==
7369
this.specversion) {
74-
const err = new TypeError("invalid spec version");
75-
err.errors = [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]];
76-
throw err;
70+
throw new ValidationError("invalid spec version", [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]]);
7771
}
7872

7973
// No erros! Its contains the minimum required attributes

Diff for: lib/bindings/http/receiver_binary_0_3.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const Constants = require("./constants.js");
22
const Spec = require("../../specs/spec_0_3.js");
3+
const ValidationError = require("../../validation_error.js");
34

45
const JSONParser = require("../../formats/json/parser.js");
56
const Base64Parser = require("../../formats/base64.js");
@@ -85,9 +86,7 @@ function checkDecorator(payload, headers) {
8586
.filter((header) => !allowedEncodings.includes(headers[header]))
8687
.forEach((header) => {
8788
// TODO: using forEach here seems off
88-
const err = new TypeError("unsupported datacontentencoding");
89-
err.errors = [headers[header]];
90-
throw err;
89+
throw new ValidationError("unsupported datacontentencoding");
9190
});
9291
}
9392

Diff for: lib/bindings/http/receiver_structured.js

+5-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const Constants = require("./constants.js");
22
const Commons = require("./commons.js");
33
const CloudEvent = require("../../cloudevent.js");
4+
const ValidationError = require("../../validation_error.js");
45

56
const {
67
isDefinedOrThrow,
@@ -9,15 +10,12 @@ const {
910

1011
function validateArgs(payload, attributes) {
1112
Array.of(payload)
12-
.filter((p) => isDefinedOrThrow(p,
13-
{ message: "payload is null or undefined" }))
14-
.filter((p) => isStringOrObjectOrThrow(p,
15-
{ message: "payload must be an object or string" }))
13+
.filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined")))
14+
.filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or string")))
1615
.shift();
1716

1817
Array.of(attributes)
19-
.filter((a) => isDefinedOrThrow(a,
20-
{ message: "attributes is null or undefined" }))
18+
.filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined")))
2119
.shift();
2220
}
2321

@@ -41,9 +39,7 @@ StructuredHTTPReceiver.prototype.check = function(payload, headers) {
4139
// Validation Level 1
4240
if (!this.allowedContentTypes
4341
.includes(sanityHeaders[Constants.HEADER_CONTENT_TYPE])) {
44-
const err = new TypeError("invalid content type");
45-
err.errors = [sanityHeaders[Constants.HEADER_CONTENT_TYPE]];
46-
throw err;
42+
throw new ValidationError("invalid content type", [sanityHeaders[Constants.HEADER_CONTENT_TYPE]]);
4743
}
4844

4945
// No erros! Its contains the minimum required attributes

Diff for: lib/bindings/http/unmarshaller.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
MIME_OCTET_STREAM
77
} = require("./constants.js");
88
const Commons = require("./commons.js");
9+
const ValidationError = require("../../validation_error.js");
910

1011
const STRUCTURED = "structured";
1112
const BINARY = "binary";
@@ -29,18 +30,18 @@ function resolveBindingName(payload, headers) {
2930
if (allowedStructuredContentTypes.includes(contentType)) {
3031
return STRUCTURED;
3132
}
32-
throwTypeError("structured+type not allowed", contentType);
33+
throwValidationError("structured+type not allowed", contentType);
3334
} else {
3435
// Binary
3536
if (allowedBinaryContentTypes.includes(contentType)) {
3637
return BINARY;
3738
}
38-
throwTypeError("content type not allowed", contentType);
39+
throwValidationError("content type not allowed", contentType);
3940
}
4041
}
4142

42-
function throwTypeError(msg, contentType) {
43-
const err = new TypeError(msg);
43+
function throwValidationError(msg, contentType) {
44+
const err = new ValidationError(msg);
4445
err.errors = [contentType];
4546
throw err;
4647
}
@@ -52,16 +53,16 @@ class Unmarshaller {
5253

5354
unmarshall(payload, headers) {
5455
if (!payload) {
55-
throw new TypeError("payload is null or undefined");
56+
throw new ValidationError("payload is null or undefined");
5657
}
5758
if (!headers) {
58-
throw new TypeError("headers is null or undefined");
59+
throw new ValidationError("headers is null or undefined");
5960
}
6061

6162
// Validation level 1
6263
const sanityHeaders = Commons.sanityAndClone(headers);
6364
if (!sanityHeaders[HEADER_CONTENT_TYPE]) {
64-
throw new TypeError("content-type header not found");
65+
throw new ValidationError("content-type header not found");
6566
}
6667

6768
// Resolve the binding

Diff for: lib/cloudevent.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ const Formatter = require("./formats/json/formatter.js");
77
class CloudEvent {
88
/**
99
* Creates a new CloudEvent instance
10-
* @param {Spec} [UserSpec] A CloudEvent version specification
11-
* @param {Formatter} [UserFormatter] Converts the event into a readable string
10+
* @param {Spec} [userSpec] A CloudEvent version specification
11+
* @param {Formatter} [userFormatter] Converts the event into a readable string
1212
*/
13-
constructor(UserSpec, UserFormatter) {
14-
this.spec = (UserSpec) ? new UserSpec(CloudEvent) : new Spec(CloudEvent);
15-
this.formatter = (UserFormatter) ? new UserFormatter() : new Formatter();
13+
constructor(userSpec, userFormatter) {
14+
this.spec = userSpec ? new userSpec(CloudEvent) : new Spec(CloudEvent);
15+
this.formatter = userFormatter ? new userFormatter() : new Formatter();
1616

1717
// The map of extensions
1818
this.extensions = {};

Diff for: lib/formats/json/parser.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ const {
33
isDefinedOrThrow,
44
isStringOrObjectOrThrow
55
} = require("../../utils/fun.js");
6+
const ValidationError = require("../../validation_error.js");
67

7-
const invalidPayloadTypeError =
8-
new Error("invalid payload type, allowed are: string or object");
9-
const nullOrUndefinedPayload =
10-
new Error("null or undefined payload");
8+
const invalidPayloadTypeError = new ValidationError("invalid payload type, allowed are: string or object");
9+
const nullOrUndefinedPayload = new ValidationError("null or undefined payload");
1110

1211
const asJSON = (v) => (isString(v) ? JSON.parse(v) : v);
1312

Diff for: lib/specs/spec_0_3.js

+5-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { v4: uuidv4 } = require("uuid");
22
const Ajv = require("ajv");
3+
const ValidationError = require("../validation_error.js");
34

45
const {
56
isBase64,
@@ -168,21 +169,15 @@ Spec03.prototype.check = function(ce) {
168169
const toCheck = (!ce ? this.payload : ce);
169170

170171
if (!isValidAgainstSchema(toCheck)) {
171-
const err = new TypeError("invalid payload");
172-
err.errors = isValidAgainstSchema.errors;
173-
throw err;
172+
throw new ValidationError("invalid payload", [isValidAgainstSchema.errors]);
174173
}
175174

176175
Array.of(toCheck)
177176
.filter((tc) => tc.datacontentencoding)
178177
.map((tc) => tc.datacontentencoding.toLocaleLowerCase("en-US"))
179178
.filter((dce) => !Object.keys(SUPPORTED_CONTENT_ENCODING).includes(dce))
180179
.forEach((dce) => {
181-
const err = new TypeError("invalid payload");
182-
err.errors = [
183-
`Unsupported content encoding: ${dce}`
184-
];
185-
throw err;
180+
throw new ValidationError("invalid payload", [`Unsupported content encoding: ${dce}`]);
186181
});
187182

188183
Array.of(toCheck)
@@ -200,11 +195,7 @@ Spec03.prototype.check = function(ce) {
200195
.filter((tc) => !SUPPORTED_CONTENT_ENCODING[tc.datacontentencoding]
201196
.check(tc.data))
202197
.forEach((tc) => {
203-
const err = new TypeError("invalid payload");
204-
err.errors = [
205-
`Invalid content encoding of data: ${tc.data}`
206-
];
207-
throw err;
198+
throw new ValidationError("invalid payload", [`Invalid content encoding of data: ${tc.data}`]);
208199
});
209200
};
210201

@@ -304,7 +295,7 @@ Spec03.prototype.addExtension = function(key, value) {
304295
if (!Object.prototype.hasOwnProperty.call(RESERVED_ATTRIBUTES, key)) {
305296
this.payload[key] = value;
306297
} else {
307-
throw new TypeError(`Reserved attribute name: '${key}'`);
298+
throw new ValidationError(`Reserved attribute name: '${key}'`);
308299
}
309300
return this;
310301
};

Diff for: lib/specs/spec_1.js

+4-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const { v4: uuidv4 } = require("uuid");
22
const Ajv = require("ajv");
3+
const ValidationError = require("../validation_error.js");
34

45
const {
56
asData,
@@ -191,9 +192,7 @@ Spec1.prototype.check = function(ce) {
191192
const toCheck = (!ce ? this.payload : ce);
192193

193194
if (!isValidAgainstSchema(toCheck)) {
194-
const err = new TypeError("invalid payload");
195-
err.errors = isValidAgainstSchema.errors;
196-
throw err;
195+
throw new ValidationError("invalid payload", [isValidAgainstSchema.errors]);
197196
}
198197
};
199198

@@ -284,10 +283,10 @@ Spec1.prototype.addExtension = function(key, value) {
284283
if (isValidType(value)) {
285284
this.payload[key] = value;
286285
} else {
287-
throw new TypeError("Invalid type of extension value");
286+
throw new ValidationError("Invalid type of extension value");
288287
}
289288
} else {
290-
throw new TypeError(`Reserved attribute name: '${key}'`);
289+
throw new ValidationError(`Reserved attribute name: '${key}'`);
291290
}
292291
return this;
293292
};

Diff for: lib/validation_error.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* A Error class that will be thrown when a CloudEvent
3+
* cannot be properly validated against a the specification.
4+
*/
5+
class ValidationError extends TypeError {
6+
/**
7+
* Constructs a new {ValidationError} with the message
8+
* and array of additional errors.
9+
* @param {string} message the error message
10+
* @param {[string]|[ErrorObject]} [errors] any additional errors related to validation
11+
*/
12+
constructor(message, errors) {
13+
super(message);
14+
this.errors = errors ? errors : [];
15+
}
16+
}
17+
18+
module.exports = ValidationError;

Diff for: test/bindings/http/promiscuous_receiver_test.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const {
88
BINARY_HEADERS_03,
99
BINARY_HEADERS_1
1010
} = require("../../../lib/bindings/http/constants.js");
11+
const ValidationError = require("../../../lib/validation_error.js");
1112

1213
const receiver = new HTTPReceiver();
1314
const id = "1234";
@@ -30,7 +31,7 @@ describe("HTTP Transport Binding Receiver for CloudEvents", () => {
3031
};
3132

3233
expect(receiver.accept.bind(receiver, {}, payload))
33-
.to.throw("no cloud event detected");
34+
.to.throw(ValidationError, "no cloud event detected");
3435
});
3536
});
3637

0 commit comments

Comments
 (0)