8000 src: remove HTTP receiver implementations and use messages/HTTP · cloudevents/sdk-javascript@6f80a4c · GitHub
[go: up one dir, main page]

Skip to content

Commit 6f80a4c

Browse files
committed
src: remove HTTP receiver implementations and use messages/HTTP
This commit removes the binary|structured receiver implementations in transport/http and uses instead the HTTP Message implementation. The concept of a sender/invoker is really just sugar coating and should probably be left up to the user to send an HTTP Message however they choose. To be more consistent with the 'binary' and 'structured' Message serialization functions, the 'Receiver' interface has been changed to 'Deserializer', and the exported function named 'toEvent()'. test(conformance): use Message and HTTP.toEvent() for conformance tests src: export types from src/messages at top level Signed-off-by: Lance Ball <lball@redhat.com>
1 parent ca3ef56 commit 6f80a4c

14 files changed

+220
-1722
lines changed

src/index.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import { ValidationError } from "./event/validation";
33
import { CloudEventV03, CloudEventV03Attributes, CloudEventV1, CloudEventV1Attributes } from "./event/interfaces";
44

55
import { Emitter, TransportOptions } from "./transport/emitter";
6-
import { Receiver, Mode } from "./transport/receiver";
6+
import { Receiver } from "./transport/receiver";
77
import { Protocol } from "./transport/protocols";
8-
import { Headers } from "./messages";
8+
import { Headers, Mode, Binding, HTTP, Message, Serializer, Deserializer } from "./messages";
99

1010
import CONSTANTS from "./constants";
1111

@@ -18,13 +18,19 @@ export {
1818
CloudEventV1Attributes,
1919
Version,
2020
ValidationError,
21+
// From messages
22+
Headers,
23+
Mode,
24+
Binding,
25+
Message,
26+
Deserializer,
27+
Serializer,
28+
HTTP,
2129
// From transport
2230
Emitter,
2331
Receiver,
24-
Mode,
2532
Protocol,
2633
TransportOptions,
27-
Headers,
2834
// From Constants
2935
CONSTANTS,
3036
};

src/messages/http/index.ts

Lines changed: 137 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
1-
import { CloudEvent, CloudEventV03, CloudEventV1, CONSTANTS, Version } from "../..";
2-
import { Message, Sender, Headers } from "..";
1+
import { CloudEvent, CloudEventV03, CloudEventV1, CONSTANTS, Mode, Version } from "../..";
2+
import { Message, Headers } from "..";
33

4-
import { headersFor, v1binaryParsers, validate } from "./headers";
4+
import { headersFor, sanitize, v03structuredParsers, v1binaryParsers, v1structuredParsers, validate } from "./headers";
55
import { asData, isBase64, isString, isStringOrObjectOrThrow, ValidationError } from "../../event/validation";
66
import { validateCloudEvent } from "../../event/spec";
7-
import { MappedParser, parserByContentType } from "../../parsers";
8-
9-
// Sender is a function that takes headers and body for transmission
< F438 code>10-
// over HTTP. Users supply this function as a parameter to HTTP.emit()
11-
// Sends a message by invoking sender(). Implements Invoker
12-
export function invoke(sender: Sender, message: Message): Promise<boolean> {
13-
return sender(message.headers, message.body);
14-
}
7+
import { Base64Parser, JSONParser, MappedParser, Parser, parserByContentType } from "../../parsers";
158

169
// implements Serializer
1710
export function binary(event: CloudEvent): Message {
@@ -33,6 +26,69 @@ export function structured(event: CloudEvent): Message {
3326
};
3427
}
3528

