Skip to content

Commit

Permalink
[core-xml] fix stringifyXML missing XML namespace issue on Firefox (#…
Browse files Browse the repository at this point in the history
…31021)

Chrome-based browsers are more error-tolerant when the XML
namespace ("xmlns:...") was set via attribute, but that doesn't work on
Firefox,
which requires using of `createElementNS()` to create element with XML
namespace.

This PR adds special handling of the "xmlns" attribute.

-------

### Packages impacted by this PR
@azure/core-xml

### Issues associated with this PR
#11655
#30979
  • Loading branch information
jeremymeng authored Sep 11, 2024
1 parent df493c5 commit ee6f42e
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 118 deletions.
2 changes: 1 addition & 1 deletion sdk/core/core-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"build:test": "echo skipped. actual commands inlined in browser test scripts",
"build": "npm run clean && tshy && dev-tool run extract-api",
"check-format": "dev-tool run vendored prettier --list-different --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.{ts,cts,mts}\" \"test/**/*.{ts,cts,mts}\" \"*.{js,cjs,mjs,json}\"",
"clean": "rimraf --glob dist temp types *.tgz *.log",
"clean": "rimraf --glob dist* temp types *.tgz *.log",
"execute:samples": "echo skipped",
"extract-api": "tshy && dev-tool run extract-api",
"format": "dev-tool run vendored prettier --write --config ../../../.prettierrc.json --ignore-path ../../../.prettierignore \"src/**/*.{ts,cts,mts}\" \"test/**/*.{ts,cts,mts}\" \"*.{js,cjs,mjs,json}\"",
Expand Down
125 changes: 125 additions & 0 deletions sdk/core/core-client/test/browser/serializationPolicyXML.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { describe, it, assert } from "vitest";
import { MapperTypeNames, createSerializer } from "../../src/index.js";
import { serializeRequestBody } from "../../src/serializationPolicy.js";
import { createPipelineRequest } from "@azure/core-rest-pipeline";
import { stringifyXML } from "@azure/core-xml";

describe("serializationPolicy", function () {
describe("serializeRequestBody()", () => {
it("should serialize an XML Composite request body with namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: { foo: "Foo", bar: "Bar" },
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
type: {
name: MapperTypeNames.Composite,
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><Foo xmlns="https://microsoft.com/foo">Foo</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar</Bar></bodyArg>`,
);
});

it("should serialize an XML Array of composite elements, namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: [
{ foo: "Foo1", bar: "Bar1" },
{ foo: "Foo2", bar: "Bar2" },
{ foo: "Foo3", bar: "Bar3" },
],
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
xmlElementName: "testItem",
type: {
name: MapperTypeNames.Sequence,
element: {
xmlNamespace: "https://microsoft.com/element",
type: {
name: "Composite",
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo1</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar1</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo2</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar2</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo3</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar3</Bar></testItem></bodyArg>`,
);
});
});
});
125 changes: 125 additions & 0 deletions sdk/core/core-client/test/node/serializationPolicyXML.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

import { describe, it, assert } from "vitest";
import { MapperTypeNames, createSerializer } from "../../src/index.js";
import { serializeRequestBody } from "../../src/serializationPolicy.js";
import { createPipelineRequest } from "@azure/core-rest-pipeline";
import { stringifyXML } from "@azure/core-xml";

describe("serializationPolicy", function () {
describe("serializeRequestBody()", () => {
it("should serialize an XML Composite request body with namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: { foo: "Foo", bar: "Bar" },
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
type: {
name: MapperTypeNames.Composite,
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><Foo xmlns="https://microsoft.com/foo">Foo</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar</Bar></bodyArg>`,
);
});

it("should serialize an XML Array of composite elements, namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: [
{ foo: "Foo1", bar: "Bar1" },
{ foo: "Foo2", bar: "Bar2" },
{ foo: "Foo3", bar: "Bar3" },
],
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
xmlElementName: "testItem",
type: {
name: MapperTypeNames.Sequence,
element: {
xmlNamespace: "https://microsoft.com/element",
type: {
name: "Composite",
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo1</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar1</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo2</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar2</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo3</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar3</Bar></testItem></bodyArg>`,
);
});
});
});
113 changes: 0 additions & 113 deletions sdk/core/core-client/test/serializationPolicy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,119 +565,6 @@ describe("serializationPolicy", function () {
assert.deepEqual(httpRequest.body, JSON.stringify(["Foo", "Bar"]));
});

it("should serialize an XML Array of composite elements, namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: [
{ foo: "Foo1", bar: "Bar1" },
{ foo: "Foo2", bar: "Bar2" },
{ foo: "Foo3", bar: "Bar3" },
],
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
xmlElementName: "testItem",
type: {
name: MapperTypeNames.Sequence,
element: {
xmlNamespace: "https://microsoft.com/element",
type: {
name: "Composite",
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo1</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar1</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo2</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar2</Bar></testItem><testItem xmlns="https://microsoft.com/element"><Foo xmlns="https://microsoft.com/foo">Foo3</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar3</Bar></testItem></bodyArg>`,
);
});

it("should serialize an XML Composite request body with namespace and prefix", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
{
bodyArg: { foo: "Foo", bar: "Bar" },
},
{
httpMethod: "POST",
requestBody: {
parameterPath: "bodyArg",
mapper: {
required: true,
serializedName: "bodyArg",
xmlNamespace: "https://microsoft.com",
type: {
name: MapperTypeNames.Composite,
modelProperties: {
foo: {
serializedName: "foo",
xmlNamespace: "https://microsoft.com/foo",
xmlName: "Foo",
type: {
name: "String",
},
},
bar: {
xmlNamespacePrefix: "bar",
xmlNamespace: "https://microsoft.com/bar",
xmlName: "Bar",
serializedName: "bar",
type: {
name: "String",
},
},
},
},
},
},
responses: { 200: {} },
serializer: createSerializer(undefined, true /** isXML */),
isXML: true,
},
stringifyXML,
);
assert.strictEqual(
httpRequest.body,
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?><bodyArg xmlns="https://microsoft.com"><Foo xmlns="https://microsoft.com/foo">Foo</Foo><Bar xmlns:bar="https://microsoft.com/bar">Bar</Bar></bodyArg>`,
);
});

it("should serialize an XML Dictionary request body", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
Expand Down
4 changes: 3 additions & 1 deletion sdk/core/core-client/test/serviceClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
HttpClient,
PipelinePolicy,
PipelineRequest,
PipelineResponse,
RestError,
SendRequest,
createEmptyPipeline,
Expand Down Expand Up @@ -1489,7 +1490,8 @@ describe("ServiceClient", function () {

it("should insert policies in the correct pipeline position", async function () {
const pipeline = createEmptyPipeline();
const sendRequest = (request: PipelineRequest, next: SendRequest) => next(request);
const sendRequest = (request: PipelineRequest, next: SendRequest): Promise<PipelineResponse> =>
next(request);
const retryPolicy: PipelinePolicy = {
name: "retry",
sendRequest,
Expand Down
Loading

0 comments on commit ee6f42e

Please sign in to comment.