Merge pull request #42 from Azure/daschult/lro

Restructure LRO polling into different polling strategies
This commit is contained in:
Dan Schulte 2018-07-24 13:59:25 -07:00 коммит произвёл GitHub
Родитель 558520433b 12ea0df77e
Коммит 5a65b73d31
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 547 добавлений и 416 удалений

Просмотреть файл

@ -1,14 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as msRest from "ms-rest-js";
import PollingState from "./pollingState";
import Constants, { LongRunningOperationStates as LroStates } from "./util/constants";
import { HttpOperationResponse, RequestOptionsBase, RequestPrepareOptions, ServiceClient, ServiceClientCredentials, ServiceClientOptions, WebResource } from "ms-rest-js";
import { createLROPollStrategy, LROPollStrategy } from "./lroPollStrategy";
import * as Constants from "./util/constants";
/**
* Options to be provided while creating the client.
*/
export interface AzureServiceClientOptions extends msRest.ServiceClientOptions {
export interface AzureServiceClientOptions extends ServiceClientOptions {
/**
* @property {string} [options.acceptLanguage] - Gets or sets the preferred language for the response. Default value is: "en-US".
*/
@ -30,11 +30,14 @@ export interface AzureServiceClientOptions extends msRest.ServiceClientOptions {
* UserTokenCredentials object used for authentication.
* @param {AzureServiceClientOptions} options - The parameter options used by AzureServiceClient
*/
export class AzureServiceClient extends msRest.ServiceClient {
acceptLanguage: string = Constants.DEFAULT_LANGUAGE;
longRunningOperationRetryTimeout = 30;
export class AzureServiceClient extends ServiceClient {
public acceptLanguage: string = Constants.DEFAULT_LANGUAGE;
/**
* The retry timeout in seconds for Long Running Operations. Default value is 30.
*/
public longRunningOperationRetryTimeout?: number;
constructor(credentials: msRest.ServiceClientCredentials, options?: AzureServiceClientOptions) {
constructor(credentials: ServiceClientCredentials, options?: AzureServiceClientOptions) {
super(credentials, options = updateOptionsWithDefaultValues(options));
if (options.acceptLanguage != undefined) {
@ -51,57 +54,30 @@ export class AzureServiceClient extends msRest.ServiceClient {
/**
* Provides a mechanism to make a request that will poll and provide the final result.
* @param {msRest.RequestPrepareOptions|msRest.WebResource} request - The request object
* @param {msRest.RequestOptionsBase} [options] Additional options to be sent while making the request
* @param {AzureRequestOptionsBase} [options] Additional options to be sent while making the request
* @returns {Promise<msRest.HttpOperationResponse>} The HttpOperationResponse containing the final polling request, response and the responseBody.
*/
sendLongRunningRequest(request: msRest.RequestPrepareOptions | msRest.WebResource, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
return this.sendRequest(request).then(response => this.getLongRunningOperationResult(response, options));
async sendLongRunningRequest(request: RequestPrepareOptions | WebResource, options?: RequestOptionsBase): Promise<HttpOperationResponse> {
return this.sendRequest(request).then((response: HttpOperationResponse) => this.getLongRunningOperationResult(response, options));
}
/**
* Poll Azure long running PUT, PATCH, POST or DELETE operations.
* @param {msRest.HttpOperationResponse} resultOfInitialRequest - result/response of the initial request which is a part of the asynchronous polling operation.
* @param {msRest.RequestOptionsBase} [options] - custom request options.
* @returns {Promise<msRest.HttpOperationResponse>} result - The final response after polling is complete.
* @param {HttpOperationResponse} initialResponse - response of the initial request which is a part of the asynchronous polling operation.
* @param {AzureRequestOptionsBase} [options] - custom request options.
* @returns {Promise<HttpOperationResponse>} The final response after polling is complete.
*/
async getLongRunningOperationResult(resultOfInitialRequest: msRest.HttpOperationResponse, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
const initialRequestMethod: msRest.HttpMethods = resultOfInitialRequest.request.method;
if (checkResponseStatusCodeFailed(resultOfInitialRequest)) {
throw new Error(`Unexpected polling status code from long running operation ` +
`"${resultOfInitialRequest.status}" for method "${initialRequestMethod}".`);
}
const pollingState = new PollingState(resultOfInitialRequest, this.longRunningOperationRetryTimeout);
pollingState.optionsOfInitialRequest = options as msRest.RequestOptionsBase;
const resourceUrl: string = resultOfInitialRequest.request.url;
while (terminalStates.indexOf(pollingState.status) === -1) {
await msRest.delay(pollingState.getTimeout());
if (pollingState.azureAsyncOperationHeaderLink) {
await updateStateFromAzureAsyncOperationHeader(this, pollingState);
} else if (pollingState.locationHeaderLink) {
await updateStateFromLocationHeader(this, initialRequestMethod, pollingState);
} else if (initialRequestMethod === "PUT") {
await updateStateFromGetResourceOperation(this, resourceUrl, pollingState);
} else {
throw new Error("Location header is missing from long running operation.");
}
}
if (pollingState.status === "Succeeded") {
if ((pollingState.azureAsyncOperationHeaderLink || !pollingState.resource) &&
(initialRequestMethod === "PUT" || initialRequestMethod === "PATCH")) {
await updateStateFromGetResourceOperation(this, resourceUrl, pollingState);
}
return pollingState.getOperationResponse();
async getLongRunningOperationResult(initialResponse: HttpOperationResponse, options?: RequestOptionsBase): Promise<HttpOperationResponse> {
const lroPollStrategy: LROPollStrategy = createLROPollStrategy(initialResponse, this, options);
const succeeded: boolean = await lroPollStrategy.pollUntilFinished();
if (succeeded) {
return lroPollStrategy.getOperationResponse();
} else {
throw pollingState.getRestError();
throw lroPollStrategy.getRestError();
}
}
}
const terminalStates: LroStates[] = ["Succeeded", "Failed", "Canceled"];
export function updateOptionsWithDefaultValues(options?: AzureServiceClientOptions): AzureServiceClientOptions {
if (!options) {
options = {};
@ -110,133 +86,4 @@ export function updateOptionsWithDefaultValues(options?: AzureServiceClientOptio
options.generateClientRequestIdHeader = true;
}
return options;
}
/**
* Verified whether an unexpected polling status code for long running operation was received for the response of the initial request.
* @param {msRest.HttpOperationResponse} initialResponse - Response to the initial request that was sent as a part of the asynchronous operation.
*/
export function checkResponseStatusCodeFailed(initialResponse: msRest.HttpOperationResponse): boolean {
const statusCode = initialResponse.status;
const method = initialResponse.request.method;
if (statusCode === 200 || statusCode === 202 ||
(statusCode === 201 && method === "PUT") ||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
return false;
} else {
return true;
}
}
/**
* Retrieve operation status by polling from "azure-asyncoperation" header.
* @param {PollingState} pollingState - The object to persist current operation state.
* @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
*/
export function updateStateFromAzureAsyncOperationHeader(client: AzureServiceClient, pollingState: PollingState): Promise<void> {
return getStatus(client, pollingState.azureAsyncOperationHeaderLink as string, pollingState.optionsOfInitialRequest).then(result => {
const parsedResponse = result.parsedBody as { [key: string]: any };
if (!parsedResponse) {
throw new Error("The response from long running operation does not contain a body.");
} else if (!parsedResponse.status) {
throw new Error(`The response "${result.bodyAsText}" from long running operation does not contain the status property.`);
}
pollingState.status = parsedResponse.status;
pollingState.error = parsedResponse.error;
pollingState.updateResponse(result);
pollingState.request = result.request;
pollingState.resource = undefined;
pollingState.resource = result.parsedBody;
});
}
/**
* Retrieve PUT operation status by polling from "location" header.
* @param {string} method - The HTTP method.
* @param {PollingState} pollingState - The object to persist current operation state.
*/
export function updateStateFromLocationHeader(client: AzureServiceClient, method: msRest.HttpMethods, pollingState: PollingState): Promise<void> {
return getStatus(client, pollingState.locationHeaderLink as string, pollingState.optionsOfInitialRequest).then(result => {
const parsedResponse = result.parsedBody as { [key: string]: any };
pollingState.updateResponse(result);
pollingState.request = result.request;
const statusCode = result.status;
if (statusCode === 202) {
pollingState.status = "InProgress";
} else if (statusCode === 200 ||
(statusCode === 201 && (method === "PUT" || method === "PATCH")) ||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
pollingState.status = "Succeeded";
pollingState.resource = parsedResponse;
// we might not throw an error, but initialize here just in case.
pollingState.error = new msRest.RestError(`Long running operation failed with status "${pollingState.status}".`);
pollingState.error.code = pollingState.status;
} else {
throw new Error(`The response with status code ${statusCode} from polling for ` +
`long running operation url "${pollingState.locationHeaderLink}" is not valid.`);
}
});
}
/**
* Polling for resource status.
* @param {string} resourceUrl - The url of resource.
* @param {PollingState} pollingState - The object to persist current operation state.
*/
export function updateStateFromGetResourceOperation(client: AzureServiceClient, resourceUrl: string, pollingState: PollingState): Promise<void> {
return getStatus(client, resourceUrl, pollingState.optionsOfInitialRequest).then(result => {
if (!result.parsedBody) {
throw new Error("The response from long running operation does not contain a body.");
}
const parsedResponse = result.parsedBody as { [key: string]: any };
pollingState.status = "Succeeded";
if (parsedResponse && parsedResponse.properties && parsedResponse.properties.provisioningState) {
pollingState.status = parsedResponse.properties.provisioningState;
}
pollingState.updateResponse(result);
pollingState.request = result.request;
pollingState.resource = parsedResponse;
// we might not throw an error, but initialize here just in case.
pollingState.error = new msRest.RestError(`Long running operation failed with status "${pollingState.status}".`);
pollingState.error.code = pollingState.status;
});
}
/**
* Retrieves operation status by querying the operation URL.
* @param {string} operationUrl - URL used to poll operation result.
* @param {object} options - Options that can be set on the request object
*/
export function getStatus(client: AzureServiceClient, operationUrl: string, options?: msRest.RequestOptionsBase): Promise<msRest.HttpOperationResponse> {
// Construct URL
const requestUrl = operationUrl.replace(" ", "%20");
// Create HTTP request object
const httpRequest: msRest.RequestPrepareOptions = {
method: "GET",
url: requestUrl,
headers: {}
};
if (options && options.customHeaders) {
const customHeaders = options.customHeaders;
for (const headerName of Object.keys(customHeaders)) {
httpRequest.headers![headerName] = customHeaders[headerName];
}
}
return client.sendRequest(httpRequest).then(operationResponse => {
const statusCode = operationResponse.status;
const responseBody = operationResponse.parsedBody;
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
const error = new msRest.RestError(`Invalid status code with response body "${operationResponse.bodyAsText}" occurred ` +
`when polling for operation status.`);
error.statusCode = statusCode;
error.request = msRest.stripRequest(operationResponse.request);
error.response = operationResponse;
error.body = responseBody;
throw error;
}
return operationResponse;
});
}

354
lib/lroPollStrategy.ts Normal file
Просмотреть файл

@ -0,0 +1,354 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { delay, HttpMethods, HttpOperationResponse, RequestOptionsBase, RequestPrepareOptions, RestError, stripRequest, WebResource } from "ms-rest-js";
import { AzureServiceClient } from "./azureServiceClient";
import { LongRunningOperationStates } from "./util/constants";
/**
* A long-running operation polling strategy base class that other polling strategies should extend.
*/
export abstract class LROPollStrategy {
private readonly _initialRequestUrl: string;
protected readonly _initialRequestMethod: HttpMethods;
protected _status: LongRunningOperationStates;
protected _mostRecentRequest: WebResource;
protected _response: HttpOperationResponse;
protected _resource: any;
constructor(initialResponse: HttpOperationResponse, private readonly _azureServiceClient: AzureServiceClient, private readonly _options?: RequestOptionsBase) {
this._response = initialResponse;
this._mostRecentRequest = initialResponse.request;
this._initialRequestUrl = this._mostRecentRequest.url;
this._initialRequestMethod = this._mostRecentRequest.method;
this._resource = getResponseBody(initialResponse);
this._status = getStatusFromResponse(initialResponse, this._resource);
this._resource = getResponseBody(initialResponse);
}
public async pollUntilFinished(): Promise<boolean> {
while (!isFinished(this._status)) {
const delayInSeconds: number = getDelayInSeconds(this._azureServiceClient, this._response);
await delay(delayInSeconds * 1000);
await this.sendPollRequest();
}
return this._status === "Succeeded";
}
protected abstract sendPollRequest(): Promise<void>;
protected shouldDoFinalGetResourceRequest(): boolean {
return !this._resource && (this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH");
}
public async getOperationResponse(): Promise<HttpOperationResponse> {
if (this.shouldDoFinalGetResourceRequest()) {
await this.updateStateFromGetResourceOperation();
}
const result: HttpOperationResponse = {
...this._response,
headers: this._response.headers.clone()
};
if (this._resource && typeof this._resource.valueOf() === "string") {
result.bodyAsText = this._resource;
result.parsedBody = JSON.parse(this._resource);
} else {
result.bodyAsText = JSON.stringify(this._resource);
result.parsedBody = this._resource;
}
return result;
}
public getRestError(): RestError {
const error = new RestError("");
error.request = stripRequest(this._mostRecentRequest);
error.response = this._response;
error.message = `Long running operation failed with status: "${this._status}".`;
error.body = this._resource;
if (error.body) {
const innerError: any = error.body.error;
if (innerError) {
if (innerError.message) {
error.message = `Long running operation failed with error: "${innerError.message}".`;
}
if (innerError.code) {
error.code = innerError.code;
}
}
}
return error;
}
protected updateStateFromGetResourceOperation(): Promise<void> {
return this.getStatus(this._initialRequestUrl).then(result => {
if (!result.parsedBody) {
throw new Error("The response from long running operation does not contain a body.");
}
this._status = getProvisioningState(result.parsedBody) || "Succeeded";
this._response = result;
this._mostRecentRequest = result.request;
this._resource = getResponseBody(result);
});
}
/**
* Retrieves operation status by querying the operation URL.
* @param {string} statusUrl URL used to poll operation result.
*/
protected getStatus(statusUrl: string): Promise<HttpOperationResponse> {
const requestUrl: string = statusUrl.replace(" ", "%20");
const httpRequest: RequestPrepareOptions = {
method: "GET",
url: requestUrl,
headers: {}
};
if (this._options && this._options.customHeaders) {
const customHeaders = this._options.customHeaders;
for (const headerName of Object.keys(customHeaders)) {
httpRequest.headers![headerName] = customHeaders[headerName];
}
}
return this._azureServiceClient.sendRequest(httpRequest).then(operationResponse => {
const statusCode: number = operationResponse.status;
const responseBody: any = operationResponse.parsedBody;
if (statusCode !== 200 && statusCode !== 201 && statusCode !== 202 && statusCode !== 204) {
const error = new RestError(`Invalid status code with response body "${operationResponse.bodyAsText}" occurred when polling for operation status.`);
error.statusCode = statusCode;
error.request = stripRequest(operationResponse.request);
error.response = operationResponse;
error.body = responseBody;
throw error;
}
return operationResponse;
});
}
}
export function getDelayInSeconds(azureServiceClient: AzureServiceClient, previousResponse: HttpOperationResponse): number {
let delayInSeconds = 30;
if (azureServiceClient.longRunningOperationRetryTimeout != undefined) {
delayInSeconds = azureServiceClient.longRunningOperationRetryTimeout;
} else {
const retryAfterHeaderValue: string | undefined = previousResponse.headers.get("retry-after");
if (retryAfterHeaderValue) {
const retryAfterDelayInSeconds: number = parseInt(retryAfterHeaderValue);
if (!Number.isNaN(retryAfterDelayInSeconds)) {
delayInSeconds = retryAfterDelayInSeconds;
}
}
}
return delayInSeconds;
}
function getProvisioningState(responseBody: any): LongRunningOperationStates | undefined {
return responseBody && responseBody.properties && responseBody.properties.provisioningState;
}
function getResponseBody(response: HttpOperationResponse): any {
let result: any;
try {
if (response.bodyAsText && response.bodyAsText.length > 0) {
result = JSON.parse(response.bodyAsText);
} else {
result = response.parsedBody;
}
} catch (error) {
const deserializationError = new RestError(`Error "${error}" occurred in parsing the responseBody " +
"while creating the PollingState for Long Running Operation- "${response.bodyAsText}"`);
deserializationError.request = response.request;
deserializationError.response = response;
throw deserializationError;
}
return result;
}
function getStatusFromResponse(response: HttpOperationResponse, responseBody?: any): LongRunningOperationStates {
if (responseBody == undefined) {
responseBody = getResponseBody(response);
}
let result: LongRunningOperationStates;
switch (response.status) {
case 202:
result = "InProgress";
break;
case 204:
result = "Succeeded";
break;
case 201:
result = getProvisioningState(responseBody) || "InProgress";
break;
case 200:
result = getProvisioningState(responseBody) || "Succeeded";
break;
default:
result = "Failed";
break;
}
return result;
}
const terminalStates: LongRunningOperationStates[] = ["Succeeded", "Failed", "Canceled"];
/**
* Get whether or not a long-running operation with the provided status is finished.
* @param status The current status of a long-running operation.
* @returns Whether or not a long-running operation with the provided status is finished.
*/
function isFinished(status: LongRunningOperationStates): boolean {
return terminalStates.indexOf(status) !== -1;
}
/**
* Create a new long-running operation polling strategy based on the provided initial response.
* @param initialResponse The initial response to the long-running operation's initial request.
* @param azureServiceClient The AzureServiceClient that was used to send the initial request.
* @param options Any options that were provided to the initial request.
*/
export function createLROPollStrategy(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase): LROPollStrategy {
if (checkResponseStatusCodeFailed(initialResponse)) {
throw new Error(`Unexpected polling status code from long running operation ` +
`"${initialResponse.status}" for method "${initialResponse.request.method}".`);
}
let result: LROPollStrategy;
if (getAzureAsyncOperationHeaderValue(initialResponse)) {
result = new AzureAsyncOperationLROPollStrategy(initialResponse, azureServiceClient, options);
} else if (getLocationHeaderValue(initialResponse)) {
result = new LocationLROPollStrategy(initialResponse, azureServiceClient, options);
} else if (initialResponse.request.method === "PUT" || isFinished(getStatusFromResponse(initialResponse))) {
result = new GetResourceLROPollStrategy(initialResponse, azureServiceClient, options);
} else {
throw new Error("Can't determine long running operation polling strategy from initial response.");
}
return result;
}
/**
* Verified whether an unexpected polling status code for long running operation was received for the response of the initial request.
* @param {msRest.HttpOperationResponse} initialResponse - Response to the initial request that was sent as a part of the asynchronous operation.
*/
function checkResponseStatusCodeFailed(initialResponse: HttpOperationResponse): boolean {
const statusCode = initialResponse.status;
const method: HttpMethods = initialResponse.request.method;
if (statusCode === 200 || statusCode === 202 ||
(statusCode === 201 && method === "PUT") ||
(statusCode === 204 && (method === "DELETE" || method === "POST"))) {
return false;
} else {
return true;
}
}
function getLocationHeaderValue(response: HttpOperationResponse): string | undefined {
return response.headers.get("location");
}
/**
* A long-running operation polling strategy that is based on the location header.
*/
class LocationLROPollStrategy extends LROPollStrategy {
private _locationHeaderValue?: string;
constructor(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase) {
super(initialResponse, azureServiceClient, options);
this._locationHeaderValue = getLocationHeaderValue(initialResponse)!;
}
/**
* Retrieve PUT operation status by polling from "location" header.
* @param {string} method - The HTTP method.
* @param {PollingState} pollingState - The object to persist current operation state.
*/
protected sendPollRequest(): Promise<void> {
return this.getStatus(this._locationHeaderValue!).then((result: HttpOperationResponse) => {
const locationHeaderValue: string | undefined = getLocationHeaderValue(result);
if (locationHeaderValue) {
this._locationHeaderValue = locationHeaderValue;
}
this._response = result;
this._mostRecentRequest = result.request;
const statusCode: number = result.status;
if (statusCode === 202) {
this._status = "InProgress";
} else if (statusCode === 200 ||
(statusCode === 201 && (this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH")) ||
(statusCode === 204 && (this._initialRequestMethod === "DELETE" || this._initialRequestMethod === "POST"))) {
this._status = "Succeeded";
this._resource = getResponseBody(result);
} else {
throw new Error(`The response with status code ${statusCode} from polling for long running operation url "${this._locationHeaderValue}" is not valid.`);
}
});
}
}
function getAzureAsyncOperationHeaderValue(response: HttpOperationResponse): string | undefined {
return response.headers.get("azure-asyncoperation");
}
/**
* A long-running operation polling strategy that is based on the azure-asyncoperation header.
*/
class AzureAsyncOperationLROPollStrategy extends LROPollStrategy {
private _azureAsyncOperationHeaderValue: string;
public constructor(initialResponse: HttpOperationResponse, azureServiceClient: AzureServiceClient, options?: RequestOptionsBase) {
super(initialResponse, azureServiceClient, options);
this._azureAsyncOperationHeaderValue = getAzureAsyncOperationHeaderValue(initialResponse)!;
}
/**
* Retrieve operation status by polling from "azure-asyncoperation" header.
* @param {PollingState} pollingState - The object to persist current operation state.
* @param {boolean} inPostOrDelete - Invoked by Post Or Delete operation.
*/
protected sendPollRequest(): Promise<void> {
return this.getStatus(this._azureAsyncOperationHeaderValue!).then((response: HttpOperationResponse) => {
const parsedResponse: any = response.parsedBody;
if (!parsedResponse) {
throw new Error("The response from long running operation does not contain a body.");
} else if (!parsedResponse.status) {
throw new Error(`The response "${response.bodyAsText}" from long running operation does not contain the status property.`);
}
const azureAsyncOperationHeaderValue: string | undefined = getAzureAsyncOperationHeaderValue(response);
if (azureAsyncOperationHeaderValue) {
this._azureAsyncOperationHeaderValue = azureAsyncOperationHeaderValue;
}
this._status = parsedResponse.status;
this._response = response;
this._mostRecentRequest = response.request;
this._resource = getResponseBody(response);
});
}
protected shouldDoFinalGetResourceRequest(): boolean {
return this._initialRequestMethod === "PUT" || this._initialRequestMethod === "PATCH";
}
}
/**
* A long-running operation polling strategy that is based on the resource's provisioning state.
*/
class GetResourceLROPollStrategy extends LROPollStrategy {
protected sendPollRequest(): Promise<void> {
return this.updateStateFromGetResourceOperation();
}
}

Просмотреть файл

@ -1,9 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { AzureServiceClientOptions, AzureServiceClient } from "./azureServiceClient";
import Constants from "./util/constants";
import { CloudError, CloudErrorMapper } from "./cloudError";
import { BaseResource, BaseResourceMapper } from "./baseResource";
import { CognitiveServicesCredentials } from "./credentials/cognitiveServicesCredentials";
export { AzureServiceClient, AzureServiceClientOptions, Constants, CloudError, CloudErrorMapper, BaseResource, BaseResourceMapper, CognitiveServicesCredentials };
export { AzureServiceClient, AzureServiceClientOptions } from "./azureServiceClient";
export { BaseResource, BaseResourceMapper } from "./baseResource";
export { CloudError, CloudErrorMapper } from "./cloudError";
export { CognitiveServicesCredentials } from "./credentials/cognitiveServicesCredentials";
export { DEFAULT_LANGUAGE, LongRunningOperationStates, msRestAzureVersion } from "./util/constants";

Просмотреть файл

@ -1,194 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import { LongRunningOperationStates as LroStates } from "./util/constants";
import * as msRest from "ms-rest-js";
/**
* @class
* Initializes a new instance of the PollingState class.
*/
export default class PollingState {
/**
* @param {msRest.HttpOperationResponse} [response] - Response of the initial request that was made as a part of the asynchronous operation.
*/
resultOfInitialRequest: msRest.HttpOperationResponse;
/**
* @param {msRest.RequestOptionsBase} [optionsOfInitialRequest] - Request options that were provided as a part of the initial request.
*/
optionsOfInitialRequest!: msRest.RequestOptionsBase;
/**
* @param {msRest.WebResource} [request] - provides information about the request made for polling.
*/
request: msRest.WebResource;
/**
* @param {Response} [response] - The response object to extract longrunning operation status.
*/
response!: msRest.HttpOperationResponse;
/**
* @param {any} [resource] - Provides information about the response body received in the polling request. Particularly useful when polling via provisioningState.
*/
resource: any;
/**
* @param {number} [retryTimeout] - The timeout in seconds to retry on intermediate operation results. Default Value is 30.
*/
retryTimeout = 30;
/**
* @param {string} [azureAsyncOperationHeaderLink] - The url that is present in "azure-asyncoperation" response header.
*/
azureAsyncOperationHeaderLink?: string;
/**
* @param {string} [locationHeaderLink] - The url that is present in "Location" response header.
*/
locationHeaderLink?: string;
/**
* @param {string} [status] - The status of polling. "Succeeded, Failed, Cancelled, Updating, Creating, etc."
*/
status: LroStates;
/**
* @param {msRest.RestError} [error] - Provides information about the error that happened while polling.
*/
error?: msRest.RestError;
constructor(resultOfInitialRequest: msRest.HttpOperationResponse, retryTimeout: number) {
this.resultOfInitialRequest = resultOfInitialRequest;
this.retryTimeout = retryTimeout;
this.updateResponse(resultOfInitialRequest);
this.request = resultOfInitialRequest.request;
// Parse response.body & assign it as the resource.
try {
if (resultOfInitialRequest.bodyAsText && resultOfInitialRequest.bodyAsText.length > 0) {
this.resource = JSON.parse(resultOfInitialRequest.bodyAsText);
} else {
this.resource = resultOfInitialRequest.parsedBody;
}
} catch (error) {
const deserializationError = new msRest.RestError(`Error "${error}" occurred in parsing the responseBody " +
"while creating the PollingState for Long Running Operation- "${resultOfInitialRequest.bodyAsText}"`);
deserializationError.request = resultOfInitialRequest.request;
deserializationError.response = resultOfInitialRequest;
throw deserializationError;
}
const resource = this.resource;
let status: LroStates;
switch (this.response.status) {
case 202:
status = "InProgress";
break;
case 204:
status = "Succeeded";
break;
case 201:
if (resource && resource.properties && resource.properties.provisioningState) {
status = resource.properties.provisioningState;
} else {
status = "InProgress";
}
break;
case 200:
if (resource && resource.properties && resource.properties.provisioningState) {
status = resource.properties.provisioningState;
} else {
status = "Succeeded";
}
break;
default:
status = "Failed";
break;
}
this.status = status;
}
/**
* Update cached data using the provided response object
* @param {Response} [response] - provider response object.
*/
updateResponse(response: msRest.HttpOperationResponse) {
this.response = response;
if (response && response.headers) {
const asyncOperationHeader: string | undefined = response.headers.get("azure-asyncoperation");
const locationHeader: string | undefined = response.headers.get("location");
if (asyncOperationHeader) {
this.azureAsyncOperationHeaderLink = asyncOperationHeader;
}
if (locationHeader) {
this.locationHeaderLink = locationHeader;
}
}
}
/**
* Gets timeout in milliseconds.
* @returns {number} timeout
*/
getTimeout() {
const retryTimeout = this.retryTimeout;
if (retryTimeout || retryTimeout === 0) {
return retryTimeout * 1000;
}
if (this.response) {
const retryAfter: string | undefined = this.response.headers.get("retry-after");
if (retryAfter) {
return parseInt(retryAfter) * 1000;
}
}
return 30 * 1000;
}
/**
* Returns long running operation result.
* @returns {msRest.HttpOperationResponse} HttpOperationResponse
*/
getOperationResponse(): msRest.HttpOperationResponse {
const result = { ...this.response, headers: this.response.headers.clone() };
const resource = this.resource;
if (resource && typeof resource.valueOf() === "string") {
result.bodyAsText = resource;
result.parsedBody = JSON.parse(resource);
} else {
result.parsedBody = resource;
result.bodyAsText = JSON.stringify(resource);
}
return result;
}
/**
* Returns an Error on operation failure.
* @param {Error} err - The error object.
* @returns {msRest.RestError} The RestError defined in the runtime.
*/
getRestError(err?: Error): msRest.RestError {
let errMsg: string;
let errCode: string | undefined = undefined;
const error = new msRest.RestError("");
error.request = msRest.stripRequest(this.request);
error.response = this.response;
const parsedResponse = this.resource as { [key: string]: any };
if (err && err.message) {
errMsg = `Long running operation failed with error: "${err.message}".`;
} else {
errMsg = `Long running operation failed with status: "${this.status}".`;
}
if (parsedResponse) {
if (parsedResponse.error && parsedResponse.error.message) {
errMsg = `Long running operation failed with error: "${parsedResponse.error.message}".`;
}
if (parsedResponse.error && parsedResponse.error.code) {
errCode = parsedResponse.error.code as string;
}
}
error.message = errMsg;
if (errCode) error.code = errCode;
error.body = parsedResponse;
return error;
}
}

Просмотреть файл

@ -10,22 +10,17 @@
*/
export type LongRunningOperationStates = "InProgress" | "Succeeded" | "Failed" | "Canceled";
const Constants = {
/**
* The default language in the request header.
*
* @const
* @type {string}
*/
export const DEFAULT_LANGUAGE = "en-us";
/**
* The default language in the request header.
*
* @const
* @type {string}
*/
DEFAULT_LANGUAGE: "en-us",
/**
* The ms-rest-azure version.
* @const
* @type {string}
*/
msRestAzureVersion: "0.1.0"
};
export default Constants;
/**
* The ms-rest-azure version.
* @const
* @type {string}
*/
export const msRestAzureVersion = "0.1.0";

14
package-lock.json сгенерированный
Просмотреть файл

@ -1,6 +1,6 @@
{
"name": "ms-rest-azure-js",
"version": "0.11.101",
"version": "0.11.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -2938,9 +2938,9 @@
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"ms-rest-js": {
"version": "0.16.371",
"resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-0.16.371.tgz",
"integrity": "sha1-iQHQQc8HynVA0YzfmmwCe2Z7ImQ=",
"version": "0.16.372",
"resolved": "https://registry.npmjs.org/ms-rest-js/-/ms-rest-js-0.16.372.tgz",
"integrity": "sha1-8xchzTHDXqv9piaAG/fJRSqJfJE=",
"requires": {
"@types/express": "^4.11.1",
"@types/form-data": "^2.2.1",
@ -6306,9 +6306,9 @@
}
},
"tsutils": {
"version": "2.28.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.28.0.tgz",
"integrity": "sha512-bh5nAtW0tuhvOJnx1GLRn5ScraRLICGyJV5wJhtRWOLsxW70Kk5tZtpK3O/hW6LDnqKS9mlUMPZj9fEMJ0gxqA==",
"version": "2.29.0",
"resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
"integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
"dev": true,
"requires": {
"tslib": "^1.8.1"

Просмотреть файл

@ -30,7 +30,7 @@
"types": "./typings/lib/msRestAzure.d.ts",
"license": "MIT",
"dependencies": {
"ms-rest-js": "~0.16.371",
"ms-rest-js": "~0.16.372",
"tslib": "^1.9.2"
},
"devDependencies": {

Просмотреть файл

@ -2,8 +2,7 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as assert from "assert";
import * as msRest from "ms-rest-js";
import { HttpHeaders, HttpOperationResponse, TokenCredentials, WebResource } from "ms-rest-js";
import { HttpHeaders, HttpOperationResponse, RequestOptionsBase, RestError, TokenCredentials, WebResource } from "ms-rest-js";
import { AzureServiceClient, AzureServiceClientOptions, updateOptionsWithDefaultValues } from "../lib/azureServiceClient";
import * as msAssert from "./msAssert";
@ -12,14 +11,14 @@ describe("AzureServiceClient", () => {
it("with no options provided", () => {
const client = new AzureServiceClient(new TokenCredentials("my-fake-token"));
assert.strictEqual(client.acceptLanguage, "en-us");
assert.strictEqual(client.longRunningOperationRetryTimeout, 30);
assert.strictEqual(client.longRunningOperationRetryTimeout, undefined);
assert.deepStrictEqual(client.userAgentInfo, { value: ["ms-rest-js/0.1.0", "ms-rest-azure/0.1.0"] });
});
it("with acceptLanguage provided", () => {
const client = new AzureServiceClient(new TokenCredentials("my-fake-token"), { acceptLanguage: "my-fake-language" });
assert.strictEqual(client.acceptLanguage, "my-fake-language");
assert.strictEqual(client.longRunningOperationRetryTimeout, 30);
assert.strictEqual(client.longRunningOperationRetryTimeout, undefined);
assert.deepStrictEqual(client.userAgentInfo, { value: ["ms-rest-js/0.1.0", "ms-rest-azure/0.1.0"] });
});
@ -49,7 +48,7 @@ describe("AzureServiceClient", () => {
}
]);
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "GET");
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
assert.strictEqual(error.message, `Error "SyntaxError: Unexpected token < in JSON at position 0" occurred while parsing the response body - <responseBody>hello</responseBody>.`);
assert.strictEqual(error.request!.headers.get("authorization"), "Bearer my-fake-token");
});
@ -166,7 +165,7 @@ describe("AzureServiceClient", () => {
{ status: 200, body: {} }
]);
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
const options: msRest.RequestOptionsBase = {
const options: RequestOptionsBase = {
customHeaders: {
a: "1"
}
@ -184,7 +183,7 @@ describe("AzureServiceClient", () => {
{ status: 200, body: { properties: { provisioningState: "Failed" } } }
]);
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
assert.strictEqual(error.message, `Long running operation failed with status: "Failed".`);
assert.strictEqual(error.code, undefined);
});
@ -210,7 +209,7 @@ describe("AzureServiceClient", () => {
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "GET");
await msAssert.throwsAsync(
serviceClient.sendLongRunningRequest(httpRequest),
new Error(`Location header is missing from long running operation.`));
new Error(`Can't determine long running operation polling strategy from initial response.`));
});
it("with 202 status and PATCH method", async () => {
@ -218,7 +217,7 @@ describe("AzureServiceClient", () => {
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PATCH");
await msAssert.throwsAsync(
serviceClient.sendLongRunningRequest(httpRequest),
new Error(`Location header is missing from long running operation.`));
new Error(`Can't determine long running operation polling strategy from initial response.`));
});
it("with 202 status, PUT method, and undefined final response body", async () => {
@ -237,7 +236,7 @@ describe("AzureServiceClient", () => {
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "POST");
await msAssert.throwsAsync(
serviceClient.sendLongRunningRequest(httpRequest),
new Error(`Location header is missing from long running operation.`));
new Error(`Can't determine long running operation polling strategy from initial response.`));
});
it("with 202 status, POST method, azure-asyncoperation header, and undefined final response body", async () => {
@ -478,7 +477,7 @@ describe("AzureServiceClient", () => {
}
]);
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "PUT");
const error: msRest.RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
const error: RestError = await msAssert.throwsAsync(serviceClient.sendLongRunningRequest(httpRequest));
assert.strictEqual(error.message, `Invalid status code with response body "undefined" occurred when polling for operation status.`);
assert.strictEqual(error.statusCode, 404);
assert.strictEqual(error.request!.headers.contains("authorization"), false);
@ -490,7 +489,7 @@ describe("AzureServiceClient", () => {
const httpRequest = new WebResource("https://fake.azure.com/longRunningOperation", "DELETE");
await msAssert.throwsAsync(
serviceClient.sendLongRunningRequest(httpRequest),
new Error(`Location header is missing from long running operation.`));
new Error(`Can't determine long running operation polling strategy from initial response.`));
});
it("with 204 status and GET method", async () => {

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as assert from "assert";
import { WebResource } from "ms-rest-js";
import { CognitiveServicesCredentials } from "../../lib/credentials/cognitiveServicesCredentials";
import * as msAssert from "../msAssert";
describe("CognitiveServicesCredentials", () => {
describe("constructor()", () => {
it("with undefined subscription key", () => {
msAssert.throws(() => new CognitiveServicesCredentials(undefined as any),
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
});
it("with null subscription key", () => {
// tslint:disable-next-line:no-null-keyword
msAssert.throws(() => new CognitiveServicesCredentials(null as any),
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
});
it("with number subscription key", () => {
msAssert.throws(() => new CognitiveServicesCredentials(50 as any),
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
});
it("with empty subscription key", () => {
msAssert.throws(() => new CognitiveServicesCredentials(""),
new Error("subscriptionKey cannot be null or undefined and must be of type string."));
});
it("with non-empty subscription key", async () => {
const credentials = new CognitiveServicesCredentials("fake-subscription-key");
const httpRequest = new WebResource();
const signedHttpRequest: WebResource = await credentials.signRequest(httpRequest);
assert.strictEqual(signedHttpRequest, httpRequest);
assert.deepEqual(signedHttpRequest.headers.rawHeaders(), {
"Ocp-Apim-Subscription-Key": "fake-subscription-key",
"X-BingApis-SDK-Client": "node-SDK"
});
});
});
});

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
import * as assert from "assert";
import { HttpHeaders, HttpOperationResponse, TokenCredentials, WebResource } from "ms-rest-js";
import { AzureServiceClient } from "../lib/azureServiceClient";
import { getDelayInSeconds } from "../lib/lroPollStrategy";
describe("LROPollStrategy", () => {
describe("getDelayInMilliseconds()", () => {
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and no retry-after header", () => {
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
const previousResponse: HttpOperationResponse = {
request: new WebResource(),
status: 200,
headers: new HttpHeaders()
};
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 30);
});
it("with 11 AzureServiceClient.longRunningOperationRetryTimeout and no retry-after header", () => {
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"), { longRunningOperationRetryTimeout: 11 });
const previousResponse: HttpOperationResponse = {
request: new WebResource(),
status: 200,
headers: new HttpHeaders()
};
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 11);
});
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and 12 retry-after header", () => {
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
const previousResponse: HttpOperationResponse = {
request: new WebResource(),
status: 200,
headers: new HttpHeaders({ "retry-after": "12" })
};
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 12);
});
it("with no AzureServiceClient.longRunningOperationRetryTimeout value and spam retry-after header", () => {
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"));
const previousResponse: HttpOperationResponse = {
request: new WebResource(),
status: 200,
headers: new HttpHeaders({ "retry-after": "spam" })
};
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 30);
});
it("with 11 AzureServiceClient.longRunningOperationRetryTimeout and 12 retry-after header", () => {
const azureServiceClient = new AzureServiceClient(new TokenCredentials("my-fake-token"), { longRunningOperationRetryTimeout: 11 });
const previousResponse: HttpOperationResponse = {
request: new WebResource(),
status: 200,
headers: new HttpHeaders({ "retry-after": "12" })
};
assert.strictEqual(getDelayInSeconds(azureServiceClient, previousResponse), 11);
});
});
});

Просмотреть файл

@ -4,10 +4,37 @@
import * as assert from "assert";
/**
* Assert that the provided asyncAction throws an Error. If the expectedError is undefined, then
* Assert that the provided syncFunction throws an Error. If the expectedError is undefined, then
* this function will just assert that an Error was thrown. If the expectedError is defined, then
* this function will assert that the Error that was thrown is equal to the provided expectedError.
* @param asyncFunction The async function that is expected to thrown an Error.
* @param syncFunction The synchronous function that is expected to thrown an Error.
* @param expectedError The Error that is expected to be thrown.
*/
export function throws(syncFunction: () => void, expectedError?: ((error: Error) => void) | Error): Error {
let thrownError: Error | undefined;
try {
syncFunction();
} catch (error) {
thrownError = error;
}
if (!thrownError) {
assert.throws(() => { });
} else if (expectedError instanceof Error) {
assert.deepStrictEqual(thrownError, expectedError);
} else if (expectedError) {
expectedError(thrownError);
}
return thrownError!;
}
/**
* Assert that the provided asyncFunction throws an Error. If the expectedError is undefined, then
* this function will just assert that an Error was thrown. If the expectedError is defined, then
* this function will assert that the Error that was thrown is equal to the provided expectedError.
* @param asyncFunction The asynchronous function that is expected to thrown an Error.
* @param expectedError The Error that is expected to be thrown.
*/
export async function throwsAsync<T>(asyncFunction: (() => Promise<T>) | Promise<T>, expectedError?: ((error: Error) => void) | Error): Promise<Error> {