diff --git a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts
index d4784c85b660..3726786edd16 100644
--- a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts
+++ b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.spec.ts
@@ -1,7 +1,6 @@
import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
-import { StaticOperationSchema } from "@smithy/types";
+import { StaticOperationSchema, StaticStructureSchema } from "@smithy/types";
import { toUtf8 } from "@smithy/util-utf8";
-import { Readable } from "node:stream";
import { describe, expect, test as it } from "vitest";
import { context, deleteObjects } from "../test-schema.spec";
@@ -13,6 +12,11 @@ describe(AwsRestXmlProtocol.name, () => {
schema: deleteObjects,
};
+ const protocol = new AwsRestXmlProtocol({
+ xmlNamespace: "http://s3.amazonaws.com/doc/2006-03-01/",
+ defaultNamespace: "com.amazonaws.s3",
+ });
+
describe("serialization", () => {
const testCases = [
{
@@ -59,11 +63,6 @@ describe(AwsRestXmlProtocol.name, () => {
for (const testCase of testCases) {
it(`should serialize HTTP Requests: ${testCase.name}`, async () => {
- const protocol = new AwsRestXmlProtocol({
- xmlNamespace: "http://s3.amazonaws.com/doc/2006-03-01/",
- defaultNamespace: "com.amazonaws.s3",
- });
-
const [, namespace, name, traits, input, output] = command.schema as StaticOperationSchema;
const httpRequest = await protocol.serializeRequest(
@@ -95,74 +94,135 @@ describe(AwsRestXmlProtocol.name, () => {
);
});
}
- });
- it("deserializes http responses", async () => {
- const httpResponse = new HttpResponse({
- statusCode: 200,
- headers: {},
- });
+ it("should prepend an xml declaration only if the content type is application/xml and the input schema does not have a payload binding", async () => {
+ const document = [
+ 3,
+ "ns",
+ "Struct",
+ 0,
+ ["a", "b", "c", "h"],
+ [0, 0, 0, [0, { httpHeader: "content-type" }]],
+ ] satisfies StaticStructureSchema;
+ const payload = [
+ 3,
+ "ns",
+ "PayloadStruct",
+ 0,
+ ["a", "b", "c", "h"],
+ [0, 0, [0, { httpPayload: 1 }], [0, { httpHeader: "content-type" }]],
+ ] satisfies StaticStructureSchema;
+
+ const createOperation = (input: StaticStructureSchema) => ({
+ namespace: "ns",
+ name: "operation",
+ traits: {},
+ input,
+ output: "unit" as const,
+ });
- const protocol = new AwsRestXmlProtocol({
- defaultNamespace: "",
- xmlNamespace: "ns",
- });
+ const httpRequest1 = await protocol.serializeRequest(
+ createOperation(document),
+ {
+ c: "",
+ h: "application/xml",
+ },
+ context
+ );
+
+ // this is not a payload binding, so although the
+ // content and header appear to be XML,
+ // the data is encoded within an XML container structure and the xml declaration
+ // is prepended by the SDK.
+ expect(httpRequest1.body).toBe(
+ `<XML></XML>`
+ );
+
+ const httpRequest2 = await protocol.serializeRequest(
+ createOperation(payload),
+ {
+ c: "",
+ h: "application/xml",
+ },
+ context
+ );
- const output = await protocol.deserializeResponse(
- {
- namespace: deleteObjects[1],
- name: deleteObjects[2],
- traits: deleteObjects[3],
- input: deleteObjects[4],
- output: deleteObjects[5],
- },
- context,
- httpResponse
- );
-
- expect(output).toEqual({
- $metadata: {
- httpStatusCode: 200,
- requestId: undefined,
- extendedRequestId: undefined,
- cfId: undefined,
- },
- });
- });
+ // even though this could be interpreted as XML input by the content and the header value,
+ // because this operation is a payload binding of a string,
+ // we simply send what the caller has provided rather than prepending the XML declaration.
+ expect(httpRequest2.body).toBe(``);
- it("decorates service exceptions with unmodeled fields", async () => {
- const httpResponse = new HttpResponse({
- statusCode: 400,
- headers: {},
- body: Buffer.from(`Oh no`),
- });
+ const httpRequest3 = await protocol.serializeRequest(
+ createOperation(payload),
+ {
+ c: "",
+ h: "text/xml",
+ },
+ context
+ );
- const protocol = new AwsRestXmlProtocol({
- defaultNamespace: "",
- xmlNamespace: "ns",
+ expect(httpRequest3.body).toBe(``);
});
+ });
- const output = await protocol
- .deserializeResponse(
+ describe("deserialization", () => {
+ it("deserializes http responses", async () => {
+ const httpResponse = new HttpResponse({
+ statusCode: 200,
+ headers: {},
+ });
+
+ const output = await protocol.deserializeResponse(
{
- namespace: "ns",
- name: "Empty",
- traits: 0,
- input: "unit" as const,
- output: [3, "ns", "EmptyOutput", 0, [], []],
+ namespace: deleteObjects[1],
+ name: deleteObjects[2],
+ traits: deleteObjects[3],
+ input: deleteObjects[4],
+ output: deleteObjects[5],
},
context,
httpResponse
- )
- .catch((e) => {
- return e;
+ );
+
+ expect(output).toEqual({
+ $metadata: {
+ httpStatusCode: 200,
+ requestId: undefined,
+ extendedRequestId: undefined,
+ cfId: undefined,
+ },
+ });
+ });
+
+ it("decorates service exceptions with unmodeled fields", async () => {
+ const httpResponse = new HttpResponse({
+ statusCode: 400,
+ headers: {},
+ body: Buffer.from(`Oh no`),
});
- expect(output).toMatchObject({
- UnmodeledField: "Oh no",
- $metadata: {
- httpStatusCode: 400,
- },
+ const output = await protocol
+ .deserializeResponse(
+ {
+ namespace: "ns",
+ name: "Empty",
+ traits: 0,
+ input: "unit" as const,
+ output: [3, "ns", "EmptyOutput", 0, [], []],
+ },
+ context,
+ httpResponse
+ )
+ .catch((e) => {
+ return e;
+ });
+
+ expect(output).toMatchObject({
+ UnmodeledField: "Oh no",
+ $metadata: {
+ httpStatusCode: 400,
+ },
+ });
});
});
});
diff --git a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts
index 458fcb2f007d..d5e85a97d9e9 100644
--- a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts
+++ b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts
@@ -70,12 +70,17 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol {
}
}
- if (request.headers["content-type"] === this.getDefaultContentType()) {
- if (typeof request.body === "string") {
- if (!request.body.startsWith("' + request.body;
- }
- }
+ if (
+ typeof request.body === "string" &&
+ request.headers["content-type"] === this.getDefaultContentType() &&
+ !request.body.startsWith("' + request.body;
}
// content-length header is set by the contentLengthMiddleware.
@@ -145,4 +150,15 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol {
protected getDefaultContentType(): string {
return "application/xml";
}
+
+ private hasUnstructuredPayloadBinding(ns: NormalizedSchema): boolean {
+ for (const [, member] of ns.structIterator()) {
+ if (member.getMergedTraits().httpPayload) {
+ // all struct members can be http payloads, but the serialization of
+ // simple types are probably not in XML format.
+ return !(member.isStructSchema() || member.isMapSchema() || member.isListSchema());
+ }
+ }
+ return false;
+ }
}