Skip to content

feat: add ValidationError type extending TypeError #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/bindings/http/http_receiver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const V03Binary = require("./receiver_binary_0_3.js");
const V03Structured = require("./receiver_structured_0_3.js");
const V1Binary = require("./receiver_binary_1.js");
const V1Structured = require("./receiver_structured_1.js");
const ValidationError = require("../../validation_error.js");
const {
SPEC_V03,
SPEC_V1,
Expand Down Expand Up @@ -63,7 +64,7 @@ function getMode(headers) {
if (headers[BINARY_HEADERS_1.ID]) {
return "binary";
}
throw new TypeError("no cloud event detected");
throw new ValidationError("no cloud event detected");
}

function getVersion(mode, headers, body) {
Expand Down
20 changes: 7 additions & 13 deletions lib/bindings/http/receiver_binary.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const { HEADER_CONTENT_TYPE, MIME_JSON, DEFAULT_SPEC_VERSION_HEADER } =
require("./constants.js");
const Commons = require("./commons.js");
const CloudEvent = require("../../cloudevent.js");
const ValidationError = require("../../validation_error.js");

const {
isDefinedOrThrow,
Expand All @@ -10,15 +11,12 @@ const {

function validateArgs(payload, attributes) {
Array.of(payload)
.filter((p) => isDefinedOrThrow(p,
{ message: "payload is null or undefined" }))
.filter((p) => isStringOrObjectOrThrow(p,
{ message: "payload must be an object or a string" }))
.filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined")))
.filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or a string")))
.shift();

Array.of(attributes)
.filter((a) => isDefinedOrThrow(a,
{ message: "attributes is null or undefined" }))
.filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined")))
.shift();
}

Expand Down Expand Up @@ -58,22 +56,18 @@ BinaryHTTPReceiver.prototype.check = function(payload, headers) {
const contentTypeHeader = sanityHeaders[HEADER_CONTENT_TYPE];
const noContentType = !this.allowedContentTypes.includes(contentTypeHeader);
if (contentTypeHeader && noContentType) {
const err = new TypeError("invalid content type");
err.errors = [sanityHeaders[HEADER_CONTENT_TYPE]];
throw err;
throw new ValidationError("invalid content type", [sanityHeaders[HEADER_CONTENT_TYPE]]);
}

this.requiredHeaders
.filter((required) => !sanityHeaders[required])
.forEach((required) => {
throw new TypeError(`header '${required}' not found`);
throw new ValidationError(`header '${required}' not found`);
});

if (sanityHeaders[DEFAULT_SPEC_VERSION_HEADER] !==
this.specversion) {
const err = new TypeError("invalid spec version");
err.errors = [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]];
throw err;
throw new ValidationError("invalid spec version", [sanityHeaders[DEFAULT_SPEC_VERSION_HEADER]]);
}

// No erros! Its contains the minimum required attributes
Expand Down
5 changes: 2 additions & 3 deletions lib/bindings/http/receiver_binary_0_3.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const Constants = require("./constants.js");
const Spec = require("../../specs/spec_0_3.js");
const ValidationError = require("../../validation_error.js");

const JSONParser = require("../../formats/json/parser.js");
const Base64Parser = require("../../formats/base64.js");
Expand Down Expand Up @@ -85,9 +86,7 @@ function checkDecorator(payload, headers) {
.filter((header) => !allowedEncodings.includes(headers[header]))
.forEach((header) => {
// TODO: using forEach here seems off
const err = new TypeError("unsupported datacontentencoding");
err.errors = [headers[header]];
throw err;
throw new ValidationError("unsupported datacontentencoding");
});
}

Expand Down
14 changes: 5 additions & 9 deletions lib/bindings/http/receiver_structured.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const Constants = require("./constants.js");
const Commons = require("./commons.js");
const CloudEvent = require("../../cloudevent.js");
const ValidationError = require("../../validation_error.js");

const {
isDefinedOrThrow,
Expand All @@ -9,15 +10,12 @@ const {

function validateArgs(payload, attributes) {
Array.of(payload)
.filter((p) => isDefinedOrThrow(p,
{ message: "payload is null or undefined" }))
.filter((p) => isStringOrObjectOrThrow(p,
{ message: "payload must be an object or string" }))
.filter((p) => isDefinedOrThrow(p, new ValidationError("payload is null or undefined")))
.filter((p) => isStringOrObjectOrThrow(p, new ValidationError("payload must be an object or string")))
.shift();

Array.of(attributes)
.filter((a) => isDefinedOrThrow(a,
{ message: "attributes is null or undefined" }))
.filter((a) => isDefinedOrThrow(a, new ValidationError("attributes is null or undefined")))
.shift();
}

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

// No erros! Its contains the minimum required attributes
Expand Down
15 changes: 8 additions & 7 deletions lib/bindings/http/unmarshaller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
MIME_OCTET_STREAM
} = require("./constants.js");
const Commons = require("./commons.js");
const ValidationError = require("../../validation_error.js");

const STRUCTURED = "structured";
const BINARY = "binary";
Expand All @@ -29,18 +30,18 @@ function resolveBindingName(payload, headers) {
if (allowedStructuredContentTypes.includes(contentType)) {
return STRUCTURED;
}
throwTypeError("structured+type not allowed", contentType);
throwValidationError("structured+type not allowed", contentType);
} else {
// Binary
if (allowedBinaryContentTypes.includes(contentType)) {
return BINARY;
}
throwTypeError("content type not allowed", contentType);
throwValidationError("content type not allowed", contentType);
}
}

function throwTypeError(msg, contentType) {
const err = new TypeError(msg);
function throwValidationError(msg, contentType) {
const err = new ValidationError(msg);
err.errors = [contentType];
throw err;
}
Expand All @@ -52,16 +53,16 @@ class Unmarshaller {

unmarshall(payload, headers) {
if (!payload) {
throw new TypeError("payload is null or undefined");
throw new ValidationError("payload is null or undefined");
}
if (!headers) {
throw new TypeError("headers is null or undefined");
throw new ValidationError("headers is null or undefined");
}

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

// Resolve the binding
Expand Down
10 changes: 5 additions & 5 deletions lib/cloudevent.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ const Formatter = require("./formats/json/formatter.js");
class CloudEvent {
/**
* Creates a new CloudEvent instance
* @param {Spec} [UserSpec] A CloudEvent version specification
* @param {Formatter} [UserFormatter] Converts the event into a readable string
* @param {Spec} [userSpec] A CloudEvent version specification
* @param {Formatter} [userFormatter] Converts the event into a readable string
*/
constructor(UserSpec, UserFormatter) {
this.spec = (UserSpec) ? new UserSpec(CloudEvent) : new Spec(CloudEvent);
this.formatter = (UserFormatter) ? new UserFormatter() : new Formatter();
constructor(userSpec, userFormatter) {
this.spec = userSpec ? new userSpec(CloudEvent) : new Spec(CloudEvent);
this.formatter = userFormatter ? new userFormatter() : new Formatter();

// The map of extensions
this.extensions = {};
Expand Down
7 changes: 3 additions & 4 deletions lib/formats/json/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ const {
isDefinedOrThrow,
isStringOrObjectOrThrow
} = require("../../utils/fun.js");
const ValidationError = require("../../validation_error.js");

const invalidPayloadTypeError =
new Error("invalid payload type, allowed are: string or object");
const nullOrUndefinedPayload =
new Error("null or undefined payload");
const invalidPayloadTypeError = new ValidationError("invalid payload type, allowed are: string or object");
const nullOrUndefinedPayload = new ValidationError("null or undefined payload");

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

Expand Down
19 changes: 5 additions & 14 deletions lib/specs/spec_0_3.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { v4: uuidv4 } = require("uuid");
const Ajv = require("ajv");
const ValidationError = require("../validation_error.js");

const {
isBase64,
Expand Down Expand Up @@ -168,21 +169,15 @@ Spec03.prototype.check = function(ce) {
const toCheck = (!ce ? this.payload : ce);

if (!isValidAgainstSchema(toCheck)) {
const err = new TypeError("invalid payload");
err.errors = isValidAgainstSchema.errors;
throw err;
throw new ValidationError("invalid payload", [isValidAgainstSchema.errors]);
}

Array.of(toCheck)
.filter((tc) => tc.datacontentencoding)
.map((tc) => tc.datacontentencoding.toLocaleLowerCase("en-US"))
.filter((dce) => !Object.keys(SUPPORTED_CONTENT_ENCODING).includes(dce))
.forEach((dce) => {
const err = new TypeError("invalid payload");
err.errors = [
`Unsupported content encoding: ${dce}`
];
throw err;
throw new ValidationError("invalid payload", [`Unsupported content encoding: ${dce}`]);
});

Array.of(toCheck)
Expand All @@ -200,11 +195,7 @@ Spec03.prototype.check = function(ce) {
.filter((tc) => !SUPPORTED_CONTENT_ENCODING[tc.datacontentencoding]
.check(tc.data))
.forEach((tc) => {
const err = new TypeError("invalid payload");
err.errors = [
`Invalid content encoding of data: ${tc.data}`
];
throw err;
throw new ValidationError("invalid payload", [`Invalid content encoding of data: ${tc.data}`]);
});
};

Expand Down Expand Up @@ -304,7 +295,7 @@ Spec03.prototype.addExtension = function(key, value) {
if (!Object.prototype.hasOwnProperty.call(RESERVED_ATTRIBUTES, key)) {
this.payload[key] = value;
} else {
throw new TypeError(`Reserved attribute name: '${key}'`);
throw new ValidationError(`Reserved attribute name: '${key}'`);
}
return this;
};
Expand Down
9 changes: 4 additions & 5 deletions lib/specs/spec_1.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const { v4: uuidv4 } = require("uuid");
const Ajv = require("ajv");
const ValidationError = require("../validation_error.js");

const {
asData,
Expand Down Expand Up @@ -191,9 +192,7 @@ Spec1.prototype.check = function(ce) {
const toCheck = (!ce ? this.payload : ce);

if (!isValidAgainstSchema(toCheck)) {
const err = new TypeError("invalid payload");
err.errors = isValidAgainstSchema.errors;
throw err;
throw new ValidationError("invalid payload", [isValidAgainstSchema.errors]);
}
};

Expand Down Expand Up @@ -284,10 +283,10 @@ Spec1.prototype.addExtension = function(key, value) {
if (isValidType(value)) {
this.payload[key] = value;
} else {
throw new TypeError("Invalid type of extension value");
throw new ValidationError("Invalid type of extension value");
}
} else {
throw new TypeError(`Reserved attribute name: '${key}'`);
throw new ValidationError(`Reserved attribute name: '${key}'`);
}
return this;
};
Expand Down
18 changes: 18 additions & 0 deletions lib/validation_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* A Error class that will be thrown when a CloudEvent
* cannot be properly validated against a the specification.
*/
class ValidationError extends TypeError {
/**
* Constructs a new {ValidationError} with the message
* and array of additional errors.
* @param {string} message the error message
* @param {[string]|[ErrorObject]} [errors] any additional errors related to validation
*/
constructor(message, errors) {
super(message);
this.errors = errors ? errors : [];
}
}

module.exports = ValidationError;
3 changes: 2 additions & 1 deletion test/bindings/http/promiscuous_receiver_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const {
BINARY_HEADERS_03,
BINARY_HEADERS_1
} = require("../../../lib/bindings/http/constants.js");
const ValidationError = require("../../../lib/validation_error.js");

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

expect(receiver.accept.bind(receiver, {}, payload))
.to.throw("no cloud event detected");
.to.throw(ValidationError, "no cloud event detected");
});
});

Expand Down
Loading