29+
/**
30+
* Converts a Message to a CloudEvent
31+
*
32+
* @param {Message} message the incoming message
33+
* @return {CloudEvent} A new {CloudEvent} instance
34+
*/
35+
export function deserialize(message: Message): CloudEvent {
36+
const cleanHeaders: Headers = sanitize(message.headers);
37+
const mode: Mode = getMode(cleanHeaders);
38+
let version = getVersion(mode, cleanHeaders, message.body);
39+
if (version !== Version.V03 && version !== Version.V1) {
40+
console.error(`Unknown spec version ${version}. Default to ${Version.V1}`);
41+
version = Version.V1;
42+
}
43+
switch (mode) {
44+
case Mode.BINARY:
45+
return parseBinary(message, version);
46+
case Mode.STRUCTURED:
47+
return parseStructured(message, version);
48+
default:
49+
throw new ValidationError("Unknown Message mode");
50+
}
51+
}
52+
53+
/**
54+
* Determines the HTTP transport mode (binary or structured) based
55+
* on the incoming HTTP headers.
56+
* @param {Headers} headers the incoming HTTP headers
57+
* @returns {Mode} the transport mode
58+
*/
59+
function getMode(headers: Headers): Mode {
60+
const contentType = headers[CONSTANTS.HEADER_CONTENT_TYPE];
61+
if (contentType && contentType.startsWith(CONSTANTS.MIME_CE)) {
62+
return Mode.STRUCTURED;
63+
}
64+
if (headers[CONSTANTS.CE_HEADERS.ID]) {
65+
return Mode.BINARY;
66+
}
67+
throw new ValidationError("no cloud event detected");
68+
}
69+
70+
/**
71+
* Determines the version of an incoming CloudEvent based on the
72+
* HTTP headers or HTTP body, depending on transport mode.
73+
* @param {Mode} mode the HTTP transport mode
74+
* @param {Headers} headers the incoming HTTP headers
75+
* @param {Record<string, unknown>} body the HTTP request body
76+
* @returns {Version} the CloudEvent specification version
77+
*/
78+
function getVersion(mode: Mode, headers: Headers, body: string | Record<string, string>) {
79+
if (mode === Mode.BINARY) {
80+
// Check the headers for the version
81+
const versionHeader = headers[CONSTANTS.CE_HEADERS.SPEC_VERSION];
82+
if (versionHeader) {
83+
return versionHeader;
84+
}
85+
} else {
86+
// structured mode - the version is in the body
87+
return typeof body === "string" ? JSON.parse(body).specversion : (body as CloudEvent).specversion;
88+
}
89+
return Version.V1;
90+
}
91+
3692
/**
3793
* Parses an incoming HTTP Message, converting it to a {CloudEvent}
3894
* instance if it conforms to the Cloud Event specification for this receiver.
@@ -42,7 +98,7 @@ export function structured(event: CloudEvent): Message {
4298
* @returns {CloudEvent} an instance of CloudEvent representing the incoming request
4399
* @throws {ValidationError} of the event does not conform to the spec
44100
*/
45-
export function receive(message: Message, version: Version = Version.V1): CloudEvent {
101+
function parseBinary(message: Message, version: Version): CloudEvent {
46102
const headers = message.headers;
47103
let body = message.body;
48104

@@ -102,3 +158,72 @@ export function receive(message: Message, version: Version = Version.V1): CloudE
102158
validateCloudEvent(cloudevent);
103159
return cloudevent;
104160
}
161+
162+
/**
163+
* Creates a new CloudEvent instance based on the provided payload and headers.
164+
*
165+
* @param {Message} message the incoming Message
166+
* @param {Version} version the spec version of this message (v1 or v03)
167+
* @returns {CloudEvent} a new CloudEvent instance for the provided headers and payload
168+
* @throws {ValidationError} if the payload and header combination do not conform to the spec
169+
*/
170+
function parseStructured(message: Message, version: Version): CloudEvent {
171+
let payload = message.body;
172+
const headers = message.headers;
173+
174+
if (!payload) throw new ValidationError("payload is null or undefined");
175+
if (!headers) throw new ValidationError("headers is null or undefined");
176+
isStringOrObjectOrThrow(payload, new ValidationError("payload must be an object or a string"));
177+
178+
if (
179+
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] &&
180+
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] != Version.V03 &&
181+
headers[CONSTANTS.CE_HEADERS.SPEC_VERSION] != Version.V1
182+
) {
183+
throw new ValidationError(`invalid spec version ${headers[CONSTANTS.CE_HEADERS.SPEC_VERSION]}`);
184+
}
185+
186+
payload = isString(payload) && isBase64(payload) ? Buffer.from(payload as string, "base64").toString() : payload;
187+
188+
// Clone and low case all headers names
189+
const sanitizedHeaders = sanitize(headers);
190+
191+
const contentType = sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE];
192+
const parser: Parser = contentType ? parserByContentType[contentType] : new JSONParser();
193+
if (!parser) throw new ValidationError(`invalid content type ${sanitizedHeaders[CONSTANTS.HEADER_CONTENT_TYPE]}`);
194+
const incoming = { ...(parser.parse(payload) as Record<string, unknown>) };
195+
196+
const eventObj: { [key: string]: unknown } = {};
197+
const parserMap: Record<string, MappedParser> = version === Version.V1 ? v1structuredParsers : v03structuredParsers;
198+
199+
for (const key in parserMap) {
200+
const property = incoming[key];
201+
if (property) {
202+
const parser: MappedParser = parserMap[key];
203+
eventObj[parser.name] = parser.parser.parse(property as string);
204+
}
205+
delete incoming[key];
206+
}
207+
208+
// extensions are what we have left after processing all other properties
209+
for (const key in incoming) {
210+
eventObj[key] = incoming[key];
211+
}
212+
213+
// ensure data content is correctly encoded
214+
if (eventObj.data && eventObj.datacontentencoding) {
215+
if (eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64 && !isBase64(eventObj.data)) {
216+
throw new ValidationError("invalid payload");
217+
} else if (eventObj.datacontentencoding === CONSTANTS.ENCODING_BASE64) {
218+
const dataParser = new Base64Parser();
219+
eventObj.data = JSON.parse(dataParser.parse(eventObj.data as string));
220+
delete eventObj.datacontentencoding;
221+
}
222+
}
223+
224+
const cloudevent = new CloudEvent(eventObj as CloudEventV1 | CloudEventV03);
225+
226+
// Validates the event
227+
validateCloudEvent(cloudevent);
228+
return cloudevent;
229+
}

