aws-iot-device-sdk-v2
Version:
NodeJS API for the AWS IoT service
853 lines • 38.7 kB
JavaScript
"use strict";
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRpcError = exports.StreamingOperation = exports.RequestResponseOperation = exports.RpcClient = exports.validateRpcClientConfig = exports.RpcError = exports.RpcErrorType = void 0;
/**
* @packageDocumentation
* @module eventstream_rpc
*/
const aws_crt_1 = require("aws-crt");
const events_1 = require("events");
const util_utf8_browser_1 = require("@aws-sdk/util-utf8-browser");
/**
* Indicates the general category of an error thrown by the eventstream RPC implementation
*/
var RpcErrorType;
(function (RpcErrorType) {
/**
* An error occurred while serializing a client model into a message in the eventstream protocol.
*/
RpcErrorType[RpcErrorType["SerializationError"] = 0] = "SerializationError";
/**
* An error occurred while deserializing a message from the eventstream protocol into the client model.
*/
RpcErrorType[RpcErrorType["DeserializationError"] = 1] = "DeserializationError";
/**
* An error occurred during the connect-connack handshake between client and server. Usually this means
* the connect was not accepted by the server and thus hints at an authentication problem.
*/
RpcErrorType[RpcErrorType["HandshakeError"] = 2] = "HandshakeError";
/**
* An error that isn't classifiable as one of the other RpcErrorType values.
*/
RpcErrorType[RpcErrorType["InternalError"] = 3] = "InternalError";
/**
* An error occurred due to an attempt to invoke an API while the target operation or client is not in the
* right state to perform the API.
*/
RpcErrorType[RpcErrorType["ClientStateError"] = 4] = "ClientStateError";
/**
* An error occurred ostensibly due to an underlying networking failure.
*/
RpcErrorType[RpcErrorType["NetworkError"] = 5] = "NetworkError";
/**
* An error occurred where the underlying transport was shut down unexpectedly.
*/
RpcErrorType[RpcErrorType["InterruptionError"] = 6] = "InterruptionError";
/**
* Invalid data was passed into the RPC client.
*/
RpcErrorType[RpcErrorType["ValidationError"] = 7] = "ValidationError";
/**
* A formally-modeled error sent from server to client
*/
RpcErrorType[RpcErrorType["ServiceError"] = 8] = "ServiceError";
})(RpcErrorType = exports.RpcErrorType || (exports.RpcErrorType = {}));
;
/**
* Wrapper type for all exceptions thrown by rpc clients and operations. This includes rejected promises.
*
* The intention is for this data model to help users make better decisions in the presence of errors. Not all errors
* are fatal/terminal, but JS doesn't really give a natural way to classify or conditionally react to general errors.
*/
class RpcError extends Error {
/** @internal */
constructor(model) {
super(model.description);
this.type = model.type;
this.description = model.description;
if (model.internalError) {
this.internalError = model.internalError;
}
if (model.serviceError) {
this.serviceError = model.serviceError;
}
}
}
exports.RpcError = RpcError;
/**
* Checks an RPC Client configuration structure and throws an exception if there is a problem with one of the
* required properties. Does explicit type checks in spite of typescript to validate even when used from a
* pure Javascript project.
*
* @param config RPC client configuration to validate
*/
function validateRpcClientConfig(config) {
if (!config) {
throw createRpcError(RpcErrorType.ValidationError, "Eventstream RPC client configuration is undefined");
}
if (!config.hostName) {
throw createRpcError(RpcErrorType.ValidationError, "Eventstream RPC client configuration must have a valid host name");
}
if (typeof config.hostName !== 'string') {
throw createRpcError(RpcErrorType.ValidationError, "Eventstream RPC client configuration host name must be a string");
}
if (config.port === undefined || config.port === null) {
throw createRpcError(RpcErrorType.ValidationError, "Eventstream RPC client configuration must have a valid port");
}
if (typeof config.port !== 'number' || !Number.isSafeInteger(config.port) || config.port < 0 || config.port > 65535) {
throw createRpcError(RpcErrorType.ValidationError, "Eventstream RPC client configuration host name must be 16-bit integer");
}
}
exports.validateRpcClientConfig = validateRpcClientConfig;
/**
* @internal a rough mirror of the internal connection state, but ultimately must be independent due to the more
* complex connection establishment process (connect/connack). Used to prevent API invocations when the client
* is not in the proper state to attempt them.
*/
var ClientState;
(function (ClientState) {
ClientState[ClientState["None"] = 0] = "None";
ClientState[ClientState["Connecting"] = 1] = "Connecting";
ClientState[ClientState["Connected"] = 2] = "Connected";
ClientState[ClientState["Finished"] = 3] = "Finished";
ClientState[ClientState["Closed"] = 4] = "Closed";
})(ClientState || (ClientState = {}));
/**
* Eventstream RPC client - uses an underlying eventstream connection to implement the eventstream RPC protocol
*/
class RpcClient extends events_1.EventEmitter {
constructor(config) {
super();
this.config = config;
this.unclosedOperations = new Set();
this.state = ClientState.None;
this.emitDisconnectOnClose = false;
let connectionOptions = {
hostName: config.hostName,
port: config.port,
socketOptions: config.socketOptions,
tlsCtx: config.tlsCtx
};
try {
// consider factoring connect timeout into socket options to help bound promise resolution/wait time in
// connect()
this.connection = new aws_crt_1.eventstream.ClientConnection(connectionOptions);
}
catch (e) {
throw createRpcError(RpcErrorType.InternalError, "Failed to create eventstream connection", e);
}
}
/**
* Factory method to create a new client
*
* @param config configuration options that the new client must use
*
* Returns a new client on success, otherwise throws an RpcError
*/
static new(config) {
return new RpcClient(config);
}
/**
* Attempts to open a network connection to the configured remote endpoint. Returned promise will be fulfilled if
* the transport-level connection is successfully established and the eventstream handshake completes without
* error.
*
* Returns a promise that is resolved with additional context on a successful connection, otherwise rejected.
*
* connect() may only be called once.
*/
connect(options) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (this.state != ClientState.None) {
reject(createRpcError(RpcErrorType.ClientStateError, "RpcClient.connect() can only be called once"));
return;
}
let onDisconnectWhileConnecting = (eventData) => {
if (this.state == ClientState.Connecting) {
this.state = ClientState.Finished;
reject(createRpcError(RpcErrorType.NetworkError, "RpcClient.connect() failed - connection closed"));
setImmediate(() => { this.close(); });
}
};
this.connection.on('disconnection', onDisconnectWhileConnecting);
this.state = ClientState.Connecting;
let connack = undefined;
try {
yield this.connection.connect({
cancelController: options === null || options === void 0 ? void 0 : options.cancelController
});
// create, transform, and send the connect
let connectMessage = {
type: aws_crt_1.eventstream.MessageType.Connect
};
if (this.config.connectTransform) {
connectMessage = yield this.config.connectTransform({
message: connectMessage,
cancelController: options === null || options === void 0 ? void 0 : options.cancelController
});
}
this._applyEventstreamRpcHeadersToConnect(connectMessage);
let connackPromise = aws_crt_1.cancel.newCancellablePromiseFromNextEvent({
cancelController: options === null || options === void 0 ? void 0 : options.cancelController,
emitter: this.connection,
eventName: aws_crt_1.eventstream.ClientConnection.PROTOCOL_MESSAGE,
eventDataTransformer: (eventData) => { return eventData.message; },
cancelMessage: "Eventstream connect() cancelled by user request"
});
yield this.connection.sendProtocolMessage({
message: connectMessage,
cancelController: options === null || options === void 0 ? void 0 : options.cancelController
});
// wait for the conn ack or cancel
connack = yield connackPromise;
}
catch (err) {
if (this.state == ClientState.Connecting) {
this.state = ClientState.Finished;
setImmediate(() => { this.close(); });
}
reject(createRpcError(RpcErrorType.InternalError, "Failed to establish eventstream RPC connection", err));
return;
}
if (this.state != ClientState.Connecting) {
reject(createRpcError(RpcErrorType.InternalError, "Eventstream RPC connection attempt interrupted"));
return;
}
if (!connack || !RpcClient.isValidConnack(connack)) {
this.state = ClientState.Finished;
reject(createRpcError(RpcErrorType.HandshakeError, "Failed to establish eventstream RPC connection - invalid connack"));
setImmediate(() => { this.close(); });
return;
}
/*
* Remove the promise-rejecting disconnect listener and replace it with a regular old listener that
* doesn't reject the connect() promise since we're going to resolve it now.
*/
this.connection.removeListener('disconnection', onDisconnectWhileConnecting);
this.connection.on('disconnection', (eventData) => {
if (eventData.errorCode != 0) {
this.disconnectionReason = new aws_crt_1.CrtError(eventData.errorCode);
}
setImmediate(() => { this.close(); });
});
/* Per the client contract, we only emit disconnect after a successful connection establishment */
this.emitDisconnectOnClose = true;
this.state = ClientState.Connected;
resolve({});
}));
});
}
/**
* Returns true if the connection is currently open and ready-to-use, false otherwise.
*/
isConnected() {
return this.state == ClientState.Connected;
}
/**
* @internal
*
* Adds an unclosed operation to the set tracked by the client. When the client is closed, all unclosed operations
* will also be closed. While not foolproof, this enables us to avoid many kinds of resource leaks when the user
* doesn't do exactly what we would like them to do (which may not be obvious to them, in all fairness).
*
* @param operation unclosed operation to register
*/
registerUnclosedOperation(operation) {
if (!this.isConnected() || !this.unclosedOperations) {
throw createRpcError(RpcErrorType.ClientStateError, "Operation registration only allowed when the client is connected");
}
this.unclosedOperations.add(operation);
}
/**
* @internal
*
* Removes an unclosed operation from the set tracked by the client. When the client is closed, all unclosed operations
* will also be closed.
*
* @param operation operation to remove, presumably because it just got closed
*/
removeUnclosedOperation(operation) {
if (this.unclosedOperations) {
this.unclosedOperations.delete(operation);
}
}
/**
* Shuts down the client and begins the process of release all native resources associated with the client
* and in-progress operations. It is critical that this function be called when finished with the client;
* otherwise, native resources will leak.
*
* The client tracks unclosed operations and, as part of this process, closes them as well.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
if (this.state == ClientState.Closed) {
resolve();
return;
}
this.state = ClientState.Closed;
if (this.emitDisconnectOnClose) {
this.emitDisconnectOnClose = false;
if (!this.disconnectionReason) {
this.disconnectionReason = new aws_crt_1.CrtError("User-initiated disconnect");
}
setImmediate(() => {
this.emit('disconnection', { reason: this.disconnectionReason });
});
}
if (this.unclosedOperations) {
let unclosedOperations = this.unclosedOperations;
this.unclosedOperations = undefined;
for (const operation of unclosedOperations) {
yield operation.close();
}
}
this.connection.close();
resolve();
}
catch (err) {
reject(err);
}
}));
});
}
/**
* @internal
*
* Creates a new stream on the client's connection for an RPC operation to use.
*
* Returns a new stream on success, otherwise throws an RpcError
*/
newStream() {
if (this.state != ClientState.Connected) {
throw createRpcError(RpcErrorType.ClientStateError, "New streams may only be created while the client is connected");
}
try {
return this.connection.newStream();
}
catch (e) {
throw createRpcError(RpcErrorType.InternalError, "Failed to create new event stream", e);
}
}
on(event, listener) {
super.on(event, listener);
return this;
}
static isValidConnack(message) {
var _a;
if (message.type != aws_crt_1.eventstream.MessageType.ConnectAck) {
return false;
}
if ((((_a = message.flags) !== null && _a !== void 0 ? _a : 0) & aws_crt_1.eventstream.MessageFlags.ConnectionAccepted) == 0) {
return false;
}
return true;
}
_applyEventstreamRpcHeadersToConnect(connectMessage) {
if (!connectMessage.headers) {
connectMessage.headers = [];
}
connectMessage.headers.push(aws_crt_1.eventstream.Header.newString(':version', '0.1.0'));
}
}
exports.RpcClient = RpcClient;
/**
* Event emitted when the client's underlying network connection is ended. Only emitted if the connection
* was previously successfully established, including a successful connect/connack handshake.
*
* Listener type: {@link DisconnectionListener}
*
* @event
*/
RpcClient.DISCONNECTION = 'disconnection';
/**
* @internal a rough mirror of the internal stream binding state.
*/
var OperationState;
(function (OperationState) {
OperationState[OperationState["None"] = 0] = "None";
OperationState[OperationState["Activating"] = 1] = "Activating";
OperationState[OperationState["Activated"] = 2] = "Activated";
OperationState[OperationState["Ended"] = 3] = "Ended";
OperationState[OperationState["Closed"] = 4] = "Closed";
})(OperationState || (OperationState = {}));
/**
* @internal
*
* Common eventstream RPC operation class that includes self-cleaning functionality (via the RPC client's
* unclosed operations logic)
*/
class OperationBase extends events_1.EventEmitter {
constructor(operationConfig) {
super();
this.operationConfig = operationConfig;
this.state = OperationState.None;
this.stream = operationConfig.client.newStream();
operationConfig.client.registerUnclosedOperation(this);
}
/**
* Shuts down the operation's stream binding, with an optional flush of a termination message to the server.
* Also removes the operation from the associated client's unclosed operation set.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (this.state == OperationState.Closed) {
resolve();
return;
}
this.operationConfig.client.removeUnclosedOperation(this);
let shouldTerminateStream = this.state == OperationState.Activated;
this.state = OperationState.Closed;
if (shouldTerminateStream) {
try {
yield this.stream.sendMessage({
message: {
type: aws_crt_1.eventstream.MessageType.ApplicationMessage,
flags: aws_crt_1.eventstream.MessageFlags.TerminateStream
}
});
}
catch (e) {
// an exception generated from trying to gently end the stream should not propagate
}
}
setImmediate(() => {
this.stream.close();
});
resolve();
}));
});
}
/**
* Activates an eventstream RPC operation
*
* @param message eventstream message to send as part of stream activation
*/
activate(message) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
if (this.state != OperationState.None) {
reject(createRpcError(RpcErrorType.ClientStateError, "Eventstream operations may only have activate() invoked once"));
return;
}
this.state = OperationState.Activating;
try {
let activatePromise = this.stream.activate({
operation: this.operationConfig.name,
message: message,
cancelController: this.operationConfig.options.cancelController
});
yield activatePromise;
}
catch (e) {
if (this.state == OperationState.Activating) {
this.state = OperationState.Ended;
setImmediate(() => { this.close(); });
}
reject(createRpcError(RpcErrorType.InternalError, "Operation stream activation failure", e));
return;
}
if (this.state != OperationState.Activating) {
reject(createRpcError(RpcErrorType.InternalError, "Operation stream activation interruption"));
return;
}
this.state = OperationState.Activated;
resolve({});
}));
});
}
/**
* @return true if the stream is currently active and ready-to-use, false otherwise.
*/
isActive() {
return this.state == OperationState.Activated;
}
/**
* @return the operation's underlying event stream binding object
*/
getStream() { return this.stream; }
/**
* Set this operation state to be "Ended" so that closing the operation will not send a terminate message.
*/
setStateEnded() {
this.state = OperationState.Ended;
}
}
/**
* Implementation for request-response eventstream RPC operations.
*/
class RequestResponseOperation extends events_1.EventEmitter {
/**
* @internal
*
* @param operationConfig
* @param serviceModel
*/
constructor(operationConfig, serviceModel) {
if (!serviceModel.operations.has(operationConfig.name)) {
throw createRpcError(RpcErrorType.InternalError, `service model has no operation named ${operationConfig.name}`);
}
super();
this.operationConfig = operationConfig;
this.serviceModel = serviceModel;
this.operation = new OperationBase(this.operationConfig);
}
/**
* Performs the request-response interaction
*
* @param request modeled request data
*/
activate(request) {
return __awaiter(this, void 0, void 0, function* () {
let resultPromise = new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
let stream = this.operation.getStream();
let responsePromise = aws_crt_1.cancel.newCancellablePromiseFromNextEvent({
cancelController: this.operationConfig.options.cancelController,
emitter: stream,
eventName: aws_crt_1.eventstream.ClientStream.MESSAGE,
eventDataTransformer: (eventData) => { return eventData.message; },
cancelMessage: "Eventstream execute() cancelled by user request"
});
if (!this.operationConfig.options.disableValidation) {
validateRequest(this.serviceModel, this.operationConfig.name, request);
}
let requestMessage = serializeRequest(this.serviceModel, this.operationConfig.name, request);
yield this.operation.activate(requestMessage);
let message = yield responsePromise;
// If the server terminated the stream, then set the operation to be ended immediately
if (((_a = message.flags) !== null && _a !== void 0 ? _a : 0) & aws_crt_1.eventstream.MessageFlags.TerminateStream) {
this.operation.setStateEnded();
}
let response = deserializeResponse(this.serviceModel, this.operationConfig.name, message);
resolve(response);
}
catch (e) {
reject(e);
}
}));
let autoClosePromise = resultPromise.finally(() => __awaiter(this, void 0, void 0, function* () { yield this.operation.close(); }));
return autoClosePromise;
});
}
}
exports.RequestResponseOperation = RequestResponseOperation;
/**
* Implementation of a bi-direction streaming operation.
*
* TODO: may change slightly for uni-directional operations
*/
class StreamingOperation extends events_1.EventEmitter {
/**
* @internal
*
* @param request
* @param operationConfig
* @param serviceModel
*/
constructor(request, operationConfig, serviceModel) {
if (!serviceModel.operations.has(operationConfig.name)) {
throw createRpcError(RpcErrorType.InternalError, `service model has no operation named ${operationConfig.name}`);
}
if (!operationConfig.options.disableValidation) {
validateRequest(serviceModel, operationConfig.name, request);
}
super();
this.request = request;
this.operationConfig = operationConfig;
this.serviceModel = serviceModel;
this.operation = new OperationBase(operationConfig);
this.responseHandled = false;
}
/**
* Activates a streaming operation
*/
activate() {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
var _a;
try {
let stream = this.operation.getStream();
stream.addListener(aws_crt_1.eventstream.ClientStream.MESSAGE, this._onStreamMessageEvent.bind(this));
stream.addListener(aws_crt_1.eventstream.ClientStream.ENDED, this._onStreamEndedEvent.bind(this));
let responsePromise = aws_crt_1.cancel.newCancellablePromiseFromNextEvent({
cancelController: this.operationConfig.options.cancelController,
emitter: stream,
eventName: aws_crt_1.eventstream.ClientStream.MESSAGE,
eventDataTransformer: (eventData) => {
this.responseHandled = true;
return eventData.message;
},
cancelMessage: "Eventstream execute() cancelled by user request"
});
let requestMessage = serializeRequest(this.serviceModel, this.operationConfig.name, this.request);
yield this.operation.activate(requestMessage);
let message = yield responsePromise;
let response = deserializeResponse(this.serviceModel, this.operationConfig.name, message);
// If the server terminated the stream, then set the operation to be ended immediately
if (((_a = message.flags) !== null && _a !== void 0 ? _a : 0) & aws_crt_1.eventstream.MessageFlags.TerminateStream) {
this.operation.setStateEnded();
// Server hung up on us. Immediately cleanup the operation state.
// Do this before resolving the promise so that any user-initiated
// requests will see the correct state, which is that the operation is closed.
yield this.close();
}
resolve(response);
}
catch (e) {
yield this.close();
reject(e);
}
}));
});
}
/**
* Sends an outbound message on a streaming operation, if the operation allows outbound streaming messages.
*
* @param message modeled data to send
*/
sendMessage(message) {
return __awaiter(this, void 0, void 0, function* () {
return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
try {
if (!doesOperationAllowOutboundMessages(this.serviceModel, this.operationConfig.name)) {
throw createRpcError(RpcErrorType.ValidationError, `Operation '${this.operationConfig.name}' does not allow outbound streaming messages.`);
}
if (!this.operationConfig.options.disableValidation) {
validateOutboundMessage(this.serviceModel, this.operationConfig.name, message);
}
let serializedMessage = serializeOutboundMessage(this.serviceModel, this.operationConfig.name, message);
let stream = this.operation.getStream();
yield stream.sendMessage({
message: serializedMessage,
cancelController: this.operationConfig.options.cancelController
});
resolve();
}
catch (err) {
reject(err);
}
}));
});
}
/**
* Asynchronous close method for the underlying event stream. The user should call this function when finished
* with the operation in order to clean up native resources. Failing to do so will cause the native resources
* to persist until the client is closed. If the client is never closed then every unclosed operation will leak.
*/
close() {
return __awaiter(this, void 0, void 0, function* () {
return this.operation.close();
});
}
on(event, listener) {
super.on(event, listener);
return this;
}
_onStreamMessageEvent(eventData) {
if (this.responseHandled) {
try {
let streamingMessage = deserializeInboundMessage(this.serviceModel, this.operationConfig.name, eventData.message);
setImmediate(() => {
this.emit(StreamingOperation.MESSAGE, streamingMessage);
});
}
catch (err) {
setImmediate(() => { this.emit(StreamingOperation.STREAM_ERROR, err); });
}
}
}
_onStreamEndedEvent(eventData) {
setImmediate(() => __awaiter(this, void 0, void 0, function* () {
this.emit(StreamingOperation.ENDED, {});
yield this.close();
}));
}
}
exports.StreamingOperation = StreamingOperation;
/**
* Event emitted when the operation's stream has ended. Only emitted if the stream was successfully activated.
*
* Listener type: {@link StreamingOperationEndedListener}
*
* @event
*/
StreamingOperation.ENDED = 'ended';
/**
* Event emitted when an incoming eventstream message resulted in some kind of error. Usually this is either
* a modeled service error or a deserialization error for messages that cannot be mapped to the service model.
*
* Listener type: {@link StreamingRpcErrorListener}
*
* @event
*/
StreamingOperation.STREAM_ERROR = 'streamError';
/**
* Event emitted when an incoming eventstream message is successfully deserialized into a modeled inbound streaming
* shape type.
*
* @event
*/
StreamingOperation.MESSAGE = 'message';
/**
* Utility function to create RpcError errors
*
* @param type type of error
* @param description longer description of error
* @param internalError optional CrtError that caused this error
* @param serviceError optional modeled eventstream RPC service error that triggered this error
*
* @return a new RpcError object
*/
function createRpcError(type, description, internalError, serviceError) {
return new RpcError({
type: type,
description: description,
internalError: internalError,
serviceError: serviceError
});
}
exports.createRpcError = createRpcError;
const SERVICE_MODEL_TYPE_HEADER_NAME = 'service-model-type';
const CONTENT_TYPE_HEADER_NAME = ':content-type';
const CONTENT_TYPE_PLAIN_TEXT = 'text/plain';
function getEventStreamMessageHeaderValueAsString(message, headerName) {
if (!message.headers) {
return undefined;
}
try {
for (const header of message.headers) {
if (header.name === headerName) {
return header.asString();
}
}
}
catch (err) {
return undefined;
}
return undefined;
}
function validateShape(model, shapeName, shape) {
if (!shape) {
throw createRpcError(RpcErrorType.ValidationError, `Shape of type '${shapeName}' is undefined`);
}
let validator = model.validators.get(shapeName);
if (!validator) {
throw createRpcError(RpcErrorType.ValidationError, `No shape named '${shapeName}' exists in the service model`);
}
validator(shape);
}
function validateOperationShape(model, operationName, shape, shapeSelector) {
let operation = model.operations.get(operationName);
if (!operation) {
throw createRpcError(RpcErrorType.InternalError, `No operation named '${operationName}' exists in the service model`);
}
let selectedShape = shapeSelector(operation);
if (!selectedShape) {
throw createRpcError(RpcErrorType.ValidationError, `Operation '${operationName}' does not have a defined selection shape`);
}
return validateShape(model, selectedShape, shape);
}
function validateRequest(model, operationName, request) {
validateOperationShape(model, operationName, request, (operation) => { return operation.requestShape; });
}
function validateOutboundMessage(model, operationName, message) {
validateOperationShape(model, operationName, message, (operation) => { return operation.outboundMessageShape; });
}
function doesOperationAllowOutboundMessages(model, operationName) {
let operation = model.operations.get(operationName);
if (!operation) {
throw createRpcError(RpcErrorType.InternalError, `No operation named '${operationName}' exists in the service model`);
}
return operation.outboundMessageShape !== undefined;
}
function serializeMessage(model, operationName, message, shapeSelector) {
let operation = model.operations.get(operationName);
if (!operation) {
throw createRpcError(RpcErrorType.InternalError, `No operation named '${operationName}' exists in the service model`);
}
let shapeName = shapeSelector(operation);
if (!shapeName) {
throw createRpcError(RpcErrorType.InternalError, `Operation '${operationName}' does not have a defined selection shape`);
}
let serializer = model.serializers.get(shapeName);
if (!serializer) {
throw createRpcError(RpcErrorType.InternalError, `No top-level shape serializer for '${shapeName}' exists in the service model`);
}
return serializer(message);
}
function serializeRequest(model, operationName, request) {
return serializeMessage(model, operationName, request, (operation) => { return operation.requestShape; });
}
function serializeOutboundMessage(model, operationName, message) {
return serializeMessage(model, operationName, message, (operation) => { return operation.outboundMessageShape; });
}
function throwResponseError(model, errorShapes, shapeName, message) {
if (!shapeName) {
if (message.type != aws_crt_1.eventstream.MessageType.ApplicationMessage) {
if (message.type == aws_crt_1.eventstream.MessageType.ApplicationError) {
let contentType = getEventStreamMessageHeaderValueAsString(message, CONTENT_TYPE_HEADER_NAME);
if (contentType && contentType === CONTENT_TYPE_PLAIN_TEXT) {
let payloadAsString = (0, util_utf8_browser_1.toUtf8)(new Uint8Array(message.payload));
;
throw createRpcError(RpcErrorType.InternalError, `Eventstream (response) message was not a modelled shape. Plain text payload is: '${payloadAsString}'`);
}
}
}
throw createRpcError(RpcErrorType.InternalError, "Eventstream (response) message was not an application message");
}
let isErrorShape = errorShapes.has(shapeName);
let serviceError = undefined;
if (isErrorShape) {
let errorDeserializer = model.deserializers.get(shapeName);
if (errorDeserializer) {
serviceError = errorDeserializer(message);
}
}
let errorType = serviceError ? RpcErrorType.ServiceError : RpcErrorType.InternalError;
let errorDescription = serviceError ? "Eventstream RPC request failed. Check serviceError property for details." : `Unexpected response shape received: '${shapeName}'`;
let rpcError = createRpcError(errorType, errorDescription, undefined, serviceError);
throw rpcError;
}
function deserializeMessage(model, operationName, message, shapeSelector) {
let operation = model.operations.get(operationName);
if (!operation) {
throw createRpcError(RpcErrorType.InternalError, `No operation named '${operationName}' exists in the service model`);
}
let messageShape = getEventStreamMessageHeaderValueAsString(message, SERVICE_MODEL_TYPE_HEADER_NAME);
let operationShape = shapeSelector(operation);
if (!messageShape || messageShape !== operationShape || !operationShape) {
throwResponseError(model, operation.errorShapes, messageShape, message);
return;
}
let deserializer = model.deserializers.get(operationShape);
if (!deserializer) {
throw createRpcError(RpcErrorType.InternalError, `No top-level shape deserializer for '${operationShape}' exists in the service model`);
}
let response = deserializer(message);
return response;
}
function deserializeResponse(model, operationName, message) {
return deserializeMessage(model, operationName, message, (operation) => { return operation.responseShape; });
}
function deserializeInboundMessage(model, operationName, message) {
return deserializeMessage(model, operationName, message, (operation) => { return operation.inboundMessageShape; });
}
//# sourceMappingURL=eventstream_rpc.js.map