diff --git a/.changeset/fluffy-deers-doubt.md b/.changeset/fluffy-deers-doubt.md
new file mode 100644
index 00000000000..a26eca305e9
--- /dev/null
+++ b/.changeset/fluffy-deers-doubt.md
@@ -0,0 +1,8 @@
+---
+"@smithy/middleware-serde": minor
+"@smithy/util-base64": minor
+"@smithy/util-utf8": minor
+"@smithy/types": minor
+---
+
+encoders allow string inputs
diff --git a/packages/middleware-serde/src/deserializerMiddleware.spec.ts b/packages/middleware-serde/src/deserializerMiddleware.spec.ts
index e09717c9b00..2e870e02e69 100644
--- a/packages/middleware-serde/src/deserializerMiddleware.spec.ts
+++ b/packages/middleware-serde/src/deserializerMiddleware.spec.ts
@@ -1,3 +1,5 @@
+import { EndpointBearer, SerdeFunctions } from "@smithy/types";
+
import { deserializerMiddleware } from "./deserializerMiddleware";
describe("deserializerMiddleware", () => {
@@ -11,7 +13,7 @@ describe("deserializerMiddleware", () => {
hostname: "hostname",
path: "path",
}),
- };
+ } as EndpointBearer & SerdeFunctions;
const mockArgs = {
input: {
diff --git a/packages/middleware-serde/src/deserializerMiddleware.ts b/packages/middleware-serde/src/deserializerMiddleware.ts
index b61bce26d15..ab4c2cf7b11 100644
--- a/packages/middleware-serde/src/deserializerMiddleware.ts
+++ b/packages/middleware-serde/src/deserializerMiddleware.ts
@@ -5,11 +5,17 @@ import {
DeserializeMiddleware,
HandlerExecutionContext,
ResponseDeserializer,
+ SerdeFunctions,
} from "@smithy/types";
-export const deserializerMiddleware = (
- options: RuntimeUtils,
- deserializer: ResponseDeserializer
+/**
+ * @internal
+ *
+ * 3rd type parameter is deprecated and unused.
+ */
+export const deserializerMiddleware = (
+ options: SerdeFunctions,
+ deserializer: ResponseDeserializer
): DeserializeMiddleware => (
next: DeserializeHandler,
context: HandlerExecutionContext
diff --git a/packages/middleware-serde/src/serdePlugin.ts b/packages/middleware-serde/src/serdePlugin.ts
index 419bb2de67d..c6db5c247f7 100644
--- a/packages/middleware-serde/src/serdePlugin.ts
+++ b/packages/middleware-serde/src/serdePlugin.ts
@@ -8,6 +8,7 @@ import {
Provider,
RequestSerializer,
ResponseDeserializer,
+ SerdeFunctions,
SerializeHandlerOptions,
UrlParser,
} from "@smithy/types";
@@ -39,14 +40,19 @@ export type V1OrV2Endpoint = {
endpoint?: Provider;
};
-export function getSerdePlugin(
- config: V1OrV2Endpoint,
- serializer: RequestSerializer,
- deserializer: ResponseDeserializer
+/**
+ * @internal
+ *
+ * Note: 2nd type parameter is deprecated and unused.
+ */
+export function getSerdePlugin(
+ config: V1OrV2Endpoint & SerdeFunctions,
+ serializer: RequestSerializer,
+ deserializer: ResponseDeserializer
): Pluggable {
return {
applyToStack: (commandStack: MiddlewareStack) => {
- commandStack.add(deserializerMiddleware(config as SerDeContext, deserializer), deserializerMiddlewareOption);
+ commandStack.add(deserializerMiddleware(config, deserializer), deserializerMiddlewareOption);
commandStack.add(serializerMiddleware(config, serializer), serializerMiddlewareOption);
},
};
diff --git a/packages/middleware-serde/src/serializerMiddleware.spec.ts b/packages/middleware-serde/src/serializerMiddleware.spec.ts
index c17f26d1314..3b5c0ec3f52 100644
--- a/packages/middleware-serde/src/serializerMiddleware.spec.ts
+++ b/packages/middleware-serde/src/serializerMiddleware.spec.ts
@@ -1,4 +1,4 @@
-import { EndpointBearer } from "@smithy/types";
+import { EndpointBearer, SerdeFunctions } from "@smithy/types";
import { serializerMiddleware } from "./serializerMiddleware";
@@ -13,7 +13,7 @@ describe("serializerMiddleware", () => {
hostname: "hostname",
path: "path",
}),
- };
+ } as EndpointBearer & SerdeFunctions;
const mockRequest = {
method: "GET",
diff --git a/packages/middleware-serde/src/serializerMiddleware.ts b/packages/middleware-serde/src/serializerMiddleware.ts
index 95253dc994f..777bc4f1aa8 100644
--- a/packages/middleware-serde/src/serializerMiddleware.ts
+++ b/packages/middleware-serde/src/serializerMiddleware.ts
@@ -2,6 +2,7 @@ import {
EndpointBearer,
HandlerExecutionContext,
RequestSerializer,
+ SerdeFunctions,
SerializeHandler,
SerializeHandlerArguments,
SerializeHandlerOutput,
@@ -10,9 +11,14 @@ import {
import type { V1OrV2Endpoint } from "./serdePlugin";
-export const serializerMiddleware = (
- options: V1OrV2Endpoint,
- serializer: RequestSerializer
+/**
+ * @internal
+ *
+ * Note: 3rd type parameter is deprecated and unused.
+ */
+export const serializerMiddleware = (
+ options: V1OrV2Endpoint & SerdeFunctions,
+ serializer: RequestSerializer
): SerializeMiddleware => (
next: SerializeHandler,
context: HandlerExecutionContext
@@ -28,7 +34,7 @@ export const serializerMiddleware = ;
+ disableHostPrefix: boolean;
+}
+
+/**
+ * @public
+ *
+ * Serde functions from the client config.
+ */
+export interface SerdeFunctions {
base64Encoder: Encoder;
base64Decoder: Decoder;
utf8Encoder: Encoder;
utf8Decoder: Decoder;
streamCollector: StreamCollector;
- requestHandler: RequestHandler;
- disableHostPrefix: boolean;
}
/**
diff --git a/packages/types/src/util.ts b/packages/types/src/util.ts
index 6b4433ef6be..bebb7775669 100644
--- a/packages/types/src/util.ts
+++ b/packages/types/src/util.ts
@@ -12,15 +12,23 @@ export type Exact = [Type1] extends [Type2] ? ([Type2] extends [Ty
/**
* @public
*
- * A function that, given a TypedArray of bytes, can produce a string
- * representation thereof.
+ * A function that, given a Uint8Array of bytes, can produce a string
+ * representation thereof. The function may optionally attempt to
+ * convert other input types to Uint8Array before encoding.
*
* @example An encoder function that converts bytes to hexadecimal
- * representation would return `'deadbeef'` when given
- * `new Uint8Array([0xde, 0xad, 0xbe, 0xef])`.
+ * representation would return `'hello'` when given
+ * `new Uint8Array([104, 101, 108, 108, 111])`.
*/
export interface Encoder {
- (input: Uint8Array): string;
+ /**
+ * Caution: the `any` type on the input is for backwards compatibility.
+ * Runtime support is limited to Uint8Array and string by default.
+ *
+ * You may choose to support more encoder input types if overriding the default
+ * implementations.
+ */
+ (input: Uint8Array | string | any): string;
}
/**
@@ -30,8 +38,8 @@ export interface Encoder {
* string.
*
* @example A decoder function that converts bytes to hexadecimal
- * representation would return `new Uint8Array([0xde, 0xad, 0xbe, 0xef])` when
- * given the string `'deadbeef'`.
+ * representation would return `new Uint8Array([104, 101, 108, 108, 111])` when
+ * given the string `'hello'`.
*/
export interface Decoder {
(input: string): Uint8Array;
diff --git a/packages/util-base64/package.json b/packages/util-base64/package.json
index c85a95f8f73..7bcdd48cb10 100644
--- a/packages/util-base64/package.json
+++ b/packages/util-base64/package.json
@@ -23,6 +23,7 @@
"license": "Apache-2.0",
"dependencies": {
"@smithy/util-buffer-from": "workspace:^",
+ "@smithy/util-utf8": "workspace:^",
"tslib": "^2.5.0"
},
"devDependencies": {
diff --git a/packages/util-base64/src/toBase64.browser.spec.ts b/packages/util-base64/src/toBase64.browser.spec.ts
index 914e47c4615..81261f095ef 100644
--- a/packages/util-base64/src/toBase64.browser.spec.ts
+++ b/packages/util-base64/src/toBase64.browser.spec.ts
@@ -1,6 +1,8 @@
/**
* @jest-environment jsdom
*/
+import type { Encoder } from "@smithy/types";
+
import testCases from "./__mocks__/testCases.json";
import { toBase64 } from "./toBase64.browser";
@@ -8,4 +10,13 @@ describe(toBase64.name, () => {
it.each(testCases as Array<[string, string, number[]]>)("%s", (desc, encoded, decoded) => {
expect(toBase64(new Uint8Array(decoded))).toEqual(encoded);
});
+
+ it("also converts strings", () => {
+ expect(toBase64("hello")).toEqual("aGVsbG8=");
+ });
+
+ it("throws on non-string non-Uint8Array", () => {
+ expect(() => (toBase64 as Encoder)(new Date())).toThrow();
+ expect(() => (toBase64 as Encoder)({})).toThrow();
+ });
});
diff --git a/packages/util-base64/src/toBase64.browser.ts b/packages/util-base64/src/toBase64.browser.ts
index 41a2695b825..bdfad0f9b15 100644
--- a/packages/util-base64/src/toBase64.browser.ts
+++ b/packages/util-base64/src/toBase64.browser.ts
@@ -1,12 +1,25 @@
+import { fromUtf8 } from "@smithy/util-utf8";
+
import { alphabetByValue, bitsPerByte, bitsPerLetter, maxLetterValue } from "./constants.browser";
+
/**
- * Converts a Uint8Array of binary data to a base-64 encoded string.
+ * Converts a Uint8Array of binary data or a utf-8 string to a base-64 encoded string.
*
- * @param input The binary data to encode
+ * @param _input - the binary data or string to encode.
+ * @returns base64 string.
*
* @see https://tools.ietf.org/html/rfc4648#section-4
*/
-export function toBase64(input: Uint8Array): string {
+export function toBase64(_input: Uint8Array | string): string {
+ let input: Uint8Array;
+ if (typeof _input === "string") {
+ input = fromUtf8(_input);
+ } else {
+ input = _input as Uint8Array;
+ }
+ if (typeof input !== "object" || typeof input.byteOffset !== "number" || typeof input.byteLength !== "number") {
+ throw new Error("@smithy/util-base64: toBase64 encoder function only accepts string | Uint8Array.");
+ }
let str = "";
for (let i = 0; i < input.length; i += 3) {
let bits = 0;
diff --git a/packages/util-base64/src/toBase64.spec.ts b/packages/util-base64/src/toBase64.spec.ts
index 77b144f1b18..ae60b4ffdb5 100644
--- a/packages/util-base64/src/toBase64.spec.ts
+++ b/packages/util-base64/src/toBase64.spec.ts
@@ -1,3 +1,5 @@
+import type { Encoder } from "@smithy/types";
+
import testCases from "./__mocks__/testCases.json";
import { toBase64 } from "./toBase64";
@@ -9,4 +11,13 @@ describe(toBase64.name, () => {
it("should throw when given a number", () => {
expect(() => toBase64(0xdeadbeefface as any)).toThrow();
});
+
+ it("also converts strings", () => {
+ expect(toBase64("hello")).toEqual("aGVsbG8=");
+ });
+
+ it("throws on non-string non-Uint8Array", () => {
+ expect(() => (toBase64 as Encoder)(new Date())).toThrow();
+ expect(() => (toBase64 as Encoder)({})).toThrow();
+ });
});
diff --git a/packages/util-base64/src/toBase64.ts b/packages/util-base64/src/toBase64.ts
index d6c32e0ed74..69c0b70a2d2 100644
--- a/packages/util-base64/src/toBase64.ts
+++ b/packages/util-base64/src/toBase64.ts
@@ -1,10 +1,22 @@
import { fromArrayBuffer } from "@smithy/util-buffer-from";
+import { fromUtf8 } from "@smithy/util-utf8";
/**
- * Converts a Uint8Array of binary data to a base-64 encoded string using
+ * Converts a Uint8Array of binary data or a utf-8 string to a base-64 encoded string using
* Node.JS's `buffer` module.
*
- * @param input The binary data to encode
+ * @param _input - the binary data or string to encode.
+ * @returns base64 string.
*/
-export const toBase64 = (input: Uint8Array): string =>
- fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("base64");
+export const toBase64 = (_input: Uint8Array | string): string => {
+ let input: Uint8Array;
+ if (typeof _input === "string") {
+ input = fromUtf8(_input);
+ } else {
+ input = _input as Uint8Array;
+ }
+ if (typeof input !== "object" || typeof input.byteOffset !== "number" || typeof input.byteLength !== "number") {
+ throw new Error("@smithy/util-base64: toBase64 encoder function only accepts string | Uint8Array.");
+ }
+ return fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("base64");
+};
diff --git a/packages/util-utf8/src/toUtf8.browser.spec.ts b/packages/util-utf8/src/toUtf8.browser.spec.ts
index f51d5bcd016..c5d82b0f5b4 100644
--- a/packages/util-utf8/src/toUtf8.browser.spec.ts
+++ b/packages/util-utf8/src/toUtf8.browser.spec.ts
@@ -1,6 +1,8 @@
/**
* @jest-environment jsdom
*/
+import type { Encoder } from "@smithy/types";
+
import { toUtf8 } from "./toUtf8.browser";
declare const global: any;
@@ -13,4 +15,13 @@ describe("toUtf8", () => {
expect(toUtf8(new Uint8Array(0))).toBe(expected);
});
+
+ it("passes through strings", () => {
+ expect(toUtf8("hello")).toEqual("hello");
+ });
+
+ it("throws on non-string non-Uint8Array", () => {
+ expect(() => (toUtf8 as Encoder)(new Date())).toThrow();
+ expect(() => (toUtf8 as Encoder)({})).toThrow();
+ });
});
diff --git a/packages/util-utf8/src/toUtf8.browser.ts b/packages/util-utf8/src/toUtf8.browser.ts
index 872cd66bf87..e1288102425 100644
--- a/packages/util-utf8/src/toUtf8.browser.ts
+++ b/packages/util-utf8/src/toUtf8.browser.ts
@@ -1 +1,15 @@
-export const toUtf8 = (input: Uint8Array): string => new TextDecoder("utf-8").decode(input);
+/**
+ *
+ * This does not convert non-utf8 strings to utf8, it only passes through strings if
+ * a string is received instead of a Uint8Array.
+ *
+ */
+export const toUtf8 = (input: Uint8Array | string): string => {
+ if (typeof input === "string") {
+ return input;
+ }
+ if (typeof input !== "object" || typeof input.byteOffset !== "number" || typeof input.byteLength !== "number") {
+ throw new Error("@smithy/util-utf8: toUtf8 encoder function only accepts string | Uint8Array.");
+ }
+ return new TextDecoder("utf-8").decode(input);
+};
diff --git a/packages/util-utf8/src/toUtf8.spec.ts b/packages/util-utf8/src/toUtf8.spec.ts
index 153ea1a1d0a..32a3b1610c0 100644
--- a/packages/util-utf8/src/toUtf8.spec.ts
+++ b/packages/util-utf8/src/toUtf8.spec.ts
@@ -1,3 +1,5 @@
+import type { Encoder } from "@smithy/types";
+
import { toUtf8 } from "./toUtf8";
const utf8StringsToByteArrays: Record = {
@@ -144,4 +146,13 @@ describe("toUtf8", () => {
it("should throw when given a number", () => {
expect(() => toUtf8(255 as any)).toThrow();
});
+
+ it("passes through strings", () => {
+ expect(toUtf8("hello")).toEqual("hello");
+ });
+
+ it("throws on non-string non-Uint8Array", () => {
+ expect(() => (toUtf8 as Encoder)(new Date())).toThrow();
+ expect(() => (toUtf8 as Encoder)({})).toThrow();
+ });
});
diff --git a/packages/util-utf8/src/toUtf8.ts b/packages/util-utf8/src/toUtf8.ts
index a192c8409fd..27dd3acb167 100644
--- a/packages/util-utf8/src/toUtf8.ts
+++ b/packages/util-utf8/src/toUtf8.ts
@@ -1,4 +1,17 @@
import { fromArrayBuffer } from "@smithy/util-buffer-from";
-export const toUtf8 = (input: Uint8Array): string =>
- fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("utf8");
+/**
+ *
+ * This does not convert non-utf8 strings to utf8, it only passes through strings if
+ * a string is received instead of a Uint8Array.
+ *
+ */
+export const toUtf8 = (input: Uint8Array | string): string => {
+ if (typeof input === "string") {
+ return input;
+ }
+ if (typeof input !== "object" || typeof input.byteOffset !== "number" || typeof input.byteLength !== "number") {
+ throw new Error("@smithy/util-utf8: toUtf8 encoder function only accepts string | Uint8Array.");
+ }
+ return fromArrayBuffer(input.buffer, input.byteOffset, input.byteLength).toString("utf8");
+};
diff --git a/yarn.lock b/yarn.lock
index 30ab53f4643..9ed7ab45403 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2554,6 +2554,7 @@ __metadata:
resolution: "@smithy/util-base64@workspace:packages/util-base64"
dependencies:
"@smithy/util-buffer-from": "workspace:^"
+ "@smithy/util-utf8": "workspace:^"
"@tsconfig/recommended": 1.0.1
"@types/node": ^14.14.31
concurrently: 7.0.0