src/messages/index.ts

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { CloudEvent, Version } from "..";
2-
import { binary, invoke, receive, structured } from "./http";
1+
import { CloudEvent } from "..";
2+
import { binary, deserialize, structured } from "./http";
33

44
/**
55
* Binding is an interface for transport protocols to implement,
@@ -9,8 +9,7 @@ import { binary, invoke, receive, structured } from "./http";
99
export interface Binding {
1010
binary: Serializer;
1111
structured: Serializer;
12-
send: Invoker;
13-
receive: Receiver;
12+
toEvent: Deserializer;
1413
}
1514

1615
/**
@@ -31,43 +30,32 @@ export interface Message {
3130
}
3231

3332
/**
34-
* Serializer is an interface for functions that can convert a
35-
* CloudEvent into a Message.
36-
*/
37-
export interface Serializer {
38-
(event: CloudEvent): Message;
39-
}
40-
41-
/**
42-
* Sender is a function interface for user-supplied functions
43-
* capable of transmitting a Message over a specific protocol
33+
* An enum representing the two transport modes, binary and structured
4434
*/
45-
export interface Sender {
46-
(headers: Headers, body: string): Promise<boolean>;
35+
export enum Mode {
36+
BINARY = "binary",
37+
STRUCTURED = "structured",
4738
}
4839

4940
/**
50-
* Invoker is an interface for functions that send a message
51-
* over a specific protocol by invoking the user-supplied
52-
* Sender function with the message headers and body.
53-
* TODO: This might be overkill
41+
* Serializer is an interface for functions that can convert a
42+
* CloudEvent into a Message.
5443
*/
55-
export interface Invoker {
56-
(sender: Sender, message: Message): Promise<boolean>;
44+
export interface Serializer {
45+
(event: CloudEvent): Message;
5746
}
5847

5948
/**
60-
* Receiver is a function interface that converts an incoming
49+
* Deserializer is a function interface that converts a
6150
* Message to a CloudEvent
6251
*/
63-
export interface Receiver {
64-
(message: Message, version: Version | undefined): CloudEvent;
52+
export interface Deserializer {
53+
(message: Message): CloudEvent;
6554
}
6655

67-
// Export HTTP transport capabilities
56+
// HTTP Message capabilities
6857
export const HTTP: Binding = {
6958
binary: binary as Serializer,
7059
structured: structured as Serializer,
71-
send: invoke as Invoker,
72-
receive: receive as Receiver,
60+
toEvent: deserialize as Deserializer,
7361
};

src/transport/http/binary_receiver.ts

Lines changed: 0 additions & 94 deletions
This file was deleted.

0 commit comments

Comments
 (0)
0