diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index 75da21dd8639..fe5101ce630b 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -165,6 +165,7 @@ dependencies:
tunnel: 0.0.6
typescript: 3.5.3
uglify-js: 3.6.0
+ underscore: 1.4.4
url: 0.11.0
util: 0.12.1
uuid: 3.3.3
@@ -174,6 +175,7 @@ dependencies:
ws: 7.1.2
xhr-mock: 2.5.0
xml2js: 0.4.19
+ xmlbuilder: 0.4.3
yargs: 13.3.0
yarn: 1.17.3
lockfileVersion: 5.1
@@ -9024,6 +9026,10 @@ packages:
node: '>=0.10.0'
resolution:
integrity: sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
+ /underscore/1.4.4:
+ dev: false
+ resolution:
+ integrity: sha1-YaajIBBiKvoHljvzJSA88SI51gQ=
/underscore/1.8.3:
dev: false
resolution:
@@ -9525,6 +9531,12 @@ packages:
dev: false
resolution:
integrity: sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
+ /xmlbuilder/0.4.3:
+ dev: false
+ engines:
+ node: '>=0.2.0'
+ resolution:
+ integrity: sha1-xGFLp04K0ZbmCcknLNnh3bKKilg=
/xmlbuilder/8.2.2:
dev: false
engines:
@@ -9793,7 +9805,7 @@ packages:
dev: false
name: '@rush-temp/abort-controller'
resolution:
- integrity: sha512-bOazCplSYAusToKu3B9vUnNASGvMSR9Yyj4i1PFTGdOfMyd5t+VZcS6RgEdtcAr+6MoUwJ230UkC/7JZ+HkdVQ==
+ integrity: sha512-W9CsoX+0a+9AAHje89SaMJSq4zFtlcVwA8KRQR6nf+e0aSBjmIiU4NAZUfRwvXE11vWhjo+YUcV2+ISBt/UvWQ==
tarball: 'file:projects/abort-controller.tgz'
version: 0.0.0
'file:projects/core-amqp.tgz':
@@ -9861,7 +9873,7 @@ packages:
dev: false
name: '@rush-temp/core-amqp'
resolution:
- integrity: sha512-zOQxTAzv2RDm8YmYZ8Tr+y82OmJANT0b3iJuBg+e02gcCSAS7N57zjeaZYSjq6dNEND7Jseo+xVZ/QdDNVGIsA==
+ integrity: sha512-jJ88adCOOKofY9LnfVCihOXMnB467Q3PLZuj4GmJpM51DyFx0KdCic1n3TqTj7SQ9sj1rKtVWyVAVAaOJRwTTA==
tarball: 'file:projects/core-amqp.tgz'
version: 0.0.0
'file:projects/core-arm.tgz':
@@ -9896,7 +9908,7 @@ packages:
dev: false
name: '@rush-temp/core-arm'
resolution:
- integrity: sha512-9uqsxSLJiO0MSCXGkCe42Kf/kJO0w7GuYc4efzzjMJOsbsjBfu4wpt5IJOyzMUznyaPBFKOiR8wKboyhNfbzmg==
+ integrity: sha512-GVZI9FYYYQ4Jt0BkIi0OJ5vhcKGeoUhaUA8mw3XXNBvERXJqBZSpiWiJ03lXbnEgWvcz4Yn2EvgiWT0JSj2gkw==
tarball: 'file:projects/core-arm.tgz'
version: 0.0.0
'file:projects/core-asynciterator-polyfill.tgz':
@@ -9914,7 +9926,7 @@ packages:
dev: false
name: '@rush-temp/core-asynciterator-polyfill'
resolution:
- integrity: sha512-X2qR67x/Wgwcs8VjH/m3EA7HFB22mL2sf9YJCvpdIUXvz5ENGH54U4TC8IAc2AxcqlKjOi1pwFUAf9yBf4nAJQ==
+ integrity: sha512-hp2FjyoaLo9iJIGV0Aq7UI8oGzIr29OewVSFfuckKI+2aDUrh6Kamlg/hw/HrZ/a3ybccP3oqGjwA2uze4DoAg==
tarball: 'file:projects/core-asynciterator-polyfill.tgz'
version: 0.0.0
'file:projects/core-auth.tgz':
@@ -9952,7 +9964,7 @@ packages:
dev: false
name: '@rush-temp/core-auth'
resolution:
- integrity: sha512-WcJPwF+dNlpEQW1GRzGQIY6YEI+uz6ChF3PISTFVyFbZjK1k+5r0ly2DRCplHUd6tVJ2GoqbqmSHR8Doz/E4Mw==
+ integrity: sha512-oVJYTPPuUZ3iD4kJtFBd0lRmS1aca8qhLXqlV3TtVIIw/MHZ+kQiFiy/bJTwjSpxOWzL4b4l8yeg2dAXjyuROg==
tarball: 'file:projects/core-auth.tgz'
version: 0.0.0
'file:projects/core-http.tgz':
@@ -10025,17 +10037,19 @@ packages:
tunnel: 0.0.6
typescript: 3.5.3
uglify-js: 3.6.0
+ underscore: 1.4.4
uuid: 3.3.3
webpack: 4.39.2_webpack@4.39.2
webpack-cli: 3.3.7_webpack@4.39.2
webpack-dev-middleware: 3.7.0_webpack@4.39.2
xhr-mock: 2.5.0
xml2js: 0.4.19
+ xmlbuilder: 0.4.3
yarn: 1.17.3
dev: false
name: '@rush-temp/core-http'
resolution:
- integrity: sha512-tnBnNKZPZvmwesatSPql50OdGqX9w/Hi2qEbvu2ATsp93+M6E5Ofg37iul2dlUdzwkfwa3tX97eTikU96MyvXw==
+ integrity: sha512-jZg32+noo1+LHfWEkWzu/cwN4vaxXenspeoozEwNiA3hgD7PI2DKZfrbVBOp1zj6Wp3VSkG3Kv2NydKXm971rw==
tarball: 'file:projects/core-http.tgz'
version: 0.0.0
'file:projects/core-paging.tgz':
@@ -10054,7 +10068,7 @@ packages:
dev: false
name: '@rush-temp/core-paging'
resolution:
- integrity: sha512-/l5SA2u/jUrYjvQBf24YFdaoYW4GAdU3iDoGDekLbjH2fSP8wOt/3oDlBM8pxGD54TB7s06Oi4OjBsQN6F5tgw==
+ integrity: sha512-rorSx6Oeq/VsKWv3L8qwnHYihYjPZS/PiCZZrIlGt7WfSH8iS60XFPxRrAiP+zIhosvd1hDlSmQ8k30iIB10QA==
tarball: 'file:projects/core-paging.tgz'
version: 0.0.0
'file:projects/core-tracing.tgz':
@@ -10092,7 +10106,7 @@ packages:
dev: false
name: '@rush-temp/core-tracing'
resolution:
- integrity: sha512-yAQfqHw4bMOPRFf2Q0SGJtgtdw+RPhMQtkNBh1s8TO5kk1HV7cE6S4Fh3kH3msRmRFM3M9yLmdcVt5asCcRjtg==
+ integrity: sha512-CRGjX9TjXCkXJMbYqwOpJxxMCDPq3ZUqJML0wdJD/1dUX84mu9aD0iMi6QjPxMPiuYmlvbqJ+aLaNXcZPoNY3A==
tarball: 'file:projects/core-tracing.tgz'
version: 0.0.0
'file:projects/event-hubs.tgz':
@@ -10168,7 +10182,7 @@ packages:
dev: false
name: '@rush-temp/event-hubs'
resolution:
- integrity: sha512-ij9RrsZTCY20A1azLf+CQ2/sC3oz8FwzxNv+of934zmASFqNxTqwylat1zRPj/zQ+5WHpfq5BoNmXpTgshzzRg==
+ integrity: sha512-TMdz6KTaNgEH8tAeEntd48I6dIV5yZbYrDxgW0cLV4eR+agvLKFa3XPx6H8iiwF/Jyn/eMKu56iLP/nwnxUVFw==
tarball: 'file:projects/event-hubs.tgz'
version: 0.0.0
'file:projects/event-processor-host.tgz':
@@ -10225,7 +10239,7 @@ packages:
dev: false
name: '@rush-temp/event-processor-host'
resolution:
- integrity: sha512-7/7AixJ0rDkzWSL4l0i3DbufYn8MgykQREWpVm5pTMaNi6HqQZGwCMSyCiVqRBSheWcn+shtaCNlRTAOA7nw2g==
+ integrity: sha512-c/puCLm/oWyCMxHOUHQ3ZszUEyoBpeoPBnpMiLYhdqGIl1cXBYxOzXJaN3MN4EgzztvbG/kUtOZt2h+tAoP7NA==
tarball: 'file:projects/event-processor-host.tgz'
version: 0.0.0
'file:projects/identity.tgz':
@@ -10277,7 +10291,7 @@ packages:
dev: false
name: '@rush-temp/identity'
resolution:
- integrity: sha512-zBuzYgT0J+3USAuHuU/4bUU5W++5Fsp3U747YIjXOR2tSbiX9QVv9DKUgtJD8lQm1jdvZduzwIhMBTJd68lk8Q==
+ integrity: sha512-aVvXmz3WeYJqmGGqVVfQOL30VQi09y/31viVZ6CfXy0MzUtrSmCwX9sfKDR3RnbvjtULGivZvJp7OY5Pwv2EPw==
tarball: 'file:projects/identity.tgz'
version: 0.0.0
'file:projects/keyvault-certificates.tgz':
@@ -10345,7 +10359,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-certificates'
resolution:
- integrity: sha512-RGwQyIALGePUl3hTCS4uu9E8RIKUpOvv/MYZDgwfQOo5ZvGrWN1HkDd0OaafAnfDFFM7aalGt1JnZBh9RCGFFQ==
+ integrity: sha512-8YOlO6QG3YkvNcFPbJL8nbroNLJpDcxGdBXukPxgNcw74wWIEGACMb77FRZbsviTl4Fm882+yJTVl6q4xNZHww==
tarball: 'file:projects/keyvault-certificates.tgz'
version: 0.0.0
'file:projects/keyvault-keys.tgz':
@@ -10414,7 +10428,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-keys'
resolution:
- integrity: sha512-jxZzHCbuJfaD39hHIzP3fyE2eidbNN7iXcHNptZk8qt+rp9E0d5MgpGrgdFbqVPFhUl5AqAbrRfUlOdYvvHR2Q==
+ integrity: sha512-DjVpS2TWUSIbp4acVsnz9mklUM/foZjjrVIs1Vo86HzF28igiFanj74BoWmiVzfubanC9+KwdEjQQ8U9CsWIXA==
tarball: 'file:projects/keyvault-keys.tgz'
version: 0.0.0
'file:projects/keyvault-secrets.tgz':
@@ -10481,7 +10495,7 @@ packages:
dev: false
name: '@rush-temp/keyvault-secrets'
resolution:
- integrity: sha512-KKRhMhKadWkVD6YNbv0oc5hUwWXePFshonrEk8sPqWCJy2wmxDQcByw5kXfvu4ff1pBu5YU2/WGJea+78lvg4Q==
+ integrity: sha512-Sml9gYtRdzXKZ/tiXOrFJebP7VayBC8dZmiCGWeypwyeME7KfNUu7RS1/SoVL3FICnoG9DGBOhuW4Hitmzx8yA==
tarball: 'file:projects/keyvault-secrets.tgz'
version: 0.0.0
'file:projects/service-bus.tgz':
@@ -10558,7 +10572,7 @@ packages:
dev: false
name: '@rush-temp/service-bus'
resolution:
- integrity: sha512-afJ8wnIxTmCpYPotxye4XswCWAlNTlXQIU8MJh7Tf0YRxLZXtUZSndz8Z9LjXcKyOJst5u1Cub0GTONIs0ZS9Q==
+ integrity: sha512-FwSxg/1Ynbnbyna5nzRpMryYhlKwPQagswhSvdmTEBT14skEz2wxcO1ZCalQL3yUHxtLqNTbwSEWs/m1iwVRbQ==
tarball: 'file:projects/service-bus.tgz'
version: 0.0.0
'file:projects/storage-blob.tgz':
@@ -10628,7 +10642,7 @@ packages:
dev: false
name: '@rush-temp/storage-blob'
resolution:
- integrity: sha512-hxiIH/jGLBocF8uWR2vDOwx7w+bFJvNGW/k+Ds1FDZxQatiZLLgQvutwzTLc2oKNBxUrXKssQGIG7e7oHl08+A==
+ integrity: sha512-kn2byrHTxQIDWXEwQS14S523e/NgrUkWWdtk/EyQb11NWHZJGzqWOR7soCXQgZfmWIGgjKfPFM/Xwrqf3vECMA==
tarball: 'file:projects/storage-blob.tgz'
version: 0.0.0
'file:projects/storage-file.tgz':
@@ -10698,7 +10712,7 @@ packages:
dev: false
name: '@rush-temp/storage-file'
resolution:
- integrity: sha512-Om0AwUAkaQ2MXCiO3Tbr9o7dAHIAORNMEGG/vHmjsQT1TcMnQtxkj6W3Vi12arC7Befc2eRBK6qCx4dmAWUc0g==
+ integrity: sha512-oj8bSAecZKtBXtPot5VqiCMqKQx5sQIjbNgGg1IyGwsdfgeTJKgMYOJpPK8MSMqvYK9EZudw3jkeFOIA378j1w==
tarball: 'file:projects/storage-file.tgz'
version: 0.0.0
'file:projects/storage-queue.tgz':
@@ -10767,7 +10781,7 @@ packages:
dev: false
name: '@rush-temp/storage-queue'
resolution:
- integrity: sha512-emEeCElwVRQ9uXdWyORLRGge2NTOWtKaZ1UNKrzC8EOToJuJapl7SG6SxCpvXuLOxVyhMXLQCOx+IRAWLi9XlA==
+ integrity: sha512-73zOl+s2xrm/5cQo1kRzdupjV451qFHM1SMEHnZMNG5uX8NlLPFy3A5qPcj5dGcrBwdA33JdZKYhMkLCLk48fQ==
tarball: 'file:projects/storage-queue.tgz'
version: 0.0.0
'file:projects/template.tgz':
@@ -10817,7 +10831,7 @@ packages:
dev: false
name: '@rush-temp/template'
resolution:
- integrity: sha512-CT4vFqfIOTwrzlk0gsSp71MNMAgA6cbt4Sa3BkCVhr81N6oXnvIoGIMCtzSerpvI8j1auYc0yM4rKldxNSFTqA==
+ integrity: sha512-gMekh0FZCuSys6TamLN8h1nt7hJ/ah6Ryu7nR26ETWqXzxQnsnZrq27JRWgA2x5QFv5XBXXZJayhpUId9K+8bA==
tarball: 'file:projects/template.tgz'
version: 0.0.0
'file:projects/testhub.tgz':
@@ -10838,9 +10852,10 @@ packages:
dev: false
name: '@rush-temp/testhub'
resolution:
- integrity: sha512-VxrbDXfuJ6Nz4rm0DHlJ+0sMk4RMKRflIyu7WxXLZGBpri9KLivFyNA0TWfZBifpdy3T1kVXyLOccskpzczDvA==
+ integrity: sha512-5P+X3IwgFa9rlq+jbtHxWVZWF1kDmRZwV8jQfHMYV5CLsJjXKw1yISIRSFmCmuXDdHYEnxO1bnRGi34QeZ+63g==
tarball: 'file:projects/testhub.tgz'
version: 0.0.0
+registry: ''
specifiers:
'@azure/amqp-common': 1.0.0-preview.6
'@azure/arm-servicebus': ^3.2.0
@@ -11008,6 +11023,7 @@ specifiers:
tunnel: ^0.0.6
typescript: ^3.2.2
uglify-js: ^3.4.9
+ underscore: 1.4.x
url: ^0.11.0
util: ^0.12.1
uuid: ^3.3.2
@@ -11017,5 +11033,6 @@ specifiers:
ws: ^7.1.1
xhr-mock: ^2.4.1
xml2js: ^0.4.19
+ xmlbuilder: 0.4.3
yargs: ^13.0.0
yarn: ^1.6.0
diff --git a/sdk/core/core-http/lib/atomResourceSerializerBase.ts b/sdk/core/core-http/lib/atomResourceSerializerBase.ts
new file mode 100644
index 000000000000..33b8abaac5dd
--- /dev/null
+++ b/sdk/core/core-http/lib/atomResourceSerializerBase.ts
@@ -0,0 +1,174 @@
+//
+// Copyright (c) Microsoft and contributors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { stringIsEmpty, stringStartsWith } from "./util/utils";
+import { Constants } from "./util/constants";
+import { AtomHandler } from "./util/atomHandler";
+import { each, isUndefined, isArray, isObject, isDate } from "./util/utils";
+const url = require("url");
+const xmlbuilder = require("xmlbuilder");
+
+const atomHandler = new AtomHandler();
+
+export class AtomResourceSerializerBase {
+ static setName(entry: any, nameProperty: any): any {
+ var parsedUrl: any = url.parse(entry[Constants.ATOM_METADATA_MARKER].id);
+ var parts = parsedUrl.pathname!.split("/");
+
+ for (var i = 0; i * 2 < parts.length - 1; i++) {
+ entry[nameProperty[i]] = parts[i * 2 + 1];
+ }
+ }
+
+ static _serialize(resourceName: any, resource: any, properties: any): any {
+ var content: any = {};
+ content[resourceName] = {
+ $: {
+ xmlns: "http://schemas.microsoft.com/netservices/2010/10/servicebus/connect"
+ }
+ };
+
+ if (resource) {
+ // Sort properties according to what is allowed by the service
+ each(properties, function(property: any): any {
+ if (!isUndefined(resource[property])) {
+ content[resourceName][property] = resource[property];
+ }
+ });
+ }
+
+ return atomHandler.serializeEntry(content);
+ }
+
+ static _parse(nameProperty: any, xml: any): any {
+ var result = atomHandler.parse(xml);
+
+ if (!result) {
+ return undefined;
+ }
+ if (isArray(result)) {
+ each(result, function(entry: any): any {
+ AtomResourceSerializerBase.setName(entry, nameProperty);
+ });
+ } else {
+ AtomResourceSerializerBase.setName(result, nameProperty);
+ }
+ return result;
+ }
+
+ static serializeEntry(content: any, namespaces?: any, properties?: any): any {
+ var doc = xmlbuilder.create("entry").att("xmlns", "http://www.w3.org/2005/Atom");
+
+ each(namespaces, function(namespace: any): any {
+ doc = doc.att("xmlns:" + namespace.key, namespace.url);
+ });
+
+ if (properties) {
+ Object.keys(properties).forEach(function(property) {
+ doc = doc.ele(property, properties[property]).up();
+ });
+ }
+
+ doc = doc.ele("updated", new Date().toISOString()).up();
+
+ content[Constants.XML_METADATA_MARKER] = { type: "application/xml" };
+
+ doc = AtomResourceSerializerBase._writeElementValue(doc, "content", content);
+
+ return doc.doc().toString();
+ }
+
+ static _writeElementValue(parentElement: any, name: any, value: any): any {
+ var ignored = false;
+ var propertyTagName = name;
+
+ if (!stringIsEmpty(value) && isObject(value) && !isDate(value)) {
+ if (Array.isArray(value) && value.length > 0) {
+ // Example:
+ // JSON: element: [ { property1: 'hi there' }, { property2: 'hello there' } ]
+ // XML: hi therehello there
+
+ Object.keys(value).forEach(function(i: any) {
+ parentElement = AtomResourceSerializerBase._writeElementValue(
+ parentElement,
+ name,
+ value[i]
+ );
+ });
+
+ // For an array no element was actually added at this level, so skip uping level.
+ ignored = true;
+ } else if (
+ value[Constants.XML_METADATA_MARKER] !== undefined &&
+ value[Constants.XML_VALUE_MARKER] !== undefined
+ ) {
+ // Example:
+ // JSON: element: { '$': { 'm:type' = 'Edm.String' }, '_': 'hi there' }
+ // XML: hi there
+
+ parentElement = parentElement.ele(propertyTagName);
+ if (!stringIsEmpty(value[Constants.XML_VALUE_MARKER])) {
+ if (stringStartsWith(value[Constants.XML_VALUE_MARKER], "hi therehello there
+
+ parentElement = parentElement.ele(propertyTagName);
+ for (var propertyName in value) {
+ if (
+ propertyName !== Constants.XML_METADATA_MARKER &&
+ value.hasOwnProperty(propertyName)
+ ) {
+ parentElement = AtomResourceSerializerBase._writeElementValue(
+ parentElement,
+ propertyName,
+ value[propertyName]
+ );
+ }
+ }
+ }
+ } else {
+ parentElement = parentElement.ele(propertyTagName);
+ if (!stringIsEmpty(value)) {
+ if (stringStartsWith(value.toString().trim(), "} The signed request object.
+ */
+ signRequest(webResource: WebResource): any {
+ if (!webResource.headers) webResource.headers = new HttpHeaders();
+
+ var targetUri = encodeURIComponent(webResource.url.toLowerCase()).toLowerCase();
+
+ let date = new Date();
+ date.setMinutes(date.getMinutes() + 5);
+ var expirationDate = Math.round(date.valueOf() / 1000);
+ var signature = this._generateSignature(targetUri, expirationDate);
+ webResource.headers.set(
+ HeaderConstants.AUTHORIZATION,
+ util.format(
+ "SharedAccessSignature sig=%s&se=%s&skn=%s&sr=%s",
+ signature,
+ expirationDate,
+ this.keyName,
+ targetUri
+ )
+ );
+
+ return Promise.resolve(webResource);
+ }
+}
diff --git a/sdk/core/core-http/lib/httpOperationResponse.ts b/sdk/core/core-http/lib/httpOperationResponse.ts
index 9b30056b32df..26d325f712f5 100644
--- a/sdk/core/core-http/lib/httpOperationResponse.ts
+++ b/sdk/core/core-http/lib/httpOperationResponse.ts
@@ -29,7 +29,7 @@ declare global {
* Stub declaration of the browser-only Blob type.
* Full type information can be obtained by including "lib": ["dom"] in tsconfig.json.
*/
- interface Blob { }
+ interface Blob {}
}
/**
@@ -52,6 +52,11 @@ export interface HttpOperationResponse extends HttpResponse {
*/
parsedBody?: any;
+ /**
+ * The error information in JSON or XML
+ */
+ errorBody?: any;
+
/**
* BROWSER ONLY
*
diff --git a/sdk/core/core-http/lib/policies/serviceBusAtomSerializationPolicy.ts b/sdk/core/core-http/lib/policies/serviceBusAtomSerializationPolicy.ts
new file mode 100644
index 000000000000..d76c40faa14e
--- /dev/null
+++ b/sdk/core/core-http/lib/policies/serviceBusAtomSerializationPolicy.ts
@@ -0,0 +1,198 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+import { HttpOperationResponse } from "../httpOperationResponse";
+import { WebResource } from "../webResource";
+import {
+ BaseRequestPolicy,
+ RequestPolicy,
+ RequestPolicyFactory,
+ RequestPolicyOptions
+} from "./requestPolicy";
+import { Constants } from "../util/constants";
+import { parseAtomXML, parseStringError } from "../util/xml";
+import { isString, extend } from "../util/utils";
+import { Buffer } from "buffer";
+import { ResourceSerializer } from "../resourceSerializer";
+
+/**
+ * Create a new serialization RequestPolicyCreator that will serialize/deserialize
+ * HTTP request bodies as they pass through the HTTP pipeline.
+ */
+export function serviceBusAtomSerializationPolicy(): RequestPolicyFactory {
+ return {
+ create: (nextPolicy: RequestPolicy, options: RequestPolicyOptions) => {
+ return new ServiceBusAtomSerializationPolicy(nextPolicy, options);
+ }
+ };
+}
+
+/**
+ * A RequestPolicy that will
+ * - serialize HTTP requests with input in JSON to ATOM based XML requests, and
+ * - deserialize the ATOM based XML responses as they pass through the HTTP pipeline.
+ */
+export class ServiceBusAtomSerializationPolicy extends BaseRequestPolicy {
+ constructor(nextPolicy: RequestPolicy, options: RequestPolicyOptions) {
+ super(nextPolicy, options);
+ }
+
+ public async sendRequest(request: WebResource): Promise {
+ let shouldParseResponse = false;
+ let serializer: ResourceSerializer;
+ if (request.atomXmlOperationSpec) {
+ serializer = request.atomXmlOperationSpec.serializer;
+ shouldParseResponse = request.atomXmlOperationSpec.shouldParseResponse;
+ if (request.body) {
+ request.body = serializer.serialize(JSON.parse(request.body));
+ }
+ }
+
+ return this._nextPolicy.sendRequest(request).then((response: HttpOperationResponse) => {
+ const parsedResponse: HttpOperationResponse = this.parseResponse(response);
+
+ // Construct response with 'result' to be backward compatibile
+ const responseInCustomJson: any = {
+ error: parsedResponse.errorBody,
+ response: parsedResponse.parsedBody,
+ result: undefined
+ };
+ responseInCustomJson.result =
+ shouldParseResponse && serializer
+ ? serializer.parse(responseInCustomJson.response)
+ : undefined;
+
+ response.parsedBody = responseInCustomJson;
+ return response;
+ });
+ }
+
+ /**
+ * Process the response.
+ * @ignore
+ *
+ * @param {WebResource} webResource The web resource that made the request.
+ * @param {Response} response The response object.
+ * @return The normalized responseObject.
+ */
+ private parseResponse(response: HttpOperationResponse): HttpOperationResponse {
+ const parsedResponse: HttpOperationResponse = this._parseXmlResponse(response);
+
+ if (response.status >= 200 && response.status < 300) {
+ response.errorBody = undefined;
+ return response;
+ }
+
+ const HttpResponseCodes: any = Constants.HttpResponseCodes;
+
+ if (!parsedResponse.errorBody) {
+ var code = Object.keys(HttpResponseCodes).filter(function(name: any): any {
+ if (HttpResponseCodes[name] === response.status) {
+ return name;
+ }
+ });
+
+ parsedResponse.errorBody = { error: { code: code[0] } };
+ }
+ var normalizedError = this._normalizeError(parsedResponse.errorBody, response);
+ parsedResponse.errorBody = normalizedError;
+ return parsedResponse;
+ }
+
+ private _parseXmlResponse(response: HttpOperationResponse): HttpOperationResponse {
+ const parsedResponse = response;
+ try {
+ if (response.bodyAsText && Buffer.byteLength(response.bodyAsText.toString()) > 0) {
+ parsedResponse.parsedBody = parseAtomXML(response.bodyAsText);
+ }
+ } catch (e) {
+ parsedResponse.errorBody = { error: e };
+ }
+ return this.parseUncategorizedResponse(parsedResponse);
+ }
+
+ private parseUncategorizedResponse(response: HttpOperationResponse): HttpOperationResponse {
+ const parsedResponse = response;
+ try {
+ // Start by assuming XML
+ parsedResponse.parsedBody = parseAtomXML(response.bodyAsText);
+ } catch (e) {
+ // Try string if XML failed to parse a valid error xml
+ try {
+ parsedResponse.parsedBody = parseStringError(response.bodyAsText);
+ } catch (e) {
+ // Do nothing
+ }
+ }
+ return parsedResponse;
+ }
+
+ _normalizeError(error: any, response: HttpOperationResponse): any {
+ if (isString(error)) {
+ return new Error(error);
+ } else if (error) {
+ var normalizedError: any = {};
+
+ var odataErrorFormat = !!error["odata.error"];
+ var errorProperties = error.Error || error.error || error["odata.error"] || error;
+ if (odataErrorFormat) {
+ for (var property in errorProperties) {
+ if (errorProperties.hasOwnProperty(property)) {
+ var value = null;
+ if (
+ property === Constants.ODATA_ERROR_MESSAGE &&
+ !isString(errorProperties[Constants.ODATA_ERROR_MESSAGE])
+ ) {
+ if (
+ errorProperties[Constants.ODATA_ERROR_MESSAGE][Constants.ODATA_ERROR_MESSAGE_VALUE]
+ ) {
+ value =
+ errorProperties[Constants.ODATA_ERROR_MESSAGE][
+ Constants.ODATA_ERROR_MESSAGE_VALUE
+ ];
+ } else {
+ value = "missing value in the message property of the odata error format";
+ }
+ } else {
+ value = errorProperties[property];
+ }
+ normalizedError[property.toLowerCase()] = value;
+ }
+ }
+ } else {
+ for (var property in errorProperties) {
+ if (errorProperties.hasOwnProperty(property)) {
+ var value = null;
+ if (property !== "$") {
+ if (errorProperties[property] && errorProperties[property]["_"]) {
+ value = errorProperties[property]["_"];
+ } else {
+ value = errorProperties[property];
+ }
+ normalizedError[property.toLowerCase()] = value;
+ }
+ }
+ }
+ }
+ var errorMessage = normalizedError.code;
+ if (normalizedError.detail) {
+ errorMessage += " - " + normalizedError.detail;
+ }
+
+ if (response) {
+ if (response.status) {
+ normalizedError.statusCode = response.status;
+ }
+
+ if (response.headers && response.headers.get("x-ms-request-id")) {
+ normalizedError.requestId = response.headers.get("x-ms-request-id");
+ }
+ }
+
+ var errorObject = new Error(errorMessage);
+ return extend(errorObject, normalizedError);
+ }
+
+ return undefined;
+ }
+}
diff --git a/sdk/core/core-http/lib/resourceSerializer.ts b/sdk/core/core-http/lib/resourceSerializer.ts
new file mode 100644
index 000000000000..cccdd1e68922
--- /dev/null
+++ b/sdk/core/core-http/lib/resourceSerializer.ts
@@ -0,0 +1,21 @@
+//
+// Copyright (c) Microsoft and contributors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+export abstract class ResourceSerializer {
+ abstract serialize(resource: any): any;
+
+ abstract parse(xml: any): any;
+}
diff --git a/sdk/core/core-http/lib/util/atomHandler.ts b/sdk/core/core-http/lib/util/atomHandler.ts
new file mode 100644
index 000000000000..6575932d0448
--- /dev/null
+++ b/sdk/core/core-http/lib/util/atomHandler.ts
@@ -0,0 +1,199 @@
+//
+// Copyright (c) Microsoft and contributors. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+import { Constants } from "./constants";
+import util from "util";
+import { isArray, each, stringIsEmpty, stringStartsWith, isDate, isObject } from "./utils";
+const xmlbuilder = require("xmlbuilder");
+
+export class AtomHandler {
+ parseEntryResult(entry: any): any {
+ var contentElementName = Object.keys(entry.content).filter(function(key) {
+ return key !== Constants.XML_METADATA_MARKER;
+ })[0];
+
+ delete entry.content[contentElementName][Constants.XML_METADATA_MARKER];
+ var result = entry.content[contentElementName];
+
+ if (result) {
+ if (entry[Constants.XML_METADATA_MARKER]) {
+ result[Constants.ATOM_METADATA_MARKER] = entry[Constants.XML_METADATA_MARKER];
+ } else {
+ result[Constants.ATOM_METADATA_MARKER] = {};
+ }
+
+ result[Constants.ATOM_METADATA_MARKER]["ContentRootElement"] = contentElementName;
+
+ for (var property in entry) {
+ if (property !== "content" && property !== Constants.XML_METADATA_MARKER) {
+ result[Constants.ATOM_METADATA_MARKER][property] = entry[property];
+ }
+ }
+ }
+
+ return result;
+ }
+
+ parseFeedResult(feed: any): any {
+ var result = [];
+ var self = this;
+ if (feed.entry) {
+ if (isArray(feed.entry)) {
+ each(feed.entry, function(entry: any) {
+ result.push(self.parseEntryResult(entry));
+ });
+ } else {
+ result.push(self.parseEntryResult(feed.entry));
+ }
+ }
+ return result;
+ }
+
+ parse(xml: any): any {
+ var self = this;
+ if (!xml) {
+ return;
+ }
+
+ if (xml.feed) {
+ return self.parseFeedResult(xml.feed);
+ }
+
+ if (xml.entry) {
+ return self.parseEntryResult(xml.entry);
+ }
+
+ throw new Error("Unrecognized result " + util.inspect(xml));
+ }
+
+ /**
+ * @param {object} content The content payload as it is to be serialized. It should include any root node(s).
+ * @param {array} namespaces An array of top level namespaces to be defined.
+ */
+ serializeEntry(content: any, namespaces?: any, properties?: any): any {
+ var doc = xmlbuilder.create();
+
+ doc = doc
+ .begin("entry", { version: "1.0", encoding: "utf-8", standalone: "yes" })
+ .att("xmlns", "http://www.w3.org/2005/Atom");
+
+ each(namespaces, function(namespace: any): any {
+ doc = doc.att("xmlns:" + namespace.key, namespace.url);
+ });
+
+ if (properties) {
+ Object.keys(properties).forEach(function(property) {
+ doc = doc.ele(property, properties[property]).up();
+ });
+ }
+
+ doc = doc.ele("updated", new Date().toISOString()).up();
+
+ content[Constants.XML_METADATA_MARKER] = { type: "application/xml" };
+
+ doc = this._writeElementValue(doc, "content", content);
+
+ return doc.doc().toString();
+ }
+
+ /*
+ * Writes a single property for an entry or complex type.
+ *
+ * @param {object} parentElement Parent DOM element under which the property should be added.
+ * @param {string} name Property name.
+ * @param {object} value Property value.
+ * @return {object} The current DOM element.
+ *
+ * {Deprecated}
+ */
+ _writeElementValue(parentElement: any, name: any, value: any): any {
+ var self = this;
+ var ignored = false;
+ var propertyTagName = name;
+
+ if (!stringIsEmpty(value) && isObject(value) && !isDate(value)) {
+ if (Array.isArray(value) && value.length > 0) {
+ // Example:
+ // JSON: element: [ { property1: 'hi there' }, { property2: 'hello there' } ]
+ // XML: hi therehello there
+
+ Object.keys(value).forEach(function(i: any) {
+ parentElement = self._writeElementValue(parentElement, name, value[i]);
+ });
+
+ // For an array no element was actually added at this level, so skip uping level.
+ ignored = true;
+ } else if (
+ value[Constants.XML_METADATA_MARKER] !== undefined &&
+ value[Constants.XML_VALUE_MARKER] !== undefined
+ ) {
+ // Example:
+ // JSON: element: { '$': { 'm:type' = 'Edm.String' }, '_': 'hi there' }
+ // XML: hi there
+
+ parentElement = parentElement.ele(propertyTagName);
+ if (!stringIsEmpty(value[Constants.XML_VALUE_MARKER])) {
+ if (stringStartsWith(value[Constants.XML_VALUE_MARKER], "hi therehello there
+
+ parentElement = parentElement.ele(propertyTagName);
+ for (var propertyName in value) {
+ if (
+ propertyName !== Constants.XML_METADATA_MARKER &&
+ value.hasOwnProperty(propertyName)
+ ) {
+ parentElement = self._writeElementValue(
+ parentElement,
+ propertyName,
+ value[propertyName]
+ );
+ }
+ }
+ }
+ } else {
+ parentElement = parentElement.ele(propertyTagName);
+ if (!stringIsEmpty(value)) {
+ if (stringStartsWith(value.toString().trim(), ", kickst
*
* @returns {object} Returns the merged target object.
*/
-export function mergeObjects(source: { [key: string]: any; }, target: { [key: string]: any; }) {
+export function mergeObjects(source: { [key: string]: any }, target: { [key: string]: any }) {
Object.keys(source).forEach((key) => {
target[key] = source[key];
});
@@ -168,7 +180,12 @@ export interface ServiceCallback {
* @param {WebResource} [request] The raw/actual request sent to the server if an error did not occur.
* @param {HttpOperationResponse} [response] The raw/actual response from the server if an error did not occur.
*/
- (err: Error | RestError | null, result?: TResult, request?: WebResource, response?: HttpOperationResponse): void;
+ (
+ err: Error | RestError | null,
+ result?: TResult,
+ request?: WebResource,
+ response?: HttpOperationResponse
+ ): void;
}
/**
@@ -182,11 +199,14 @@ export function promiseToCallback(promise: Promise): Function {
throw new Error("The provided input is not a Promise.");
}
return (cb: Function): void => {
- promise.then((data: any) => {
- cb(undefined, data);
- }, (err: Error) => {
- cb(err);
- });
+ promise.then(
+ (data: any) => {
+ cb(undefined, data);
+ },
+ (err: Error) => {
+ cb(err);
+ }
+ );
};
}
@@ -200,11 +220,14 @@ export function promiseToServiceCallback(promise: Promise): void => {
- promise.then((data: HttpOperationResponse) => {
- process.nextTick(cb, undefined, data.parsedBody as T, data.request, data);
- }, (err: Error) => {
- process.nextTick(cb, err);
- });
+ promise.then(
+ (data: HttpOperationResponse) => {
+ process.nextTick(cb, undefined, data.parsedBody as T, data.request, data);
+ },
+ (err: Error) => {
+ process.nextTick(cb, err);
+ }
+ );
};
}
@@ -221,8 +244,8 @@ export function prepareXMLRootList(obj: any, elementName: string) {
* @param {Array