From 81dbe790947d5a6a05356fa862b35568c3e0a5c8 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Fri, 11 Sep 2020 02:32:05 +0000 Subject: [PATCH 1/9] Prototype xmlns --- sdk/core/core-http/review/core-http.api.md | 4 + sdk/core/core-http/src/serializer.ts | 20 +- sdk/core/core-http/src/serviceClient.ts | 10 +- sdk/core/core-http/test/serviceClientTests.ts | 232 ++++++++++++++++++ 4 files changed, 261 insertions(+), 5 deletions(-) diff --git a/sdk/core/core-http/review/core-http.api.md b/sdk/core/core-http/review/core-http.api.md index ec6cd89c7816..8c24346958f7 100644 --- a/sdk/core/core-http/review/core-http.api.md +++ b/sdk/core/core-http/review/core-http.api.md @@ -78,6 +78,10 @@ export interface BaseMapper { xmlIsWrapped?: boolean; // (undocumented) xmlName?: string; + // (undocumented) + xmlNamespace?: string; + // (undocumented) + xmlNamespacePrefix?: string; } // @public (undocumented) diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index 1b32f489f496..3e771966ce56 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -352,6 +352,7 @@ function serializeBasicTypes(typeName: string, objectName: string, value: any): } } } + return value; } @@ -589,6 +590,12 @@ function serializeCompositeType( } if (parentObject != undefined) { + if (mapper.xmlNamespace) { + const xmlnsKey = mapper.xmlNamespacePrefix + ? `xmlns:${mapper.xmlNamespacePrefix}` + : "xmlns"; + parentObject.$ = { [xmlnsKey]: mapper.xmlNamespace }; + } const propertyObjectName = propertyMapper.serializedName !== "" ? objectName + "." + propertyMapper.serializedName @@ -610,6 +617,13 @@ function serializeCompositeType( propertyObjectName ); if (serializedValue !== undefined && propName != undefined) { + const xmlnsKey = propertyMapper.xmlNamespacePrefix + ? `xmlns:${propertyMapper.xmlNamespacePrefix}` + : "xmlns"; + const xmlNamespace = { [xmlnsKey]: propertyMapper.xmlNamespace }; + const value = propertyMapper.xmlNamespace + ? { _: serializedValue, $: xmlNamespace } + : serializedValue; if (propertyMapper.xmlIsAttribute) { // $ is the key attributes are kept under in xml2js. // This keeps things simple while preventing name collision @@ -617,9 +631,9 @@ function serializeCompositeType( parentObject.$ = parentObject.$ || {}; parentObject.$[propName] = serializedValue; } else if (propertyMapper.xmlIsWrapped) { - parentObject[propName] = { [propertyMapper.xmlElementName!]: serializedValue }; + parentObject[propName] = { [propertyMapper.xmlElementName!]: value }; } else { - parentObject[propName] = serializedValue; + parentObject[propName] = value; } } } @@ -961,6 +975,8 @@ export interface EnumMapperType { export interface BaseMapper { xmlName?: string; + xmlNamespace?: string; + xmlNamespacePrefix?: string; xmlIsAttribute?: boolean; xmlElementName?: string; xmlIsWrapped?: boolean; diff --git a/sdk/core/core-http/src/serviceClient.ts b/sdk/core/core-http/src/serviceClient.ts index 1bed817c86d4..192179e7343e 100644 --- a/sdk/core/core-http/src/serviceClient.ts +++ b/sdk/core/core-http/src/serviceClient.ts @@ -283,6 +283,7 @@ export class ServiceClient { ); } } + console.log("request info = ", httpRequest.url, httpRequest.operationSpec?.requestBody); return httpPipeline.sendRequest(httpRequest); } @@ -489,6 +490,7 @@ export class ServiceClient { let rawResponse: HttpOperationResponse; let sendRequestError; try { + console.log(httpRequest); rawResponse = await this.sendRequest(httpRequest); } catch (error) { sendRequestError = error; @@ -538,7 +540,7 @@ export function serializeRequestBody( ); const bodyMapper = operationSpec.requestBody.mapper; - const { required, xmlName, xmlElementName, serializedName } = bodyMapper; + const { required, xmlName, xmlElementName, serializedName, xmlNamespace, xmlNamespacePrefix} = bodyMapper; const typeName = bodyMapper.type.name; try { @@ -555,16 +557,18 @@ export function serializeRequestBody( const isStream = typeName === MapperType.Stream; if (operationSpec.isXML) { + const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; + const value = xmlNamespace && !["Composite", "Sequence"].includes(typeName) ? {_: httpRequest.body, $: {[xmlnsKey]: xmlNamespace}} : httpRequest.body; if (typeName === MapperType.Sequence) { httpRequest.body = stringifyXML( utils.prepareXMLRootList( - httpRequest.body, + value, xmlElementName || xmlName || serializedName! ), { rootName: xmlName || serializedName } ); } else if (!isStream) { - httpRequest.body = stringifyXML(httpRequest.body, { + httpRequest.body = stringifyXML(value, { rootName: xmlName || serializedName }); } diff --git a/sdk/core/core-http/test/serviceClientTests.ts b/sdk/core/core-http/test/serviceClientTests.ts index 9464b05da824..f0f3842d14cd 100644 --- a/sdk/core/core-http/test/serviceClientTests.ts +++ b/sdk/core/core-http/test/serviceClientTests.ts @@ -567,6 +567,38 @@ describe("ServiceClient", function() { ); }); + it("should serialize an XML String request body with namespace", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + xmlNamespace: "https://microsoft.com", + type: { + name: MapperType.String + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true /** isXML*/), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `body value` + ); + }); + it("should serialize an XML ByteArray request body", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -598,6 +630,206 @@ describe("ServiceClient", function() { ); }); + it("should serialize an XML ByteArray request body with namespace", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + xmlNamespace: "https://microsoft.com", + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `SmF2YXNjcmlwdA==` + ); + }); + + it("should serialize an XML ByteArray request body with namespace and prefix", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + xmlNamespace: "https://microsoft.com", + xmlNamespacePrefix: "sample", + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `SmF2YXNjcmlwdA==` + ); + }); + + it("should serialize an XML Composite request body", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: {foo: "Foo", bar: "Bar"} + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.Composite, + modelProperties: { + foo: { + serializedName: "foo", + xmlName: "Foo", + type: { + name: "String" + } + }, + bar: { + xmlName: "Bar", + serializedName: "bar", + type: { + name: "String" + } + } + } + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `FooBar` + ); + }); + + it.skip("should serialize an XML Array request body with namespace and prefix", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: ["Foo", "Bar"] + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + xmlNamespace: "https://microsoft.com", + xmlElementName: "testItem", + type: { + name: MapperType.Sequence, + element: { + type: {name: "String"}, + xmlNamespace: "https://microsoft.com" + } + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `FooBar` + ); + }) + + it("should serialize an XML Composite request body with namespace and prefix", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: {foo: "Foo", bar: "Bar"} + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + xmlNamespace: "https://microsoft.com", + type: { + name: MapperType.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: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `FooBar` + ); + }) + it("should serialize an XML Stream request body", () => { const httpRequest = new WebResource(); serializeRequestBody( From a67740fb750dda14ef8e0e28561acfbaa05d406b Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 15 Sep 2020 19:44:32 +0000 Subject: [PATCH 2/9] Update prototype --- sdk/core/core-http/src/serializer.ts | 27 +- sdk/core/core-http/test/serviceClientTests.ts | 43 +-- sdk/core/core-http/test/testMappers.ts | 266 ++++++++++++++++++ 3 files changed, 296 insertions(+), 40 deletions(-) create mode 100644 sdk/core/core-http/test/testMappers.ts diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index 3e771966ce56..10ad596d5dfd 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -594,7 +594,7 @@ function serializeCompositeType( const xmlnsKey = mapper.xmlNamespacePrefix ? `xmlns:${mapper.xmlNamespacePrefix}` : "xmlns"; - parentObject.$ = { [xmlnsKey]: mapper.xmlNamespace }; + parentObject.$ = { ...parentObject.$, [xmlnsKey]: mapper.xmlNamespace }; } const propertyObjectName = propertyMapper.serializedName !== "" @@ -616,14 +616,9 @@ function serializeCompositeType( toSerialize, propertyObjectName ); + if (serializedValue !== undefined && propName != undefined) { - const xmlnsKey = propertyMapper.xmlNamespacePrefix - ? `xmlns:${propertyMapper.xmlNamespacePrefix}` - : "xmlns"; - const xmlNamespace = { [xmlnsKey]: propertyMapper.xmlNamespace }; - const value = propertyMapper.xmlNamespace - ? { _: serializedValue, $: xmlNamespace } - : serializedValue; + const value = getXmlObjectValue(propertyMapper, serializedValue); if (propertyMapper.xmlIsAttribute) { // $ is the key attributes are kept under in xml2js. // This keeps things simple while preventing name collision @@ -659,6 +654,22 @@ function serializeCompositeType( return object; } +function getXmlObjectValue(propertyMapper: Mapper, serializedValue: any) { + if (!propertyMapper.xmlNamespace) { + return serializedValue; + } + + const xmlnsKey = propertyMapper.xmlNamespacePrefix + ? `xmlns:${propertyMapper.xmlNamespacePrefix}` + : "xmlns"; + const xmlNamespace = { [xmlnsKey]: propertyMapper.xmlNamespace }; + + if (["Composite"].includes(propertyMapper.type.name)) { + return { $: xmlNamespace, ...serializedValue }; + } + return { _: serializedValue, $: xmlNamespace }; +} + function isSpecialXmlProperty(propertyName: string): boolean { return ["$", "_"].includes(propertyName); } diff --git a/sdk/core/core-http/test/serviceClientTests.ts b/sdk/core/core-http/test/serviceClientTests.ts index f0f3842d14cd..73f266e38f38 100644 --- a/sdk/core/core-http/test/serviceClientTests.ts +++ b/sdk/core/core-http/test/serviceClientTests.ts @@ -26,6 +26,7 @@ import { OperationSpec } from "../src/coreHttp"; import { ParameterPath } from "../src/operationParameter"; +import { requestBody1 } from "./testMappers"; describe("ServiceClient", function() { it("should serialize headerCollectionPrefix", async function() { @@ -701,36 +702,14 @@ describe("ServiceClient", function() { new ServiceClient(), httpRequest, { - bodyArg: {foo: "Foo", bar: "Bar"} + requestBody: { + updated: new Date("2020-08-12T23:36:18.308Z"), + content: { type: "application/xml", queueDescription: { maxDeliveryCount: 15 } } + } }, { httpMethod: "POST", - requestBody: { - parameterPath: "bodyArg", - mapper: { - required: true, - serializedName: "bodyArg", - type: { - name: MapperType.Composite, - modelProperties: { - foo: { - serializedName: "foo", - xmlName: "Foo", - type: { - name: "String" - } - }, - bar: { - xmlName: "Bar", - serializedName: "bar", - type: { - name: "String" - } - } - } - } - } - }, + requestBody: requestBody1, responses: { 200: {} }, serializer: new Serializer(undefined, true), isXML: true @@ -738,7 +717,7 @@ describe("ServiceClient", function() { ); assert.strictEqual( httpRequest.body, - `FooBar` + `2020-08-12T23:36:18.308Z15` ); }); @@ -762,7 +741,7 @@ describe("ServiceClient", function() { type: { name: MapperType.Sequence, element: { - type: {name: "String"}, + type: { name: "String" }, xmlNamespace: "https://microsoft.com" } } @@ -777,7 +756,7 @@ describe("ServiceClient", function() { httpRequest.body, `FooBar` ); - }) + }); it("should serialize an XML Composite request body with namespace and prefix", () => { const httpRequest = new WebResource(); @@ -785,7 +764,7 @@ describe("ServiceClient", function() { new ServiceClient(), httpRequest, { - bodyArg: {foo: "Foo", bar: "Bar"} + bodyArg: { foo: "Foo", bar: "Bar" } }, { httpMethod: "POST", @@ -828,7 +807,7 @@ describe("ServiceClient", function() { httpRequest.body, `FooBar` ); - }) + }); it("should serialize an XML Stream request body", () => { const httpRequest = new WebResource(); diff --git a/sdk/core/core-http/test/testMappers.ts b/sdk/core/core-http/test/testMappers.ts new file mode 100644 index 000000000000..06053b8e785c --- /dev/null +++ b/sdk/core/core-http/test/testMappers.ts @@ -0,0 +1,266 @@ +import { CompositeMapper, OperationParameter } from "../src/coreHttp"; + +const QueueDescription: CompositeMapper = { + serializedName: "QueueDescription", + xmlName: "QueueDescription", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Composite", + className: "QueueDescription", + modelProperties: { + lockDuration: { + serializedName: "lockDuration", + xmlName: "LockDuration", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + maxSizeInMegabytes: { + serializedName: "maxSizeInMegabytes", + xmlName: "MaxSizeInMegabytes", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + requiresDuplicateDetection: { + serializedName: "requiresDuplicateDetection", + xmlName: "RequiresDuplicateDetection", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + requiresSession: { + serializedName: "requiresSession", + xmlName: "RequiresSession", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + defaultMessageTimeToLive: { + serializedName: "defaultMessageTimeToLive", + xmlName: "DefaultMessageTimeToLive", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + deadLetteringOnMessageExpiration: { + serializedName: "deadLetteringOnMessageExpiration", + xmlName: "DeadLetteringOnMessageExpiration", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + duplicateDetectionHistoryTimeWindow: { + serializedName: "duplicateDetectionHistoryTimeWindow", + xmlName: "DuplicateDetectionHistoryTimeWindow", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + maxDeliveryCount: { + serializedName: "maxDeliveryCount", + xmlName: "MaxDeliveryCount", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + enableBatchedOperations: { + serializedName: "enableBatchedOperations", + xmlName: "EnableBatchedOperations", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + sizeInBytes: { + serializedName: "sizeInBytes", + xmlName: "SizeInBytes", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + messageCount: { + serializedName: "messageCount", + xmlName: "MessageCount", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + isAnonymousAccessible: { + serializedName: "isAnonymousAccessible", + xmlName: "IsAnonymousAccessible", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + status: { + serializedName: "status", + xmlName: "Status", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + forwardTo: { + serializedName: "forwardTo", + xmlName: "ForwardTo", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + userMetadata: { + serializedName: "userMetadata", + xmlName: "UserMetadata", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + createdAt: { + serializedName: "createdAt", + xmlName: "CreatedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + updatedAt: { + serializedName: "updatedAt", + xmlName: "UpdatedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + accessedAt: { + serializedName: "accessedAt", + xmlName: "AccessedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + supportOrdering: { + serializedName: "supportOrdering", + xmlName: "SupportOrdering", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + autoDeleteOnIdle: { + serializedName: "autoDeleteOnIdle", + xmlName: "AutoDeleteOnIdle", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + enablePartitioning: { + serializedName: "enablePartitioning", + xmlName: "EnablePartitioning", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + entityAvailabilityStatus: { + serializedName: "entityAvailabilityStatus", + xmlName: "EntityAvailabilityStatus", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + enableExpress: { + serializedName: "enableExpress", + xmlName: "EnableExpress", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + forwardDeadLetteredMessagesTo: { + serializedName: "forwardDeadLetteredMessagesTo", + xmlName: "ForwardDeadLetteredMessagesTo", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + } + } + } +}; + +const CreateQueueBodyContent: CompositeMapper = { + serializedName: "CreateQueueBodyContent", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "Composite", + className: "CreateQueueBodyContent", + modelProperties: { + type: { + defaultValue: "application/xml", + serializedName: "type", + xmlName: "type", + xmlIsAttribute: true, + type: { + name: "String" + } + }, + queueDescription: { + serializedName: "queueDescription", + xmlName: "QueueDescription", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + ...QueueDescription.type + } + } + } + } +}; + +const CreateQueueBody: CompositeMapper = { + serializedName: "CreateQueueBody", + xmlName: "entry", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "Composite", + className: "CreateQueueBody", + modelProperties: { + updated: { + serializedName: "updated", + xmlName: "updated", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "DateTime" + } + }, + content: { + serializedName: "content", + xmlName: "content", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + ...CreateQueueBodyContent.type + } + } + } + } +}; + +export const requestBody1: OperationParameter = { + parameterPath: "requestBody", + mapper: CreateQueueBody +}; From d782959fc78a7bd04af1964fc337868b72fd6e47 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 15 Sep 2020 22:54:24 +0000 Subject: [PATCH 3/9] Additional tests --- sdk/core/core-http/src/serializer.ts | 39 ++- sdk/core/core-http/src/serviceClient.ts | 4 +- sdk/core/core-http/src/util/utils.ts | 14 +- sdk/core/core-http/test/serviceClientTests.ts | 246 +++++++++++++++++- 4 files changed, 283 insertions(+), 20 deletions(-) diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index 10ad596d5dfd..b08760a586c4 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -144,11 +144,11 @@ export class Serializer { } else if (mapperType.match(/^Base64Url$/i) !== null) { payload = serializeBase64UrlType(objectName, object); } else if (mapperType.match(/^Sequence$/i) !== null) { - payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName); + payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName, Boolean(this.isXML)); } else if (mapperType.match(/^Dictionary$/i) !== null) { payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName); } else if (mapperType.match(/^Composite$/i) !== null) { - payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName); + payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName, Boolean(this.isXML)); } } return payload; @@ -463,7 +463,8 @@ function serializeSequenceType( serializer: Serializer, mapper: SequenceMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): any[] { if (!Array.isArray(object)) { throw new Error(`${objectName} must be of type Array.`); @@ -477,7 +478,22 @@ function serializeSequenceType( } const tempArray = []; for (let i = 0; i < object.length; i++) { - tempArray[i] = serializer.serialize(elementType, object[i], objectName); + const serializedValue = serializer.serialize(elementType, object[i], objectName); + + if (isXml && elementType.xmlNamespace) { + const xmlnsKey = elementType.xmlNamespacePrefix + ? `xmlns:${elementType.xmlNamespacePrefix}` + : "xmlns"; + if(elementType.type.name === "Composite") { + tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } else { + tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } + } + + tempArray[i] = serializedValue; } return tempArray; } @@ -550,7 +566,8 @@ function serializeCompositeType( serializer: Serializer, mapper: CompositeMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): any { if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) { mapper = getPolymorphicMapper(serializer, mapper, object, "clientName"); @@ -590,7 +607,7 @@ function serializeCompositeType( } if (parentObject != undefined) { - if (mapper.xmlNamespace) { + if (isXml && mapper.xmlNamespace) { const xmlnsKey = mapper.xmlNamespacePrefix ? `xmlns:${mapper.xmlNamespacePrefix}` : "xmlns"; @@ -618,14 +635,14 @@ function serializeCompositeType( ); if (serializedValue !== undefined && propName != undefined) { - const value = getXmlObjectValue(propertyMapper, serializedValue); - if (propertyMapper.xmlIsAttribute) { + const value = getXmlObjectValue(propertyMapper, serializedValue, isXml); + if (isXml && propertyMapper.xmlIsAttribute) { // $ is the key attributes are kept under in xml2js. // This keeps things simple while preventing name collision // with names in user documents. parentObject.$ = parentObject.$ || {}; parentObject.$[propName] = serializedValue; - } else if (propertyMapper.xmlIsWrapped) { + } else if (isXml && propertyMapper.xmlIsWrapped) { parentObject[propName] = { [propertyMapper.xmlElementName!]: value }; } else { parentObject[propName] = value; @@ -654,8 +671,8 @@ function serializeCompositeType( return object; } -function getXmlObjectValue(propertyMapper: Mapper, serializedValue: any) { - if (!propertyMapper.xmlNamespace) { +function getXmlObjectValue(propertyMapper: Mapper, serializedValue: any, isXml: boolean) { + if (!isXml || !propertyMapper.xmlNamespace) { return serializedValue; } diff --git a/sdk/core/core-http/src/serviceClient.ts b/sdk/core/core-http/src/serviceClient.ts index 192179e7343e..59f2be58537f 100644 --- a/sdk/core/core-http/src/serviceClient.ts +++ b/sdk/core/core-http/src/serviceClient.ts @@ -563,7 +563,9 @@ export function serializeRequestBody( httpRequest.body = stringifyXML( utils.prepareXMLRootList( value, - xmlElementName || xmlName || serializedName! + xmlElementName || xmlName || serializedName!, + xmlnsKey, + xmlNamespace ), { rootName: xmlName || serializedName } ); diff --git a/sdk/core/core-http/src/util/utils.ts b/sdk/core/core-http/src/util/utils.ts index 8c116895286d..10cdc1a92363 100644 --- a/sdk/core/core-http/src/util/utils.ts +++ b/sdk/core/core-http/src/util/utils.ts @@ -189,11 +189,21 @@ export function promiseToServiceCallback(promise: Promise { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.String + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(), + } + ); + assert.strictEqual(httpRequest.body, `"body value"`); + }); + + it("should serialize a JSON ByteArray request body", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -510,6 +538,36 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, `"SmF2YXNjcmlwdA=="`); }); + it("should serialize a JSON ByteArray request body with namespace, ignoring xml properties", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, false /** isXML */), + } + ); + assert.strictEqual( + httpRequest.body, + `"SmF2YXNjcmlwdA=="` + ); + }); + it("should serialize a JSON Stream request body", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -537,6 +595,34 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, "body value"); }); + it("should serialize a JSON Stream request body, ignore namespace", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + xmlNamespace: "http://microsoft.com", + serializedName: "bodyArg", + type: { + name: MapperType.Stream + } + } + }, + responses: { 200: {} }, + serializer: new Serializer() + } + ); + assert.strictEqual(httpRequest.body, "body value"); + }); + it("should serialize an XML String request body", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -621,7 +707,7 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: new Serializer(), + serializer: new Serializer(undefined, true/** isXml */), isXML: true } ); @@ -631,6 +717,38 @@ describe("ServiceClient", function() { ); }); + it("should serialize a Json ByteArray request body, ignoring namespace", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + xmlNamespace: "https://google.com", + serializedName: "bodyArg", + type: { + name: MapperType.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, false /** isXML */), + } + ); + assert.strictEqual( + httpRequest.body, + `"SmF2YXNjcmlwdA=="` + ); + }); + + it("should serialize an XML ByteArray request body with namespace", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -711,7 +829,7 @@ describe("ServiceClient", function() { httpMethod: "POST", requestBody: requestBody1, responses: { 200: {} }, - serializer: new Serializer(undefined, true), + serializer: new Serializer(undefined, true /** isXML */), isXML: true } ); @@ -721,7 +839,29 @@ describe("ServiceClient", function() { ); }); - it.skip("should serialize an XML Array request body with namespace and prefix", () => { + it("should serialize a JSON Composite request body, ignoring XML metadata", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + requestBody: { + updated: new Date("2020-08-12T23:36:18.308Z"), + content: { type: "application/xml", queueDescription: { maxDeliveryCount: 15 } } + } + }, + { + httpMethod: "POST", + requestBody: requestBody1, + responses: { 200: {} }, + serializer: new Serializer(), + } + ); + + assert.deepEqual(httpRequest.body, '{"updated":"2020-08-12T23:36:18.308Z","content":{"type":"application/xml","queueDescription":{"maxDeliveryCount":15}}}'); + }); + + it("should serialize an XML Array request body with namespace and prefix", () => { const httpRequest = new WebResource(); serializeRequestBody( new ServiceClient(), @@ -741,8 +881,8 @@ describe("ServiceClient", function() { type: { name: MapperType.Sequence, element: { - type: { name: "String" }, - xmlNamespace: "https://microsoft.com" + xmlNamespace: "https://microsoft.com/element", + type: { name: "String" } } } } @@ -754,7 +894,101 @@ describe("ServiceClient", function() { ); assert.strictEqual( httpRequest.body, - `FooBar` + `FooBar` + ); + }); + + it("should serialize a JSON Array request body, ignoring XML metadata", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: ["Foo", "Bar"] + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + xmlNamespace: "https://microsoft.com", + xmlElementName: "testItem", + type: { + name: MapperType.Sequence, + element: { + xmlNamespace: "https://microsoft.com/element", + type: { name: "String" } + } + } + } + }, + responses: { 200: {} }, + serializer: new Serializer() + } + ); + assert.deepEqual( + httpRequest.body, JSON.stringify(["Foo", "Bar"]) + ); + }); + + + it("should serialize an XML Array of composite elements, namespace and prefix", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + 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: MapperType.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: new Serializer(undefined, true), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `Foo1Bar1Foo2Bar2Foo3Bar3` ); }); From 5b855499b43a3af04ebdfba07e19c4fcf2f6d963 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 15 Sep 2020 23:45:55 +0000 Subject: [PATCH 4/9] Address PR comments --- sdk/core/core-http/src/serviceClient.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/sdk/core/core-http/src/serviceClient.ts b/sdk/core/core-http/src/serviceClient.ts index 59f2be58537f..570ed12a24ee 100644 --- a/sdk/core/core-http/src/serviceClient.ts +++ b/sdk/core/core-http/src/serviceClient.ts @@ -283,7 +283,6 @@ export class ServiceClient { ); } } - console.log("request info = ", httpRequest.url, httpRequest.operationSpec?.requestBody); return httpPipeline.sendRequest(httpRequest); } @@ -490,7 +489,6 @@ export class ServiceClient { let rawResponse: HttpOperationResponse; let sendRequestError; try { - console.log(httpRequest); rawResponse = await this.sendRequest(httpRequest); } catch (error) { sendRequestError = error; @@ -558,7 +556,7 @@ export function serializeRequestBody( if (operationSpec.isXML) { const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; - const value = xmlNamespace && !["Composite", "Sequence"].includes(typeName) ? {_: httpRequest.body, $: {[xmlnsKey]: xmlNamespace}} : httpRequest.body; + const value = getXmlValueWithNamespace(xmlNamespace, xmlnsKey, typeName, httpRequest.body); if (typeName === MapperType.Sequence) { httpRequest.body = stringifyXML( utils.prepareXMLRootList( @@ -616,6 +614,19 @@ export function serializeRequestBody( } } +/** + * Adds an xml namespace to the xml serialized object if needed, otherwise it just returns the value itself + */ +function getXmlValueWithNamespace(xmlNamespace: string | undefined, xmlnsKey: string, typeName: string, serializedValue: any): any { + // Composite and Sequence schemas already got their root namespace set during serialization + // We just need to add xmlns to the other schema types + if(xmlNamespace && !["Composite", "Sequence"].includes(typeName)) { + return {_:serializedValue, $: {[xmlnsKey]: xmlNamespace}} ; + } + + return serializedValue; +} + function getValueOrFunctionResult( value: undefined | string | ((defaultValue: string) => string), defaultValueCreator: () => string From 3c76994451847ac0189e3310b7480888ec296823 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 15 Sep 2020 23:46:08 +0000 Subject: [PATCH 5/9] formatting --- sdk/core/core-http/src/serviceClient.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk/core/core-http/src/serviceClient.ts b/sdk/core/core-http/src/serviceClient.ts index 570ed12a24ee..5f4e0e1baed3 100644 --- a/sdk/core/core-http/src/serviceClient.ts +++ b/sdk/core/core-http/src/serviceClient.ts @@ -556,7 +556,12 @@ export function serializeRequestBody( if (operationSpec.isXML) { const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; - const value = getXmlValueWithNamespace(xmlNamespace, xmlnsKey, typeName, httpRequest.body); + const value = getXmlValueWithNamespace( + xmlNamespace, + xmlnsKey, + typeName, + httpRequest.body + ); if (typeName === MapperType.Sequence) { httpRequest.body = stringifyXML( utils.prepareXMLRootList( From ce547fb7797be915f0088f9f09adcc49deb29590 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Thu, 17 Sep 2020 18:16:01 +0000 Subject: [PATCH 6/9] Apply changes to core-client --- .../core-client/review/core-client.api.md | 4 + sdk/core/core-client/src/interfaces.ts | 44 ++ sdk/core/core-client/src/serializer.ts | 108 +++- sdk/core/core-client/src/serviceClient.ts | 52 +- .../core-client/test/serviceClient.spec.ts | 535 +++++++++++++++++- sdk/core/core-client/test/testMappers.ts | 268 +++++++++ sdk/core/core-http/src/serializer.ts | 111 +++- sdk/core/core-http/src/serviceClient.ts | 34 +- sdk/core/core-http/test/serviceClientTests.ts | 159 +++++- 9 files changed, 1251 insertions(+), 64 deletions(-) diff --git a/sdk/core/core-client/review/core-client.api.md b/sdk/core/core-client/review/core-client.api.md index 1681e51a5672..9d34631038de 100644 --- a/sdk/core/core-client/review/core-client.api.md +++ b/sdk/core/core-client/review/core-client.api.md @@ -41,6 +41,10 @@ export interface BaseMapper { xmlIsWrapped?: boolean; // (undocumented) xmlName?: string; + // (undocumented) + xmlNamespace?: string; + // (undocumented) + xmlNamespacePrefix?: string; } // @public (undocumented) diff --git a/sdk/core/core-client/src/interfaces.ts b/sdk/core/core-client/src/interfaces.ts index dd38df47b315..f2e29f0da31c 100644 --- a/sdk/core/core-client/src/interfaces.ts +++ b/sdk/core/core-client/src/interfaces.ts @@ -375,17 +375,61 @@ export interface EnumMapperType { } export interface BaseMapper { + /** + * Name for the xml element + */ xmlName?: string; + /** + * Xml element namespace + */ + xmlNamespace?: string; + /** + * Xml element namespace prefix + */ + xmlNamespacePrefix?: string; + /** + * Determines if the current property should be serialized as an attribute of the parent xml element + */ xmlIsAttribute?: boolean; + /** + * Name for the xml elements when serializing an array + */ xmlElementName?: string; + /** + * Whether or not the current propery should have a wrapping XML element + */ xmlIsWrapped?: boolean; + /** + * Whether or not the current propery is readonly + */ readOnly?: boolean; + /** + * Whether or not the current propery is a constant + */ isConstant?: boolean; + /** + * Whether or not the current propery is required + */ required?: boolean; + /** + * Whether or not the current propery allows mull as a value + */ nullable?: boolean; + /** + * The name to use when serializing + */ serializedName?: string; + /** + * Type of the mapper + */ type: MapperType; + /** + * Default value when one is not explicitly provided + */ defaultValue?: any; + /** + * Constraints to test the current value against + */ constraints?: MapperConstraints; } diff --git a/sdk/core/core-client/src/serializer.ts b/sdk/core/core-client/src/serializer.ts index d63a83efe684..8669d4e63518 100644 --- a/sdk/core/core-client/src/serializer.ts +++ b/sdk/core/core-client/src/serializer.ts @@ -153,11 +153,29 @@ class SerializerImpl implements Serializer { } else if (mapperType.match(/^Base64Url$/i) !== null) { payload = serializeBase64UrlType(objectName, object); } else if (mapperType.match(/^Sequence$/i) !== null) { - payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName); + payload = serializeSequenceType( + this, + mapper as SequenceMapper, + object, + objectName, + Boolean(this.isXML) + ); } else if (mapperType.match(/^Dictionary$/i) !== null) { - payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName); + payload = serializeDictionaryType( + this, + mapper as DictionaryMapper, + object, + objectName, + Boolean(this.isXML) + ); } else if (mapperType.match(/^Composite$/i) !== null) { - payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName); + payload = serializeCompositeType( + this, + mapper as CompositeMapper, + object, + objectName, + Boolean(this.isXML) + ); } } return payload; @@ -481,7 +499,8 @@ function serializeSequenceType( serializer: Serializer, mapper: SequenceMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): any { if (!Array.isArray(object)) { throw new Error(`${objectName} must be of type Array.`); @@ -495,7 +514,21 @@ function serializeSequenceType( } const tempArray = []; for (let i = 0; i < object.length; i++) { - tempArray[i] = serializer.serialize(elementType, object[i], objectName); + const serializedValue = serializer.serialize(elementType, object[i], objectName); + if (isXml && elementType.xmlNamespace) { + const xmlnsKey = elementType.xmlNamespacePrefix + ? `xmlns:${elementType.xmlNamespacePrefix}` + : "xmlns"; + if (elementType.type.name === "Composite") { + tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } else { + tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } + } + + tempArray[i] = serializedValue; } return tempArray; } @@ -504,7 +537,8 @@ function serializeDictionaryType( serializer: Serializer, mapper: DictionaryMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): any { if (typeof object !== "object") { throw new Error(`${objectName} must be of type object.`); @@ -518,8 +552,34 @@ function serializeDictionaryType( } const tempDictionary: { [key: string]: any } = {}; for (const key of Object.keys(object)) { - tempDictionary[key] = serializer.serialize(valueType, object[key], objectName + "." + key); + const serializedValue = serializer.serialize(valueType, object[key], objectName); + // If the element needs an XML namespace we need to add it within the $ property + if (isXml && valueType.xmlNamespace) { + const xmlnsKey = valueType.xmlNamespacePrefix + ? `xmlns:${valueType.xmlNamespacePrefix}` + : "xmlns"; + // If the value is an object the object's properties need to be siblings of the $ property + if (valueType.type.name === "Composite") { + tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; + continue; + } else { + // When the value is not an object, it has to go under _ + tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; + continue; + } + } + + // Just add the value when we are not serializing XML or it doesn't need a namespace + tempDictionary[key] = serializedValue; } + + // Add the namespace to the root element if needed + if (isXml && mapper.xmlNamespace) { + const xmlnsKey = mapper.xmlNamespacePrefix ? `xmlns:${mapper.xmlNamespacePrefix}` : "xmlns"; + + return { ...tempDictionary, $: { [xmlnsKey]: mapper.xmlNamespace } }; + } + return tempDictionary; } @@ -568,7 +628,8 @@ function serializeCompositeType( serializer: Serializer, mapper: CompositeMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): any { if (getPolymorphicDiscriminatorRecursively(serializer, mapper)) { mapper = getPolymorphicMapper(serializer, mapper, object, "clientName"); @@ -609,6 +670,12 @@ function serializeCompositeType( } if (parentObject !== undefined && parentObject !== null) { + if (isXml && mapper.xmlNamespace) { + const xmlnsKey = mapper.xmlNamespacePrefix + ? `xmlns:${mapper.xmlNamespacePrefix}` + : "xmlns"; + parentObject.$ = { ...parentObject.$, [xmlnsKey]: mapper.xmlNamespace }; + } const propertyObjectName = propertyMapper.serializedName !== "" ? objectName + "." + propertyMapper.serializedName @@ -630,16 +697,17 @@ function serializeCompositeType( propertyObjectName ); if (serializedValue !== undefined && propName !== undefined && propName !== null) { - if (propertyMapper.xmlIsAttribute) { + const value = getXmlObjectValue(propertyMapper, serializedValue, isXml); + if (isXml && propertyMapper.xmlIsAttribute) { // $ is the key attributes are kept under in xml2js. // This keeps things simple while preventing name collision // with names in user documents. parentObject.$ = parentObject.$ || {}; parentObject.$[propName] = serializedValue; - } else if (propertyMapper.xmlIsWrapped) { - parentObject[propName] = { [propertyMapper.xmlElementName!]: serializedValue }; + } else if (isXml && propertyMapper.xmlIsWrapped) { + parentObject[propName] = { [propertyMapper.xmlElementName!]: value }; } else { - parentObject[propName] = serializedValue; + parentObject[propName] = value; } } } @@ -665,6 +733,22 @@ function serializeCompositeType( return object; } +function getXmlObjectValue(propertyMapper: Mapper, serializedValue: any, isXml: boolean) { + if (!isXml || !propertyMapper.xmlNamespace) { + return serializedValue; + } + + const xmlnsKey = propertyMapper.xmlNamespacePrefix + ? `xmlns:${propertyMapper.xmlNamespacePrefix}` + : "xmlns"; + const xmlNamespace = { [xmlnsKey]: propertyMapper.xmlNamespace }; + + if (["Composite"].includes(propertyMapper.type.name)) { + return { $: xmlNamespace, ...serializedValue }; + } + return { _: serializedValue, $: xmlNamespace }; +} + function isSpecialXmlProperty(propertyName: string): boolean { return ["$", "_"].includes(propertyName); } diff --git a/sdk/core/core-client/src/serviceClient.ts b/sdk/core/core-client/src/serviceClient.ts index a8d761d767e7..fba4f29eed04 100644 --- a/sdk/core/core-client/src/serviceClient.ts +++ b/sdk/core/core-client/src/serviceClient.ts @@ -250,7 +250,14 @@ export function serializeRequestBody( ); const bodyMapper = operationSpec.requestBody.mapper; - const { required, serializedName, xmlName, xmlElementName } = bodyMapper; + const { + required, + serializedName, + xmlName, + xmlElementName, + xmlNamespace, + xmlNamespacePrefix + } = bodyMapper; const typeName = bodyMapper.type.name; try { @@ -267,13 +274,21 @@ export function serializeRequestBody( const isStream = typeName === MapperTypeNames.Stream; if (operationSpec.isXML) { + const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; + const value = getXmlValueWithNamespace(xmlNamespace, xmlnsKey, typeName, request.body); + if (typeName === MapperTypeNames.Sequence) { request.body = stringifyXML( - prepareXMLRootList(request.body, xmlElementName || xmlName || serializedName!), + prepareXMLRootList( + value, + xmlElementName || xmlName || serializedName!, + xmlnsKey, + xmlNamespace + ), { rootName: xmlName || serializedName } ); } else if (!isStream) { - request.body = stringifyXML(request.body, { + request.body = stringifyXML(value, { rootName: xmlName || serializedName }); } @@ -317,6 +332,24 @@ export function serializeRequestBody( } } +/** + * Adds an xml namespace to the xml serialized object if needed, otherwise it just returns the value itself + */ +function getXmlValueWithNamespace( + xmlNamespace: string | undefined, + xmlnsKey: string, + typeName: string, + serializedValue: any +): any { + // Composite and Sequence schemas already got their root namespace set during serialization + // We just need to add xmlns to the other schema types + if (xmlNamespace && !["Composite", "Sequence", "Dictionary"].includes(typeName)) { + return { _: serializedValue, $: { [xmlnsKey]: xmlNamespace } }; + } + + return serializedValue; +} + function createDefaultPipeline( options: { baseUri?: string; credential?: TokenCredential } = {} ): Pipeline { @@ -338,11 +371,20 @@ function createDefaultPipeline( return pipeline; } -function prepareXMLRootList(obj: any, elementName: string): { [key: string]: any[] } { +function prepareXMLRootList( + obj: any, + elementName: string, + xmlNamespaceKey?: string, + xmlNamespace?: string +): { [key: string]: any[] } { if (!Array.isArray(obj)) { obj = [obj]; } - return { [elementName]: obj }; + if (!xmlNamespaceKey || !xmlNamespace) { + return { [elementName]: obj }; + } + + return { [elementName]: obj, $: { [xmlNamespaceKey]: xmlNamespace } }; } function flattenResponse( diff --git a/sdk/core/core-client/test/serviceClient.spec.ts b/sdk/core/core-client/test/serviceClient.spec.ts index 5eb10b91bfd7..d35686ee7c5d 100644 --- a/sdk/core/core-client/test/serviceClient.spec.ts +++ b/sdk/core/core-client/test/serviceClient.spec.ts @@ -25,6 +25,7 @@ import { stringifyXML } from "@azure/core-xml"; import { serializeRequestBody } from "../src/serviceClient"; import { getOperationArgumentValueFromParameter } from "../src/operationHelpers"; import { deserializationPolicy } from "../src/deserializationPolicy"; +import { Mappers } from "./testMappers"; describe("ServiceClient", function() { it("should serialize headerCollectionPrefix", async function() { @@ -189,7 +190,7 @@ describe("ServiceClient", function() { 200: { bodyMapper: { type: { - name: "Sequence", + name: MapperTypeNames.Sequence, element: { type: { name: "Number" @@ -233,6 +234,32 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, `"body value"`); }); + it("should serialize a JSON String request body with namespace, ignoring namespace", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: "String" + } + } + }, + responses: { 200: {} }, + serializer: createSerializer() + } + ); + assert.strictEqual(httpRequest.body, `"body value"`); + }); + it("should serialize a JSON ByteArray request body", () => { const httpRequest = createPipelineRequest({ url: "https://example.com" }); serializeRequestBody( @@ -259,6 +286,34 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, `"SmF2YXNjcmlwdA=="`); }); + it("should serialize a JSON ByteArray request body, ignoring xml properties", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + xmlNamespace: "https://microsoft.com", + xmlNamespacePrefix: "test", + serializedName: "bodyArg", + type: { + name: MapperTypeNames.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: createSerializer() + } + ); + assert.strictEqual(httpRequest.body, `"SmF2YXNjcmlwdA=="`); + }); + it("should serialize a JSON Stream request body", () => { const httpRequest = createPipelineRequest({ url: "https://example.com" }); serializeRequestBody( @@ -286,6 +341,35 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, "body value"); }); + it("should serialize a JSON Stream request body", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + xmlNamespace: "http://microsoft.com", + xmlNamespacePrefix: "test", + serializedName: "bodyArg", + type: { + name: MapperTypeNames.Stream + } + } + }, + responses: { 200: {} }, + serializer: createSerializer() + }, + stringifyXML + ); + assert.strictEqual(httpRequest.body, "body value"); + }); + it("should serialize an XML String request body", () => { const httpRequest = createPipelineRequest({ url: "https://example.com" }); serializeRequestBody( @@ -317,6 +401,38 @@ describe("ServiceClient", function() { ); }); + it("should serialize an XML String request body, with namespace", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + xmlNamespace: "https://microsoft.com", + type: { + name: MapperTypeNames.String + } + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `body value` + ); + }); + it("should serialize an XML ByteArray request body", () => { const httpRequest = createPipelineRequest({ url: "https://example.com" }); serializeRequestBody( @@ -337,7 +453,7 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: createSerializer(), + serializer: createSerializer(undefined, true /** isXML */), isXML: true }, stringifyXML @@ -368,7 +484,7 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: createSerializer(), + serializer: createSerializer(undefined, true /** isXML */), isXML: true }, stringifyXML @@ -376,6 +492,419 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, "body value"); }); + it("should serialize an XML Stream request body, with namespace", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: MapperTypeNames.Stream + } + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual(httpRequest.body, "body value"); + }); + + it("should serialize an XML ByteArray request body with namespace and prefix", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: stringToByteArray("Javascript") + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + xmlNamespace: "https://microsoft.com", + xmlNamespacePrefix: "sample", + required: true, + serializedName: "bodyArg", + type: { + name: MapperTypeNames.ByteArray + } + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `SmF2YXNjcmlwdA==` + ); + }); + + it("should serialize an XML Composite request body", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + requestBody: { + updated: new Date("2020-08-12T23:36:18.308Z"), + content: { type: "application/xml", queueDescription: { maxDeliveryCount: 15 } } + } + }, + { + httpMethod: "POST", + requestBody: Mappers.requestBody1, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `2020-08-12T23:36:18.308Z15` + ); + }); + + it("should serialize a JSON Composite request body, ignoring XML metadata", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + requestBody: { + updated: new Date("2020-08-12T23:36:18.308Z"), + content: { type: "application/xml", queueDescription: { maxDeliveryCount: 15 } } + } + }, + { + httpMethod: "POST", + requestBody: Mappers.requestBody1, + responses: { 200: {} }, + serializer: createSerializer() + } + ); + + assert.deepEqual( + httpRequest.body, + '{"updated":"2020-08-12T23:36:18.308Z","content":{"type":"application/xml","queueDescription":{"maxDeliveryCount":15}}}' + ); + }); + + it("should serialize an XML Array request body with namespace and prefix", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: ["Foo", "Bar"] + }, + { + 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: "String" } + } + } + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `FooBar` + ); + }); + + it("should serialize a JSON Array request body, ignoring XML metadata", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + bodyArg: ["Foo", "Bar"] + }, + { + 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: "String" } + } + } + } + }, + responses: { 200: {} }, + serializer: createSerializer() + } + ); + 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, + `Foo1Bar1Foo2Bar2Foo3Bar3` + ); + }); + + 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, + `FooBar` + ); + }); + + it("should serialize an XML Dictionary request body", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `helloworld` + ); + }); + + it("should serialize an XML Dictionary request body, with namespace and prefix", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + xmlNamespacePrefix: "sample", + xmlNamespace: "https://microsoft.com", + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + xmlNamespacePrefix: "el", + xmlNamespace: "https://microsoft.com/element", + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: createSerializer(undefined, true /** isXML */), + isXML: true + }, + stringifyXML + ); + assert.strictEqual( + httpRequest.body, + `helloworld` + ); + }); + + it("should serialize a JSON Dictionary request body, ignoring xml metadata", () => { + const httpRequest = createPipelineRequest({ url: "https://example.com" }); + serializeRequestBody( + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + xmlNamespacePrefix: "sample", + xmlNamespace: "https://microsoft.com", + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + xmlNamespacePrefix: "el", + xmlNamespace: "https://microsoft.com/element", + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: createSerializer() + }, + stringifyXML + ); + assert.deepEqual(httpRequest.body, `{"alpha":"hello","beta":"world"}`); + }); + it("should serialize a string send to a text/plain endpoint as just a string", () => { const httpRequest = createPipelineRequest({ url: "https://example.com" }); serializeRequestBody( diff --git a/sdk/core/core-client/test/testMappers.ts b/sdk/core/core-client/test/testMappers.ts index 1db4895d63a9..88022ffa533e 100644 --- a/sdk/core/core-client/test/testMappers.ts +++ b/sdk/core/core-client/test/testMappers.ts @@ -1,5 +1,268 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. + +import { CompositeMapper } from "../src/interfaces"; + +const QueueDescription: CompositeMapper = { + serializedName: "QueueDescription", + xmlName: "QueueDescription", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Composite", + className: "QueueDescription", + modelProperties: { + lockDuration: { + serializedName: "lockDuration", + xmlName: "LockDuration", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + maxSizeInMegabytes: { + serializedName: "maxSizeInMegabytes", + xmlName: "MaxSizeInMegabytes", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + requiresDuplicateDetection: { + serializedName: "requiresDuplicateDetection", + xmlName: "RequiresDuplicateDetection", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + requiresSession: { + serializedName: "requiresSession", + xmlName: "RequiresSession", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + defaultMessageTimeToLive: { + serializedName: "defaultMessageTimeToLive", + xmlName: "DefaultMessageTimeToLive", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + deadLetteringOnMessageExpiration: { + serializedName: "deadLetteringOnMessageExpiration", + xmlName: "DeadLetteringOnMessageExpiration", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + duplicateDetectionHistoryTimeWindow: { + serializedName: "duplicateDetectionHistoryTimeWindow", + xmlName: "DuplicateDetectionHistoryTimeWindow", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + maxDeliveryCount: { + serializedName: "maxDeliveryCount", + xmlName: "MaxDeliveryCount", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + enableBatchedOperations: { + serializedName: "enableBatchedOperations", + xmlName: "EnableBatchedOperations", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + sizeInBytes: { + serializedName: "sizeInBytes", + xmlName: "SizeInBytes", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + messageCount: { + serializedName: "messageCount", + xmlName: "MessageCount", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Number" + } + }, + isAnonymousAccessible: { + serializedName: "isAnonymousAccessible", + xmlName: "IsAnonymousAccessible", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + status: { + serializedName: "status", + xmlName: "Status", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + forwardTo: { + serializedName: "forwardTo", + xmlName: "ForwardTo", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + userMetadata: { + serializedName: "userMetadata", + xmlName: "UserMetadata", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + createdAt: { + serializedName: "createdAt", + xmlName: "CreatedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + updatedAt: { + serializedName: "updatedAt", + xmlName: "UpdatedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + accessedAt: { + serializedName: "accessedAt", + xmlName: "AccessedAt", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "DateTime" + } + }, + supportOrdering: { + serializedName: "supportOrdering", + xmlName: "SupportOrdering", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + autoDeleteOnIdle: { + serializedName: "autoDeleteOnIdle", + xmlName: "AutoDeleteOnIdle", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "TimeSpan" + } + }, + enablePartitioning: { + serializedName: "enablePartitioning", + xmlName: "EnablePartitioning", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + entityAvailabilityStatus: { + serializedName: "entityAvailabilityStatus", + xmlName: "EntityAvailabilityStatus", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + }, + enableExpress: { + serializedName: "enableExpress", + xmlName: "EnableExpress", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "Boolean" + } + }, + forwardDeadLetteredMessagesTo: { + serializedName: "forwardDeadLetteredMessagesTo", + xmlName: "ForwardDeadLetteredMessagesTo", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + name: "String" + } + } + } + } +}; + +const CreateQueueBodyContent: CompositeMapper = { + serializedName: "CreateQueueBodyContent", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "Composite", + className: "CreateQueueBodyContent", + modelProperties: { + type: { + defaultValue: "application/xml", + serializedName: "type", + xmlName: "type", + xmlIsAttribute: true, + type: { + name: "String" + } + }, + queueDescription: { + serializedName: "queueDescription", + xmlName: "QueueDescription", + xmlNamespace: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect", + type: { + ...QueueDescription.type + } + } + } + } +}; + +const CreateQueueBody: CompositeMapper = { + serializedName: "CreateQueueBody", + xmlName: "entry", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "Composite", + className: "CreateQueueBody", + modelProperties: { + updated: { + serializedName: "updated", + xmlName: "updated", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + name: "DateTime" + } + }, + content: { + serializedName: "content", + xmlName: "content", + xmlNamespace: "http://www.w3.org/2005/Atom", + type: { + ...CreateQueueBodyContent.type + } + } + } + } +}; + const internalMappers: any = {}; internalMappers.SimpleProduct = { @@ -750,4 +1013,9 @@ internalMappers.discriminators = { "Pet.Dog": internalMappers.Dog }; +internalMappers.requestBody1 = { + parameterPath: "requestBody", + mapper: CreateQueueBody +}; + export const Mappers = internalMappers; diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index b08760a586c4..88508b8a8eca 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -144,11 +144,29 @@ export class Serializer { } else if (mapperType.match(/^Base64Url$/i) !== null) { payload = serializeBase64UrlType(objectName, object); } else if (mapperType.match(/^Sequence$/i) !== null) { - payload = serializeSequenceType(this, mapper as SequenceMapper, object, objectName, Boolean(this.isXML)); + payload = serializeSequenceType( + this, + mapper as SequenceMapper, + object, + objectName, + Boolean(this.isXML) + ); } else if (mapperType.match(/^Dictionary$/i) !== null) { - payload = serializeDictionaryType(this, mapper as DictionaryMapper, object, objectName); + payload = serializeDictionaryType( + this, + mapper as DictionaryMapper, + object, + objectName, + Boolean(this.isXML) + ); } else if (mapperType.match(/^Composite$/i) !== null) { - payload = serializeCompositeType(this, mapper as CompositeMapper, object, objectName, Boolean(this.isXML)); + payload = serializeCompositeType( + this, + mapper as CompositeMapper, + object, + objectName, + Boolean(this.isXML) + ); } } return payload; @@ -484,13 +502,13 @@ function serializeSequenceType( const xmlnsKey = elementType.xmlNamespacePrefix ? `xmlns:${elementType.xmlNamespacePrefix}` : "xmlns"; - if(elementType.type.name === "Composite") { - tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; - } else { - tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; - } + if (elementType.type.name === "Composite") { + tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } else { + tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; + continue; + } } tempArray[i] = serializedValue; @@ -502,7 +520,8 @@ function serializeDictionaryType( serializer: Serializer, mapper: DictionaryMapper, object: any, - objectName: string + objectName: string, + isXml: boolean ): { [key: string]: any } { if (typeof object !== "object") { throw new Error(`${objectName} must be of type object.`); @@ -516,8 +535,34 @@ function serializeDictionaryType( } const tempDictionary: { [key: string]: any } = {}; for (const key of Object.keys(object)) { - tempDictionary[key] = serializer.serialize(valueType, object[key], objectName + "." + key); + const serializedValue = serializer.serialize(valueType, object[key], objectName); + // If the element needs an XML namespace we need to add it within the $ property + if (isXml && valueType.xmlNamespace) { + const xmlnsKey = valueType.xmlNamespacePrefix + ? `xmlns:${valueType.xmlNamespacePrefix}` + : "xmlns"; + // If the value is an object the object's properties need to be siblings of the $ property + if (valueType.type.name === "Composite") { + tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; + continue; + } else { + // When the value is not an object, it has to go under _ + tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; + continue; + } + } + + // Just add the value when we are not serializing XML or it doesn't need a namespace + tempDictionary[key] = serializedValue; } + + // Add the namespace to the root element if needed + if (isXml && mapper.xmlNamespace) { + const xmlnsKey = mapper.xmlNamespacePrefix ? `xmlns:${mapper.xmlNamespacePrefix}` : "xmlns"; + + return { ...tempDictionary, $: { [xmlnsKey]: mapper.xmlNamespace } }; + } + return tempDictionary; } @@ -1002,19 +1047,61 @@ export interface EnumMapperType { } export interface BaseMapper { + /** + * Name for the xml element + */ xmlName?: string; + /** + * Xml element namespace + */ xmlNamespace?: string; + /** + * Xml element namespace prefix + */ xmlNamespacePrefix?: string; + /** + * Determines if the current property should be serialized as an attribute of the parent xml element + */ xmlIsAttribute?: boolean; + /** + * Name for the xml elements when serializing an array + */ xmlElementName?: string; + /** + * Whether or not the current propery should have a wrapping XML element + */ xmlIsWrapped?: boolean; + /** + * Whether or not the current propery is readonly + */ readOnly?: boolean; + /** + * Whether or not the current propery is a constant + */ isConstant?: boolean; + /** + * Whether or not the current propery is required + */ required?: boolean; + /** + * Whether or not the current propery allows mull as a value + */ nullable?: boolean; + /** + * The name to use when serializing + */ serializedName?: string; + /** + * Type of the mapper + */ type: MapperType; + /** + * Default value when one is not explicitly provided + */ defaultValue?: any; + /** + * Constraints to test the current value against + */ constraints?: MapperConstraints; } diff --git a/sdk/core/core-http/src/serviceClient.ts b/sdk/core/core-http/src/serviceClient.ts index 5f4e0e1baed3..4f67810a9f6f 100644 --- a/sdk/core/core-http/src/serviceClient.ts +++ b/sdk/core/core-http/src/serviceClient.ts @@ -538,7 +538,14 @@ export function serializeRequestBody( ); const bodyMapper = operationSpec.requestBody.mapper; - const { required, xmlName, xmlElementName, serializedName, xmlNamespace, xmlNamespacePrefix} = bodyMapper; + const { + required, + xmlName, + xmlElementName, + serializedName, + xmlNamespace, + xmlNamespacePrefix + } = bodyMapper; const typeName = bodyMapper.type.name; try { @@ -555,13 +562,13 @@ export function serializeRequestBody( const isStream = typeName === MapperType.Stream; if (operationSpec.isXML) { - const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; - const value = getXmlValueWithNamespace( - xmlNamespace, - xmlnsKey, - typeName, - httpRequest.body - ); + const xmlnsKey = xmlNamespacePrefix ? `xmlns:${xmlNamespacePrefix}` : "xmlns"; + const value = getXmlValueWithNamespace( + xmlNamespace, + xmlnsKey, + typeName, + httpRequest.body + ); if (typeName === MapperType.Sequence) { httpRequest.body = stringifyXML( utils.prepareXMLRootList( @@ -622,11 +629,16 @@ export function serializeRequestBody( /** * Adds an xml namespace to the xml serialized object if needed, otherwise it just returns the value itself */ -function getXmlValueWithNamespace(xmlNamespace: string | undefined, xmlnsKey: string, typeName: string, serializedValue: any): any { +function getXmlValueWithNamespace( + xmlNamespace: string | undefined, + xmlnsKey: string, + typeName: string, + serializedValue: any +): any { // Composite and Sequence schemas already got their root namespace set during serialization // We just need to add xmlns to the other schema types - if(xmlNamespace && !["Composite", "Sequence"].includes(typeName)) { - return {_:serializedValue, $: {[xmlnsKey]: xmlNamespace}} ; + if (xmlNamespace && !["Composite", "Sequence", "Dictionary"].includes(typeName)) { + return { _: serializedValue, $: { [xmlnsKey]: xmlNamespace } }; } return serializedValue; diff --git a/sdk/core/core-http/test/serviceClientTests.ts b/sdk/core/core-http/test/serviceClientTests.ts index 2ea572891850..8ca5126b9b6d 100644 --- a/sdk/core/core-http/test/serviceClientTests.ts +++ b/sdk/core/core-http/test/serviceClientTests.ts @@ -504,13 +504,12 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: new Serializer(), + serializer: new Serializer() } ); assert.strictEqual(httpRequest.body, `"body value"`); }); - it("should serialize a JSON ByteArray request body", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -559,13 +558,10 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: new Serializer(undefined, false /** isXML */), + serializer: new Serializer(undefined, false /** isXML */) } ); - assert.strictEqual( - httpRequest.body, - `"SmF2YXNjcmlwdA=="` - ); + assert.strictEqual(httpRequest.body, `"SmF2YXNjcmlwdA=="`); }); it("should serialize a JSON Stream request body", () => { @@ -707,7 +703,7 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: new Serializer(undefined, true/** isXml */), + serializer: new Serializer(undefined, true /** isXml */), isXML: true } ); @@ -717,6 +713,127 @@ describe("ServiceClient", function() { ); }); + it("should serialize an XML Dictionary request body", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true /** isXml */), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `helloworld` + ); + }); + + it("should serialize an XML Dictionary request body, with namespace and prefix", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + xmlNamespacePrefix: "sample", + xmlNamespace: "https://microsoft.com", + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + xmlNamespacePrefix: "el", + xmlNamespace: "https://microsoft.com/element", + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: new Serializer(undefined, true /** isXml */), + isXML: true + } + ); + assert.strictEqual( + httpRequest.body, + `helloworld` + ); + }); + + it("should serialize a JSON Dictionary request body, ignoring xml metadata", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + metadata: { + alpha: "hello", + beta: "world" + } + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "metadata", + mapper: { + xmlNamespacePrefix: "sample", + xmlNamespace: "https://microsoft.com", + serializedName: "metadata", + type: { + name: "Dictionary", + value: { + xmlNamespacePrefix: "el", + xmlNamespace: "https://microsoft.com/element", + type: { + name: "String" + } + } + }, + headerCollectionPrefix: "foo-bar-" + } + }, + responses: { 200: {} }, + serializer: new Serializer() + } + ); + assert.deepEqual(httpRequest.body, `{"alpha":"hello","beta":"world"}`); + }); + it("should serialize a Json ByteArray request body, ignoring namespace", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -739,16 +856,12 @@ describe("ServiceClient", function() { } }, responses: { 200: {} }, - serializer: new Serializer(undefined, false /** isXML */), + serializer: new Serializer(undefined, false /** isXML */) } ); - assert.strictEqual( - httpRequest.body, - `"SmF2YXNjcmlwdA=="` - ); + assert.strictEqual(httpRequest.body, `"SmF2YXNjcmlwdA=="`); }); - it("should serialize an XML ByteArray request body with namespace", () => { const httpRequest = new WebResource(); serializeRequestBody( @@ -854,11 +967,14 @@ describe("ServiceClient", function() { httpMethod: "POST", requestBody: requestBody1, responses: { 200: {} }, - serializer: new Serializer(), + serializer: new Serializer() } ); - assert.deepEqual(httpRequest.body, '{"updated":"2020-08-12T23:36:18.308Z","content":{"type":"application/xml","queueDescription":{"maxDeliveryCount":15}}}'); + assert.deepEqual( + httpRequest.body, + '{"updated":"2020-08-12T23:36:18.308Z","content":{"type":"application/xml","queueDescription":{"maxDeliveryCount":15}}}' + ); }); it("should serialize an XML Array request body with namespace and prefix", () => { @@ -928,19 +1044,20 @@ describe("ServiceClient", function() { serializer: new Serializer() } ); - assert.deepEqual( - httpRequest.body, JSON.stringify(["Foo", "Bar"]) - ); + assert.deepEqual(httpRequest.body, JSON.stringify(["Foo", "Bar"])); }); - it("should serialize an XML Array of composite elements, namespace and prefix", () => { const httpRequest = new WebResource(); serializeRequestBody( new ServiceClient(), httpRequest, { - bodyArg: [ { foo: "Foo1", bar: "Bar1" }, { foo: "Foo2", bar: "Bar2" }, { foo: "Foo3", bar: "Bar3" }] + bodyArg: [ + { foo: "Foo1", bar: "Bar1" }, + { foo: "Foo2", bar: "Bar2" }, + { foo: "Foo3", bar: "Bar3" } + ] }, { httpMethod: "POST", From f98feeac3630a44066b03bf672423d18f07ebbe4 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Thu, 17 Sep 2020 19:46:19 +0000 Subject: [PATCH 7/9] Address comments --- sdk/core/core-client/src/serializer.ts | 14 ++++----- .../core-client/test/serviceClient.spec.ts | 3 ++ sdk/core/core-http/review/core-http.api.md | 14 --------- sdk/core/core-http/src/serializer.ts | 14 ++++----- sdk/core/core-http/test/serviceClientTests.ts | 30 +++++++++++++++++++ 5 files changed, 43 insertions(+), 32 deletions(-) diff --git a/sdk/core/core-client/src/serializer.ts b/sdk/core/core-client/src/serializer.ts index 8669d4e63518..b38bc4cf3722 100644 --- a/sdk/core/core-client/src/serializer.ts +++ b/sdk/core/core-client/src/serializer.ts @@ -521,14 +521,12 @@ function serializeSequenceType( : "xmlns"; if (elementType.type.name === "Composite") { tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; } else { tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; } + } else { + tempArray[i] = serializedValue; } - - tempArray[i] = serializedValue; } return tempArray; } @@ -561,16 +559,14 @@ function serializeDictionaryType( // If the value is an object the object's properties need to be siblings of the $ property if (valueType.type.name === "Composite") { tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - continue; } else { // When the value is not an object, it has to go under _ tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - continue; } + } else { + // Add the serialized value when we are not serializing XML or it doesn't need a namespace + tempDictionary[key] = serializedValue; } - - // Just add the value when we are not serializing XML or it doesn't need a namespace - tempDictionary[key] = serializedValue; } // Add the namespace to the root element if needed diff --git a/sdk/core/core-client/test/serviceClient.spec.ts b/sdk/core/core-client/test/serviceClient.spec.ts index d35686ee7c5d..e7c9984aafc1 100644 --- a/sdk/core/core-client/test/serviceClient.spec.ts +++ b/sdk/core/core-client/test/serviceClient.spec.ts @@ -247,6 +247,8 @@ describe("ServiceClient", function() { parameterPath: "bodyArg", mapper: { required: true, + xmlNamespace: "https://example.com", + xmlNamespacePrefix: "foo", serializedName: "bodyArg", type: { name: "String" @@ -505,6 +507,7 @@ describe("ServiceClient", function() { parameterPath: "bodyArg", mapper: { required: true, + xmlNamespace: "https://microsoft.com", serializedName: "bodyArg", type: { name: MapperTypeNames.Stream diff --git a/sdk/core/core-http/review/core-http.api.md b/sdk/core/core-http/review/core-http.api.md index 8c24346958f7..0586887a7575 100644 --- a/sdk/core/core-http/review/core-http.api.md +++ b/sdk/core/core-http/review/core-http.api.md @@ -54,33 +54,19 @@ export type Authenticator = (challenge: object) => Promise; // @public (undocumented) export interface BaseMapper { - // (undocumented) constraints?: MapperConstraints; - // (undocumented) defaultValue?: any; - // (undocumented) isConstant?: boolean; - // (undocumented) nullable?: boolean; - // (undocumented) readOnly?: boolean; - // (undocumented) required?: boolean; - // (undocumented) serializedName?: string; - // (undocumented) type: MapperType; - // (undocumented) xmlElementName?: string; - // (undocumented) xmlIsAttribute?: boolean; - // (undocumented) xmlIsWrapped?: boolean; - // (undocumented) xmlName?: string; - // (undocumented) xmlNamespace?: string; - // (undocumented) xmlNamespacePrefix?: string; } diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index 88508b8a8eca..18074384bdf0 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -504,14 +504,12 @@ function serializeSequenceType( : "xmlns"; if (elementType.type.name === "Composite") { tempArray[i] = { ...serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; } else { tempArray[i] = { _: serializedValue, $: { [xmlnsKey]: elementType.xmlNamespace } }; - continue; } + } else { + tempArray[i] = serializedValue; } - - tempArray[i] = serializedValue; } return tempArray; } @@ -544,16 +542,14 @@ function serializeDictionaryType( // If the value is an object the object's properties need to be siblings of the $ property if (valueType.type.name === "Composite") { tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - continue; } else { // When the value is not an object, it has to go under _ tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - continue; } + } else { + // Add the serialized value when we are not serializing XML or it doesn't need a namespace + tempDictionary[key] = serializedValue; } - - // Just add the value when we are not serializing XML or it doesn't need a namespace - tempDictionary[key] = serializedValue; } // Add the namespace to the root element if needed diff --git a/sdk/core/core-http/test/serviceClientTests.ts b/sdk/core/core-http/test/serviceClientTests.ts index 8ca5126b9b6d..5ad4cc6c85ed 100644 --- a/sdk/core/core-http/test/serviceClientTests.ts +++ b/sdk/core/core-http/test/serviceClientTests.ts @@ -497,6 +497,8 @@ describe("ServiceClient", function() { parameterPath: "bodyArg", mapper: { required: true, + xmlNamespace: "https://example.com", + xmlNamespacePrefix: "foo", serializedName: "bodyArg", type: { name: MapperType.String @@ -1188,6 +1190,34 @@ describe("ServiceClient", function() { assert.strictEqual(httpRequest.body, "body value"); }); + it("should serialize an XML Stream request body, with namespace", () => { + const httpRequest = new WebResource(); + serializeRequestBody( + new ServiceClient(), + httpRequest, + { + bodyArg: "body value" + }, + { + httpMethod: "POST", + requestBody: { + parameterPath: "bodyArg", + mapper: { + required: true, + serializedName: "bodyArg", + type: { + name: MapperType.Stream + } + } + }, + responses: { 200: {} }, + serializer: new Serializer(), + isXML: true + } + ); + assert.strictEqual(httpRequest.body, "body value"); + }); + it("should serialize a string send to a text/plain endpoint as just a string", () => { const httpRequest = new WebResource(); serializeRequestBody( From dea1c34a47f4b3e142380bb2577111be7ca03c52 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Fri, 18 Sep 2020 14:35:13 +0000 Subject: [PATCH 8/9] update core-client api-extractor --- sdk/core/core-client/review/core-client.api.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sdk/core/core-client/review/core-client.api.md b/sdk/core/core-client/review/core-client.api.md index 9d34631038de..e647e4e5be33 100644 --- a/sdk/core/core-client/review/core-client.api.md +++ b/sdk/core/core-client/review/core-client.api.md @@ -17,33 +17,19 @@ import { TransferProgressEvent } from '@azure/core-https'; // @public (undocumented) export interface BaseMapper { - // (undocumented) constraints?: MapperConstraints; - // (undocumented) defaultValue?: any; - // (undocumented) isConstant?: boolean; - // (undocumented) nullable?: boolean; - // (undocumented) readOnly?: boolean; - // (undocumented) required?: boolean; - // (undocumented) serializedName?: string; - // (undocumented) type: MapperType; - // (undocumented) xmlElementName?: string; - // (undocumented) xmlIsAttribute?: boolean; - // (undocumented) xmlIsWrapped?: boolean; - // (undocumented) xmlName?: string; - // (undocumented) xmlNamespace?: string; - // (undocumented) xmlNamespacePrefix?: string; } From c140b4bbb8f746dd37cb7910747f2be4898391a5 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Fri, 18 Sep 2020 16:35:26 +0000 Subject: [PATCH 9/9] use getXmlObjectValue --- sdk/core/core-client/src/serializer.ts | 16 +--------------- sdk/core/core-http/src/serializer.ts | 16 +--------------- 2 files changed, 2 insertions(+), 30 deletions(-) diff --git a/sdk/core/core-client/src/serializer.ts b/sdk/core/core-client/src/serializer.ts index b38bc4cf3722..82cb24707674 100644 --- a/sdk/core/core-client/src/serializer.ts +++ b/sdk/core/core-client/src/serializer.ts @@ -552,21 +552,7 @@ function serializeDictionaryType( for (const key of Object.keys(object)) { const serializedValue = serializer.serialize(valueType, object[key], objectName); // If the element needs an XML namespace we need to add it within the $ property - if (isXml && valueType.xmlNamespace) { - const xmlnsKey = valueType.xmlNamespacePrefix - ? `xmlns:${valueType.xmlNamespacePrefix}` - : "xmlns"; - // If the value is an object the object's properties need to be siblings of the $ property - if (valueType.type.name === "Composite") { - tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - } else { - // When the value is not an object, it has to go under _ - tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - } - } else { - // Add the serialized value when we are not serializing XML or it doesn't need a namespace - tempDictionary[key] = serializedValue; - } + tempDictionary[key] = getXmlObjectValue(valueType, serializedValue, isXml); } // Add the namespace to the root element if needed diff --git a/sdk/core/core-http/src/serializer.ts b/sdk/core/core-http/src/serializer.ts index 18074384bdf0..685f4d9f076d 100644 --- a/sdk/core/core-http/src/serializer.ts +++ b/sdk/core/core-http/src/serializer.ts @@ -535,21 +535,7 @@ function serializeDictionaryType( for (const key of Object.keys(object)) { const serializedValue = serializer.serialize(valueType, object[key], objectName); // If the element needs an XML namespace we need to add it within the $ property - if (isXml && valueType.xmlNamespace) { - const xmlnsKey = valueType.xmlNamespacePrefix - ? `xmlns:${valueType.xmlNamespacePrefix}` - : "xmlns"; - // If the value is an object the object's properties need to be siblings of the $ property - if (valueType.type.name === "Composite") { - tempDictionary[key] = { ...serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - } else { - // When the value is not an object, it has to go under _ - tempDictionary[key] = { _: serializedValue, $: { [xmlnsKey]: valueType.xmlNamespace } }; - } - } else { - // Add the serialized value when we are not serializing XML or it doesn't need a namespace - tempDictionary[key] = serializedValue; - } + tempDictionary[key] = getXmlObjectValue(valueType, serializedValue, isXml); } // Add the namespace to the root element if needed