feat!: Remove All API's that are labeled "Remove in 4.0" (#362)

* feat!: remove all 4.0 deprecation.

* This removes all the APIs that were deprecated in the 3.x releases and marked as "remove in 4.0".

* Also removes any tests associated with those API's

* squash: remove axios as a dependecy


Signed-off-by: Lucas Holmquist <lholmqui@redhat.com>
This commit is contained in:
Lucas Holmquist 2020-11-13 14:12:26 -05:00 committed by GitHub
parent 8205bc96ae
commit 875f70017a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1812 additions and 2783 deletions

3990
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -99,7 +99,6 @@
"homepage": "https://github.com/cloudevents/sdk-javascript#readme",
"dependencies": {
"ajv": "~6.12.3",
"axios": "~0.19.2",
"uuid": "~8.3.0"
},
"devDependencies": {

View File

@ -2,17 +2,8 @@ import { CloudEvent, Version } from "./event/cloudevent";
import { ValidationError } from "./event/validation";
import { CloudEventV03, CloudEventV03Attributes, CloudEventV1, CloudEventV1Attributes } from "./event/interfaces";
import {
Emitter,
TransportOptions,
Options,
TransportFunction,
EmitterFunction,
emitterFor,
} from "./transport/emitter";
import { Receiver } from "./transport/receiver";
import { Protocol } from "./transport/protocols";
import { Headers, Mode, Binding, HTTP, Message, Serializer, Deserializer, headersFor } from "./message";
import { Options, TransportFunction, EmitterFunction, emitterFor } from "./transport/emitter";
import { Headers, Mode, Binding, HTTP, Message, Serializer, Deserializer } from "./message";
import CONSTANTS from "./constants";
@ -32,13 +23,8 @@ export {
Message,
Deserializer,
Serializer,
headersFor, // TODO: Deprecated. Remove for 4.0
HTTP,
// From transport
Emitter, // TODO: Deprecated. Remove for 4.0
Receiver, // TODO: Deprecated. Remove for 4.0
Protocol, // TODO: Deprecated. Remove for 4.0
TransportOptions, // TODO: Deprecated. Remove for 4.0
TransportFunction,
EmitterFunction,
emitterFor,

View File

@ -1,7 +1,6 @@
import { IncomingHttpHeaders } from "http";
import { CloudEvent } from "..";
import { binary, deserialize, structured, isEvent } from "./http";
import { headersFor } from "./http/headers";
/**
* Binding is an interface for transport protocols to implement,
@ -71,6 +70,3 @@ export const HTTP: Binding = {
toEvent: deserialize as Deserializer,
isEvent: isEvent as Detector,
};
// TODO: Deprecated. Remove this for 4.0
export { headersFor };

View File

@ -1,32 +1,6 @@
import { CloudEvent } from "../event/cloudevent";
import { axiosEmitter } from "./http";
import { Protocol } from "./protocols";
import { Agent } from "http";
import { HTTP, Message, Mode } from "../message";
/**
* Options supplied to the Emitter when sending an event.
* In addition to url and protocol, TransportOptions may
* also accept custom options that will be passed to the
* Node.js http functions.
* @deprecated will be removed in 4.0.0
*/
export interface TransportOptions {
/**
* The endpoint that will receieve the event.
* @example http://cncf.example.com/receiver
*/
url?: string;
/**
* The network protocol over which the event will be sent.
* @example HTTPStructured
* @example HTTPBinary
*/
protocol?: Protocol;
[key: string]: string | Record<string, unknown> | Protocol | Agent | undefined;
}
/**
* Options is an additional, optional dictionary of options that may
* be passed to an EmitterFunction and TransportFunction
@ -84,51 +58,3 @@ export function emitterFor(fn: TransportFunction, options = { binding: HTTP, mod
}
};
}
/**
* A class to send binary and structured CloudEvents to a remote endpoint.
* Currently, supported protocols are HTTPBinary and HTTPStructured.
*
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
* @see https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md#13-content-modes
* @deprecated Will be removed in 4.0.0. Consider using the emitterFactory
*
*/
export class Emitter {
url?: string;
protocol: Protocol;
binaryEmitter: EmitterFunction;
structuredEmitter: EmitterFunction;
constructor(options: TransportOptions = { protocol: Protocol.HTTPBinary }) {
this.protocol = options.protocol as Protocol;
this.url = options.url;
this.binaryEmitter = emitterFor(axiosEmitter(this.url as string));
this.structuredEmitter = emitterFor(axiosEmitter(this.url as string), { binding: HTTP, mode: Mode.STRUCTURED });
}
/**
* Sends the {CloudEvent} to an event receiver over HTTP POST
*
* @param {CloudEvent} event the CloudEvent to be sent
* @param {Object} [options] The configuration options for this event. Options
* provided will be passed along to Node.js `http.request()`.
* https://nodejs.org/api/http.html#http_http_request_options_callback
* @param {string} [options.url] The HTTP/S url that should receive this event.
* The URL is optional if one was provided when this emitter was constructed.
* In that case, it will be used as the recipient endpoint. The endpoint can
* be overridden by providing a URL here.
* @returns {Promise} Promise with an eventual response from the receiver
* @deprecated Will be removed in 4.0.0. Consider using the emitterFactory
*/
send(event: CloudEvent, options?: TransportOptions): Promise<unknown> {
options = options || {};
options.url = options.url || this.url;
if (options.protocol != this.protocol) {
if (this.protocol === Protocol.HTTPBinary) return this.binaryEmitter(event, options);
return this.structuredEmitter(event, options);
}
return this.binaryEmitter(event, options);
}
}

View File

@ -1,27 +0,0 @@
import { Headers, Message, HTTP } from "../message";
import { sanitize } from "../message/http/headers";
import { CloudEvent } from "..";
/**
* A class to receive a CloudEvent from an HTTP POST request.
*/
export const Receiver = {
/**
* Acceptor for an incoming HTTP CloudEvent POST. Can process
* binary and structured incoming CloudEvents.
*
* @param {Object} headers HTTP headers keyed by header name ("Content-Type")
* @param {Object|JSON} body The body of the HTTP request
* @return {CloudEvent} A new {CloudEvent} instance
* @deprecated Will be removed in 4.0.0. Consider using the Message interface with HTTP.toEvent(message)
*/
accept(headers: Headers, body: string | Record<string, unknown> | undefined | null): CloudEvent {
const cleanHeaders: Headers = sanitize(headers);
const cleanBody = body ? (typeof body === "object" ? JSON.stringify(body) : body) : undefined;
const message: Message = {
headers: cleanHeaders,
body: cleanBody,
};
return HTTP.toEvent(message);
},
};

View File

@ -1,260 +0,0 @@
import "mocha";
import { expect } from "chai";
import nock from "nock";
import CONSTANTS from "../../src/constants";
const DEFAULT_CE_CONTENT_TYPE = CONSTANTS.DEFAULT_CE_CONTENT_TYPE;
const DEFAULT_CONTENT_TYPE = CONSTANTS.DEFAULT_CONTENT_TYPE;
import { CloudEvent, Version, Emitter, Protocol } from "../../src";
import { headersFor } from "../../src/message/http/headers";
const receiver = "https://cloudevents.io/";
const type = "com.example.test";
const source = "urn:event:from:myapi/resource/123";
const ext1Name = "lunch";
const ext1Value = "tacos";
const ext2Name = "supper";
const ext2Value = "sushi";
const ext3Name = "snack";
const ext3Value = { value: "chips" };
const eventData = {
lunchBreak: "noon",
};
describe("HTTP Transport Binding Emitter for CloudEvents", () => {
beforeEach(() => {
nock(receiver)
.post("/")
.reply(function (uri: string, body: nock.Body) {
// return the request body and the headers so they can be
// examined in the test
if (typeof body === "string") {
body = JSON.parse(body);
}
const returnBody = { ...(body as Record<string, unknown>), ...this.req.headers };
return [201, returnBody];
});
});
describe("V1", () => {
const emitter = new Emitter({ url: receiver });
const event = new CloudEvent({
type,
source,
time: new Date().toISOString(),
data: eventData,
[ext1Name]: ext1Value,
[ext2Name]: ext2Value,
[ext3Name]: ext3Value,
});
it("Sends a binary 1.0 CloudEvent by default", () =>
emitter
.send(event)
.then((value: unknown) => {
const data = (value as Record<
string,
Record<string, Record<string, string | Record<string, string | Record<string, string>>>>
>).data;
// A binary message will have a ce-id header
expect(data["content-type"]).to.equal(DEFAULT_CONTENT_TYPE);
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
expect(data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
// A binary message will have a request body for the data
expect(data.lunchBreak).to.equal(data.lunchBreak);
// Ensure extensions are handled properly
expect(data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext1Name}`]).to.equal(ext1Value);
expect(data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext2Name}`]).to.equal(ext2Value);
expect((data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext3Name}`] as Record<string, string>).value).to.equal(
ext3Value.value,
);
})
.catch(expect.fail));
it("Provides the HTTP headers for a binary event", () => {
const headers = headersFor(event);
expect(headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(event.type);
expect(headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(event.specversion);
expect(headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(event.source);
expect(headers[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
expect(headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(event.time);
});
it("Sends a binary CloudEvent with Custom Headers", () =>
emitter
.send(event, { headers: { customheader: "value" } })
.then((value: unknown) => {
const data = (value as Record<
string,
Record<string, Record<string, string | Record<string, string | Record<string, string>>>>
>).data;
// A binary message will have a ce-id header
expect(data.customheader).to.equal("value");
expect(data["content-type"]).to.equal(DEFAULT_CONTENT_TYPE);
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
expect(data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V1);
// A binary message will have a request body for the data
expect(data.lunchBreak).to.equal(data.lunchBreak);
})
.catch(expect.fail));
it("Sends a structured 1.0 CloudEvent if specified", () =>
emitter
.send(event, { protocol: Protocol.HTTPStructured })
.then((value: unknown) => {
const data = (value as Record<string, Record<string, unknown>>).data as Record<
string,
string | Record<string, string>
>;
// A structured message will have a cloud event content type
expect(data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(data.specversion).to.equal(Version.V1);
expect((data as Record<string, Record<string, string>>).data.lunchBreak).to.equal(eventData.lunchBreak);
// Ensure extensions are handled properly
expect(data[ext1Name]).to.equal(ext1Value);
expect(data[ext2Name]).to.equal(ext2Value);
})
.catch(expect.fail));
it("Sends to an alternate URL if specified", () => {
nock(receiver)
.post("/alternate")
.reply(function (uri, requestBody: nock.Body) {
// return the request body and the headers so they can be
// examined in the test
if (typeof requestBody === "string") {
requestBody = JSON.parse(requestBody);
}
const returnBody = { ...(requestBody as Record<string, unknown>), ...this.req.headers };
return [201, returnBody];
});
return emitter
.send(event, { protocol: Protocol.HTTPStructured, url: `${receiver}alternate` })
.then((value: unknown) => {
const data = (value as Record<string, Record<string, unknown>>).data as Record<
string,
string | Record<string, string>
>;
// A structured message will have a cloud event content type
expect(data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(data.specversion).to.equal(Version.V1);
expect((data as Record<string, Record<string, string>>).data.lunchBreak).to.equal(eventData.lunchBreak);
})
.catch(expect.fail);
});
});
describe("V03", () => {
const emitter = new Emitter({ url: receiver });
const event = new CloudEvent({
specversion: Version.V03,
type,
source,
time: new Date().toISOString(),
data: eventData,
[ext1Name]: ext1Value,
[ext2Name]: ext2Value,
[ext3Name]: ext3Value,
});
it("Sends a binary 0.3 CloudEvent", () =>
emitter
.send(event)
.then((value: unknown) => {
const data = (value as Record<string, Record<string, unknown>>).data as Record<
string,
string | Record<string, string>
>;
// A binary message will have a ce-id header
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
expect(data[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(Version.V03);
// A binary message will have a request body for the data
expect(data.lunchBreak).to.equal(data.lunchBreak);
// Ensure extensions are handled properly
expect(data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext1Name}`]).to.equal(ext1Value);
expect(data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext2Name}`]).to.equal(ext2Value);
expect((data[`${CONSTANTS.EXTENSIONS_PREFIX}${ext3Name}`] as Record<string, string>).value).to.equal(
ext3Value.value,
);
})
.catch(expect.fail));
it("Provides the HTTP headers for a binary event", () => {
const headers = headersFor(event);
expect(headers[CONSTANTS.CE_HEADERS.TYPE]).to.equal(event.type);
expect(headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]).to.equal(event.specversion);
expect(headers[CONSTANTS.CE_HEADERS.SOURCE]).to.equal(event.source);
expect(headers[CONSTANTS.CE_HEADERS.ID]).to.equal(event.id);
expect(headers[CONSTANTS.CE_HEADERS.TIME]).to.equal(event.time);
});
it("Sends a structured 0.3 CloudEvent if specified", () =>
emitter
.send(event, { protocol: Protocol.HTTPStructured })
.then((value: unknown) => {
const data = (value as Record<string, Record<string, unknown>>).data as Record<
string,
string | Record<string, string>
>;
// A structured message will have a cloud event content type
expect(data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(data.specversion).to.equal(Version.V03);
expect((data as Record<string, Record<string, string>>).data.lunchBreak).to.equal(eventData.lunchBreak);
// Ensure extensions are handled properly
expect(data[ext1Name]).to.equal(ext1Value);
expect(data[ext2Name]).to.equal(ext2Value);
})
.catch(expect.fail));
it("Sends to an alternate URL if specified", () => {
nock(receiver)
.post("/alternate")
.reply(function (uri, requestBody: nock.Body) {
// return the request body and the headers so they can be
// examined in the test
if (typeof requestBody === "string") {
requestBody = JSON.parse(requestBody);
}
const returnBody = { ...(requestBody as Record<string, unknown>), ...this.req.headers };
return [201, returnBody];
});
return emitter
.send(event, { protocol: Protocol.HTTPStructured, url: `${receiver}alternate` })
.then((value: unknown) => {
const data = (value as Record<string, Record<string, unknown>>).data as Record<
string,
string | Record<string, string>
>;
// A structured message will have a cloud event content type
expect(data["content-type"]).to.equal(DEFAULT_CE_CONTENT_TYPE);
// Ensure other CE headers don't exist - just testing for ID
expect(data[CONSTANTS.CE_HEADERS.ID]).to.equal(undefined);
// The spec version would have been specified in the body
expect(data.specversion).to.equal(Version.V03);
expect((data as Record<string, Record<string, string>>).data.lunchBreak).to.equal(eventData.lunchBreak);
})
.catch(expect.fail);
});
});
});

View File

@ -1,212 +0,0 @@
import "mocha";
import { expect } from "chai";
import { CloudEvent, Receiver, ValidationError } from "../../src";
import { CloudEventV1 } from "../../src/event/interfaces";
const id = "1234";
const type = "org.cncf.cloudevents.test";
const source = "urn:event:from:myapi/resourse/123";
const structuredHeaders = { "content-type": "application/cloudevents+json" };
const data = { lunch: "sushi" };
describe("HTTP Transport Binding Receiver for CloudEvents", () => {
describe("HTTP CloudEvent format detection", () => {
const specversion = "1.0";
it("Throws when the event format cannot be detected", () => {
const payload = {
id,
type,
source,
data,
specversion,
};
expect(Receiver.accept.bind(Receiver, {}, payload)).to.throw(ValidationError, "no cloud event detected");
});
it("Converts the JSON body of a binary event to an Object", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event: CloudEvent = Receiver.accept(binaryHeaders, data);
expect(typeof event.data).to.equal("object");
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
});
it("Accepts binary events when the data property is undefined", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = Receiver.accept(binaryHeaders, undefined);
expect(event.data).to.be.undefined;
});
it("Accepts binary events when the data property is null", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = Receiver.accept(binaryHeaders, null);
expect(event.data).to.be.undefined;
});
it("Converts the JSON body of a structured event to an Object", () => {
const payload = {
id,
type,
source,
data,
specversion,
};
const event = Receiver.accept(structuredHeaders, payload);
expect(typeof event.data).to.equal("object");
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
});
it("Recognizes headers in title case for binary events", () => {
const binaryHeaders = {
"Content-Type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event: CloudEvent = Receiver.accept(binaryHeaders, data);
expect(event.validate()).to.be.true;
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
});
it("Recognizes headers in title case for structured events", () => {
const structuredHeaders = { "Content-Type": "application/cloudevents+json" };
const payload = {
id,
type,
source,
data,
specversion,
};
const event: CloudEvent = Receiver.accept(structuredHeaders, payload);
expect(event.validate()).to.be.true;
expect((event.data as Record<string, string>).lunch).to.equal("sushi");
});
});
describe("V1", () => {
const specversion = "1.0";
it("Structured data returns a CloudEvent", () => {
const payload = {
id,
type,
source,
data,
specversion,
};
const event = Receiver.accept(structuredHeaders, payload);
validateEvent(event, specversion);
});
it("Binary data returns a CloudEvent", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = Receiver.accept(binaryHeaders, data);
validateEvent(event, specversion);
});
});
describe("V03", () => {
const specversion = "0.3";
it("Structured data returns a CloudEvent", () => {
const payload = {
id,
type,
source,
data,
specversion,
};
const event = Receiver.accept(structuredHeaders, payload);
validateEvent(event, specversion);
});
it("Binary data returns a CloudEvent", () => {
const binaryHeaders = {
"content-type": "application/json; charset=utf-8",
"ce-specversion": specversion,
"ce-id": id,
"ce-type": type,
"ce-source": source,
};
const event = Receiver.accept(binaryHeaders, data);
validateEvent(event, specversion);
});
});
describe("Kafka-Knative event source", () => {
const specversion = "1.0";
const id = "partition:1/offset:23";
const type = "dev.knative.kafka.event";
const source = "/apis/v1/namespaces/kafka/kafkasources/kafka-source#knative-demo-topic";
it("Should be parsable", () => {
const headers = {
host: "event-display.kafka.svc.cluster.local",
"user-agent": "Go-http-client/1.1",
"content-length": "59",
"accept-encoding": "gzip",
"ce-id": id,
"ce-source": source,
"ce-specversion": "1.0",
"ce-subject": "partition:1#23",
"ce-time": "2020-05-07T14:16:30.245Z",
"ce-type": type,
forwarded: "for=10.131.0.72;proto=http",
"k-proxy-request": "activator",
"x-envoy-expected-rq-timeout-ms": "600000",
"x-forwarded-for": "10.131.0.72, 10.128.2.99",
"x-forwarded-proto": "http",
"x-request-id": "d3649c1b-a968-40bf-a9da-3e853abc0c8b",
};
const event = Receiver.accept(headers, data);
expect(event instanceof CloudEvent).to.equal(true);
expect(event.id).to.equal(id);
expect(event.type).to.equal(type);
expect(event.source).to.equal(source);
expect(event.data).to.deep.equal(data);
expect(event.specversion).to.equal(specversion);
});
});
});
function validateEvent(event: CloudEventV1, specversion: string) {
expect(event instanceof CloudEvent).to.equal(true);
expect(event.id).to.equal(id);
expect(event.type).to.equal(type);
expect(event.source).to.equal(source);
expect(event.data).to.deep.equal(data);
expect(event.specversion).to.equal(specversion);
}

View File

@ -1,6 +1,6 @@
import "mocha";
import { expect } from "chai";
import { CloudEvent, Emitter, Version } from "../../src";
import { CloudEvent, Version } from "../../src";
const fixture = {
type: "org.cloudevents.test",
@ -13,13 +13,6 @@ describe("The SDK Requirements", () => {
expect(event instanceof CloudEvent).to.equal(true);
});
it("should expose an Emitter type", () => {
const emitter = new Emitter({
url: "http://example.com",
});
expect(emitter instanceof Emitter).to.equal(true);
});
describe("v0.3", () => {
it("should create an event using the right spec version", () => {
expect(