diff --git a/package-lock.json b/package-lock.json index a539f8d..9a5b443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,6 @@ "@babel/preset-env": "^7.25.0", "@babel/preset-typescript": "^7.24.7", "@types/k6": "^0.52.0", - "@types/uuid": "^10.0.0", "@types/webpack": "^5.28.5", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", @@ -2344,13 +2343,6 @@ "undici-types": "~6.11.1" } }, - "node_modules/@types/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/webpack": { "version": "5.28.5", "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-5.28.5.tgz", diff --git a/package.json b/package.json index ce7acf3..ead91e7 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "@babel/preset-env": "^7.25.0", "@babel/preset-typescript": "^7.24.7", "@types/k6": "^0.52.0", - "@types/uuid": "^10.0.0", "@types/webpack": "^5.28.5", "@typescript-eslint/eslint-plugin": "^7.17.0", "@typescript-eslint/parser": "^7.17.0", diff --git a/src/internal/client.ts b/src/internal/client.ts index 0e8c3f8..5122647 100644 --- a/src/internal/client.ts +++ b/src/internal/client.ts @@ -1,6 +1,20 @@ +import { RefinedResponse, ResponseType } from 'k6/http' + import { AWSConfig } from './config' import { Endpoint } from './endpoint' import { HTTPHeaders } from './http' +import { + GeneralErrorKind, + DNSErrorKind, + TCPErrorKind, + TLSErrorKind, + HTTP2ErrorKind, + GeneralError, + DNSError, + TCPError, + TLSError, + HTTP2Error, +} from './error' /** * Class allowing to build requests targeting AWS APIs @@ -60,6 +74,64 @@ export class AWSClient { public set endpoint(endpoint: Endpoint) { this._endpoint = endpoint } + + /** + * Handles the k6 http response potential errors produced when making a + * request to an AWS service. + * + * Importantly, this method only handles errors that emerge from the k6 http client itself, and + * won't handle AWS specific errors. To handle AWS specific errors, client classes are + * expected to implement their own error handling logic by overriding this method. + * + * @param response {RefinedResponse} the response received by the k6 http client + * @param operation {string | undefined } the name of the operation that was attempted when the error occurred + * @param {boolean} returns true if an error was handled, false otherwise + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + protected handleError(response: RefinedResponse, operation?: string): boolean { + const status: number = response.status + const errorCode: number = response.error_code + const errorMessage: string = response.error + + // We consider codes 200-299 as success. + // + // We do not consider 3xx as success as some services such as S3 can use + // 301 to indicate a bucket not found + if (status >= 200 && status < 300 && errorMessage == '' && errorCode === 0) { + return false + } + + switch (errorCode) { + case GeneralErrorKind.GenericError: + case GeneralErrorKind.NonTCPNetworkError: + case GeneralErrorKind.InvalidURL: + case GeneralErrorKind.HTTPRequestTimeout: + throw new GeneralError(errorCode); + case DNSErrorKind.GenericDNSError: + case DNSErrorKind.NoIPFound: + case DNSErrorKind.BlacklistedIP: + case DNSErrorKind.BlacklistedHostname: + throw new DNSError(errorCode); + case TCPErrorKind.GenericTCPError: + case TCPErrorKind.BrokenPipeOnWrite: + case TCPErrorKind.UnknownTCPError: + case TCPErrorKind.GeneralTCPDialError: + case TCPErrorKind.DialTimeoutError: + case TCPErrorKind.DialConnectionRefused: + case TCPErrorKind.DialUnknownError: + case TCPErrorKind.ResetByPeer: + throw new TCPError(errorCode); + case TLSErrorKind.GeneralTLSError: + case TLSErrorKind.UnknownAuthority: + case TLSErrorKind.CertificateHostnameMismatch: + throw new TLSError(errorCode); + case HTTP2ErrorKind.GenericHTTP2Error: + case HTTP2ErrorKind.GeneralHTTP2GoAwayError: + throw new HTTP2Error(errorCode); + } + + return true + } } /** diff --git a/src/internal/error.ts b/src/internal/error.ts index 1e7760a..46a52fa 100644 --- a/src/internal/error.ts +++ b/src/internal/error.ts @@ -32,12 +32,19 @@ export class AWSError extends Error { * Parse an AWSError from an XML document * * @param {string} xmlDocument - Serialized XML document to parse the error from + * @returns {AWSError} - The parsed AWSError object */ static parseXML(xmlDocument: string): AWSError { const doc = parseHTML(xmlDocument) return new AWSError(doc.find('Message').text(), doc.find('Code').text()) } + /** + * Parse an AWSError from a Response object + * + * @param {Response} response - The Response object to parse the error from + * @returns {AWSError} - The parsed AWSError object + */ static parse(response: Response): AWSError { if (response.headers['Content-Type'] === 'application/json') { const error = (response.json() as JSONObject) || {} @@ -53,3 +60,207 @@ export class AWSError extends Error { } } } + +/** + * Base class for network errors as produced by k6. + * + * Based on the network error handling in k6, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + * + * @typeparam N - The name of the network error + * @typeparam K - The kind of the network error + */ +export class NetworkError extends Error { + code: K + name: N + + /** + * Create a NetworkError + * + * @param {N} name - The name of the network error + * @param {K} code - The kind of the network error + */ + constructor(name: N, code: K) { + super(ErrorMessages[code] || 'An unknown error occurred') + this.name = name + this.code = code + } +} + +/** + * Represents a general network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export class GeneralError extends NetworkError<'GeneralError', GeneralErrorKind> { + /** + * Create a GeneralError + * + * @param {GeneralErrorKind} code - The kind of the general error + */ + constructor(code: GeneralErrorKind) { + super('GeneralError', code) + } +} + +/** + * Represents a DNS-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export class DNSError extends NetworkError<'DNSError', DNSErrorKind> { + /** + * Create a DNSError + * + * @param {DNSErrorKind} code - The kind of the DNS error + */ + constructor(code: DNSErrorKind) { + super('DNSError', code) + } +} + +/** + * Represents a TCP-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export class TCPError extends NetworkError<'TCPError', TCPErrorKind> { + /** + * Create a TCPError + * + * @param {TCPErrorKind} code - The kind of the TCP error + */ + constructor(code: TCPErrorKind) { + super('TCPError', code) + } +} + +/** + * Represents a TLS-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export class TLSError extends NetworkError<'TLSError', TLSErrorKind> { + /** + * Create a TLSError + * + * @param {TLSErrorKind} code - The kind of the TLS error + */ + constructor(code: TLSErrorKind) { + super('TLSError', code) + } +} + +/** + * Represents an HTTP/2-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export class HTTP2Error extends NetworkError<'HTTP2Error', HTTP2ErrorKind> { + /** + * Create an HTTP2Error + * + * @param {HTTP2ErrorKind} code - The kind of the HTTP/2 error + */ + constructor(code: HTTP2ErrorKind) { + super('HTTP2Error', code) + } +} + +/** + * Represents the name of a network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +type NetworkErrorName = 'GeneralError' | 'DNSError' | 'TCPError' | 'TLSError' | 'HTTP2Error' + +/** + * Represents the kind of a network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +type ErrorKind = GeneralErrorKind | DNSErrorKind | TCPErrorKind | TLSErrorKind | HTTP2ErrorKind + +/** + * Represents the kind of a general network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export enum GeneralErrorKind { + GenericError = 1000, + NonTCPNetworkError = 1010, + InvalidURL = 1020, + HTTPRequestTimeout = 1050, +} + +/** + * Represents the kind of a DNS-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export enum DNSErrorKind { + GenericDNSError = 1100, + NoIPFound = 1101, + BlacklistedIP = 1110, + BlacklistedHostname = 1111, +} + +/** + * Represents the kind of a TCP-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export enum TCPErrorKind { + GenericTCPError = 1200, + BrokenPipeOnWrite = 1201, + UnknownTCPError = 1202, + GeneralTCPDialError = 1210, + DialTimeoutError = 1211, + DialConnectionRefused = 1212, + DialUnknownError = 1213, + ResetByPeer = 1220, +} + +/** + * Represents the kind of a TLS-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export enum TLSErrorKind { + GeneralTLSError = 1300, + UnknownAuthority = 1310, + CertificateHostnameMismatch = 1311, +} + +/** + * Represents the kind of an HTTP/2-related network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +export enum HTTP2ErrorKind { + GenericHTTP2Error = 1600, + GeneralHTTP2GoAwayError = 1610, +} + +/** + * Error messages for each kind of network error, as described in: + * https://grafana.com/docs/k6/latest/javascript-api/error-codes/ + */ +const ErrorMessages: { [key in ErrorKind]: string } = { + [GeneralErrorKind.GenericError]: 'A generic error that isn’t any of the ones listed below', + [GeneralErrorKind.NonTCPNetworkError]: + 'A non-TCP network error - this is a placeholder and there is no error currently known to trigger it', + [GeneralErrorKind.InvalidURL]: 'An invalid URL was specified', + [GeneralErrorKind.HTTPRequestTimeout]: 'The HTTP request has timed out', + [DNSErrorKind.GenericDNSError]: 'A generic DNS error that isn’t any of the ones listed below', + [DNSErrorKind.NoIPFound]: 'No IP for the provided host was found', + [DNSErrorKind.BlacklistedIP]: + 'Blacklisted IP was resolved or a connection to such was tried to be established', + [DNSErrorKind.BlacklistedHostname]: 'Blacklisted hostname using The Block Hostnames option', + [TCPErrorKind.GenericTCPError]: 'A generic TCP error that isn’t any of the ones listed below', + [TCPErrorKind.BrokenPipeOnWrite]: + 'A “broken pipe” on write - the other side has likely closed the connection', + [TCPErrorKind.UnknownTCPError]: + 'An unknown TCP error - We got an error that we don’t recognize but it is from the operating system and has errno set on it. The message in error includes the operation(write,read) and the errno, the OS, and the original message of the error', + [TCPErrorKind.GeneralTCPDialError]: 'General TCP dial error', + [TCPErrorKind.DialTimeoutError]: 'Dial timeout error - the timeout for the dial was reached', + [TCPErrorKind.DialConnectionRefused]: + 'Dial connection refused - the connection was refused by the other party on dial', + [TCPErrorKind.DialUnknownError]: 'Dial unknown error', + [TCPErrorKind.ResetByPeer]: + 'Reset by peer - the connection was reset by the other party, most likely a server', + [TLSErrorKind.GeneralTLSError]: 'General TLS error', + [TLSErrorKind.UnknownAuthority]: 'Unknown authority - the certificate issuer is unknown', + [TLSErrorKind.CertificateHostnameMismatch]: 'The certificate doesn’t match the hostname', + [HTTP2ErrorKind.GenericHTTP2Error]: + 'A generic HTTP/2 error that isn’t any of the ones listed below', + [HTTP2ErrorKind.GeneralHTTP2GoAwayError]: 'A general HTTP/2 GoAway error', +} diff --git a/src/internal/event-bridge.ts b/src/internal/event-bridge.ts index aa8ab88..38b0bb7 100644 --- a/src/internal/event-bridge.ts +++ b/src/internal/event-bridge.ts @@ -70,18 +70,17 @@ export class EventBridgeClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(EventBridgeOperation.PutEvents, res) + this.handleError(res, EventBridgeOperation.PutEvents) } - _handle_error( - operation: EventBridgeOperation, - response: RefinedResponse - ) { - const errorCode = response.error_code - if (errorCode === 0) { - return + + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation); + if (!errored) { + return false } + const errorCode = response.error_code const error = response.json() as JSONObject if (errorCode >= 1400 && errorCode <= 1499) { // In the event of certain errors, the message is not set. @@ -95,16 +94,18 @@ export class EventBridgeClient extends AWSClient { } // Otherwise throw a standard service error - throw new EventBridgeServiceError(errorMessage, error.__type as string, operation) + throw new EventBridgeServiceError(errorMessage, error.__type as string, operation as EventBridgeOperation) } if (errorCode === 1500) { throw new EventBridgeServiceError( 'An error occured on the server side', 'InternalServiceError', - operation + operation as EventBridgeOperation ) } + + return true } } diff --git a/src/internal/kinesis.ts b/src/internal/kinesis.ts index f1f30d5..6cbc18c 100644 --- a/src/internal/kinesis.ts +++ b/src/internal/kinesis.ts @@ -281,23 +281,18 @@ export class KinesisClient extends AWSClient { headers: signedRequest.headers, }) - this._handle_error(action, res) + this.handleError(res, action) return res } - /** - * If the response is an error, throw an error - * - * @param {string} operation - The name of the operation that was called. - * @param response - RefinedResponse - * @returns The response is being returned. - */ - _handle_error(operation: string, response: RefinedResponse) { - const errorCode = response.error_code - if (errorCode === 0) { - return + + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation); + if (!errored) { + return false } + const errorCode = response.error_code const error = response.json() as JSONObject if (errorCode >= 1400 && errorCode <= 1499) { // In the event of certain errors, the message is not set. @@ -311,16 +306,18 @@ export class KinesisClient extends AWSClient { } // Otherwise throw a standard service error - throw new KinesisServiceError(errorMessage, error.__type as string, operation) + throw new KinesisServiceError(errorMessage, error.__type as string, operation || 'Unknown') } if (errorCode === 1500) { throw new KinesisServiceError( 'An error occured on the server side', 'InternalServiceError', - operation + operation || 'Unknown' ) } + + return true } } diff --git a/src/internal/kms.ts b/src/internal/kms.ts index 175b6ae..866bf1c 100644 --- a/src/internal/kms.ts +++ b/src/internal/kms.ts @@ -69,7 +69,7 @@ export class KMSClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(KMSOperation.ListKeys, res) + this.handleError(res, KMSOperation.ListKeys) const json: JSONArray = res.json('Keys') as JSONArray return json.map((k) => KMSKey.fromJSON(k as JSONObject)) @@ -114,17 +114,18 @@ export class KMSClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(KMSOperation.GenerateDataKey, res) + this.handleError(res, KMSOperation.GenerateDataKey) return KMSDataKey.fromJSON(res.json() as JSONObject) } - _handle_error(operation: KMSOperation, response: RefinedResponse) { - const errorCode = response.error_code - if (errorCode === 0) { - return + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation); + if (!errored) { + return false } + const errorCode = response.error_code const error = response.json() as JSONObject if (errorCode >= 1400 && errorCode <= 1499) { // In the event of certain errors, the message is not set. @@ -138,16 +139,18 @@ export class KMSClient extends AWSClient { } // Otherwise throw a standard service error - throw new KMSServiceError(errorMessage, error.__type as string, operation) + throw new KMSServiceError(errorMessage, error.__type as string, operation as KMSOperation) } if (errorCode === 1500) { throw new KMSServiceError( 'An error occured on the server side', 'InternalServiceError', - operation + operation as KMSOperation ) } + + return true } } diff --git a/src/internal/lambda.ts b/src/internal/lambda.ts index be44356..4a0639e 100644 --- a/src/internal/lambda.ts +++ b/src/internal/lambda.ts @@ -81,7 +81,7 @@ export class LambdaClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(res) + this.handleError(res) const logResult = res.headers['X-Amz-Log-Result'] const response = { @@ -99,12 +99,11 @@ export class LambdaClient extends AWSClient { } } - private _handle_error(response: RefinedResponse) { - const errorCode: number = response.error_code - const errorMessage: string = response.error - if (errorMessage == '' && errorCode === 0) { - return + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation); + if (!errored) { + return false; } const awsError = AWSError.parse(response) @@ -115,6 +114,8 @@ export class LambdaClient extends AWSClient { default: throw awsError } + + return true } } diff --git a/src/internal/s3.ts b/src/internal/s3.ts index f9debdf..8a32448 100644 --- a/src/internal/s3.ts +++ b/src/internal/s3.ts @@ -63,7 +63,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('ListBuckets', res) + this.handleError(res, 'ListBuckets') const buckets: Array = [] @@ -122,7 +122,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('ListObjectsV2', res) + this.handleError(res, 'ListObjectsV2') const objects: Array = [] @@ -185,7 +185,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, null, { headers: signedRequest.headers, }) - this._handle_error('GetObject', res) + this.handleError(res, 'GetObject') return new S3Object( objectKey, @@ -243,7 +243,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error('PutObject', res) + this.handleError(res, 'PutObject') } /** @@ -272,7 +272,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('DeleteObject', res) + this.handleError(res, 'DeleteObject') } /** @@ -312,7 +312,7 @@ export class S3Client extends AWSClient { headers: signedRequest.headers, }) - this._handle_error('CopyObject', res) + this.handleError(res, 'CopyObject') } /** @@ -345,7 +345,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('CreateMultipartUpload', res) + this.handleError(res, 'CreateMultipartUpload') return new S3MultipartUpload( objectKey, @@ -395,7 +395,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('UploadPart', res) + this.handleError(res, 'UploadPart') return new S3Part(partNumber, res.headers['Etag']) } @@ -445,8 +445,7 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - - this._handle_error('CompleteMultipartUpload', res) + this.handleError(res, 'CompleteMultipartUpload') } /** @@ -480,18 +479,15 @@ export class S3Client extends AWSClient { const res = await http.asyncRequest(method, signedRequest.url, signedRequest.body || null, { headers: signedRequest.headers, }) - this._handle_error('AbortMultipartUpload', res) + this.handleError(res, 'AbortMultipartUpload') } - _handle_error(operation: S3Operation, response: RefinedResponse) { - const status: number = response.status - const errorCode: number = response.error_code - const errorMessage: string = response.error - - // We consider codes 200-299 as success - if (status >= 200 && status < 300 && errorMessage == '' && errorCode === 0) { - return - } + handleError(response: RefinedResponse, operation?: string): boolean { + // As we are overriding the AWSClient method: call the parent class handleError method + const errored = super.handleError(response); + if (!errored) { + return false; + } // A 301 response is returned when the bucket is not found. // Generally meaning that either the bucket name is wrong or the @@ -499,8 +495,9 @@ export class S3Client extends AWSClient { // // See: https://github.com/grafana/k6/issues/2474 // See: https://github.com/golang/go/issues/49281 - if (status == 301 || (errorMessage && errorMessage.startsWith('301'))) { - throw new S3ServiceError('Resource not found', 'ResourceNotFound', operation) + const errorMessage: string = response.error + if (response.status == 301 || (errorMessage && errorMessage.startsWith('301'))) { + throw new S3ServiceError('Resource not found', 'ResourceNotFound', operation as S3Operation) } const awsError = AWSError.parseXML(response.body as string) @@ -508,7 +505,7 @@ export class S3Client extends AWSClient { case 'AuthorizationHeaderMalformed': throw new InvalidSignatureError(awsError.message, awsError.code) default: - throw new S3ServiceError(awsError.message, awsError.code || 'unknown', operation) + throw new S3ServiceError(awsError.message, awsError.code || 'unknown', operation as S3Operation) } } } diff --git a/src/internal/secrets-manager.ts b/src/internal/secrets-manager.ts index 150e07f..1377565 100644 --- a/src/internal/secrets-manager.ts +++ b/src/internal/secrets-manager.ts @@ -70,7 +70,7 @@ export class SecretsManagerClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(SecretsManagerOperation.ListSecrets, res) + this.handleError(res, SecretsManagerOperation.ListSecrets) const json: JSONArray = res.json('SecretList') as JSONArray return json.map((s) => Secret.fromJSON(s as JSONObject)) @@ -103,7 +103,7 @@ export class SecretsManagerClient extends AWSClient { headers: signedRequest.headers, }) - this._handle_error(SecretsManagerOperation.GetSecretValue, res) + this.handleError(res, SecretsManagerOperation.GetSecretValue) return Secret.fromJSON(res.json() as JSONObject) } @@ -162,7 +162,7 @@ export class SecretsManagerClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(SecretsManagerOperation.CreateSecret, res) + this.handleError(res, SecretsManagerOperation.CreateSecret) return Secret.fromJSON(res.json() as JSONObject) } @@ -202,7 +202,7 @@ export class SecretsManagerClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(SecretsManagerOperation.PutSecretValue, res) + this.handleError(res, SecretsManagerOperation.PutSecretValue) return Secret.fromJSON(res.json() as JSONObject) } @@ -251,18 +251,17 @@ export class SecretsManagerClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(SecretsManagerOperation.DeleteSecret, res) + this.handleError(res, SecretsManagerOperation.DeleteSecret) } - _handle_error( - operation: SecretsManagerOperation, - response: RefinedResponse - ) { - const errorCode = response.error_code - if (errorCode === 0) { - return + + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation) + if (!errored) { + return false } + const errorCode = response.error_code const error = response.json() as JSONObject if (errorCode >= 1400 && errorCode <= 1499) { // In the event of certain errors, the message is not set. @@ -276,16 +275,18 @@ export class SecretsManagerClient extends AWSClient { } // Otherwise throw a standard service error - throw new SecretsManagerServiceError(errorMessage, error.__type as string, operation) + throw new SecretsManagerServiceError(errorMessage, error.__type as string, operation as SecretsManagerOperation) } if (errorCode === 1500) { throw new SecretsManagerServiceError( 'An error occured on the server side', 'InternalServiceError', - operation + operation as SecretsManagerOperation ) } + + return true } } diff --git a/src/internal/signature.ts b/src/internal/signature.ts index 79407d3..847a722 100644 --- a/src/internal/signature.ts +++ b/src/internal/signature.ts @@ -231,7 +231,6 @@ export class SignatureV4 { if (this.credentials.sessionToken) { request.query[constants.AMZ_TOKEN_QUERY_PARAM] = this.credentials.sessionToken } - // Add base signing query parameters to the request, as described in the documentation // @see https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html request.query[constants.AMZ_ALGORITHM_QUERY_PARAM] = constants.SIGNING_ALGORITHM_IDENTIFIER @@ -270,7 +269,7 @@ export class SignatureV4 { ) // If a request path was provided, add it to the URL - let url = request.endpoint.href + let url = originalRequest.endpoint.href if (request.path) { // Ensure there is a trailing slash at the end of the URL // so that appending the path does not result in a malformed URL. diff --git a/src/internal/sqs.ts b/src/internal/sqs.ts index b65aef8..35c8f77 100644 --- a/src/internal/sqs.ts +++ b/src/internal/sqs.ts @@ -235,6 +235,40 @@ export class SQSClient extends AWSClient { default: throw new SQSServiceError(errorMessage, error.__type as string, operation) } + + return true + } + + protected handleError( + response: RefinedResponse, + operation?: string + ): boolean { + const errored = super.handleError(response) + if (!errored) { + return false + } + + const errorCode: number = response.error_code + + if (errorCode === 0) { + return false + } + + const error = response.json() as JSONObject + + const errorMessage: string = + (error.Message as string) || (error.message as string) || (error.__type as string) + + switch (error.__type) { + case 'InvalidSignatureException': + throw new InvalidSignatureError(errorMessage, error.__type) + default: + throw new SQSServiceError( + errorMessage, + error.__type as string, + operation as SQSOperation + ) + } } } diff --git a/src/internal/ssm.ts b/src/internal/ssm.ts index b9e8aeb..d866d21 100644 --- a/src/internal/ssm.ts +++ b/src/internal/ssm.ts @@ -73,20 +73,18 @@ export class SystemsManagerClient extends AWSClient { const res = await http.asyncRequest(this.method, signedRequest.url, signedRequest.body, { headers: signedRequest.headers, }) - this._handle_error(SystemsManagerOperation.GetParameter, res) + this.handleError(res, SystemsManagerOperation.GetParameter) return SystemsManagerParameter.fromJSON(res.json() as JSONObject) } - _handle_error( - operation: SystemsManagerOperation, - response: RefinedResponse - ) { - const errorCode = response.error_code - if (errorCode === 0) { - return + protected handleError(response: RefinedResponse, operation?: string): boolean { + const errored = super.handleError(response, operation); + if (!errored) { + return false } + const errorCode = response.error_code const error = response.json() as JSONObject if (errorCode >= 1400 && errorCode <= 1499) { // In the event of certain errors, the message is not set. @@ -100,16 +98,18 @@ export class SystemsManagerClient extends AWSClient { } // Otherwise throw a standard service error - throw new SystemsManagerServiceError(errorMessage, error.__type as string, operation) + throw new SystemsManagerServiceError(errorMessage, error.__type as string, operation as SystemsManagerOperation) } if (errorCode === 1500) { throw new SystemsManagerServiceError( 'An error occured on the server side', 'InternalServiceError', - operation + operation as SystemsManagerOperation ) } + + return true } }