Skip to content

Commit

Permalink
Make LROData a symbol in the response (#787)
Browse files Browse the repository at this point in the history
* Make LROData a symbol in the response

* Update lro imports

* Update symbol name

* Update symbol assignment

* Update smoke tests

* Update test type
  • Loading branch information
joheredi authored Nov 5, 2020
1 parent ab8e8d1 commit fce7fe1
Show file tree
Hide file tree
Showing 146 changed files with 9,105 additions and 888 deletions.
29 changes: 25 additions & 4 deletions src/generators/modelsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ export function generateModels(clientDetails: ClientDetails, project: Project) {
moduleSpecifier: "@azure/core-http"
});

// Import LRO Symbol if any of the operations is an LRO one
if (
clientDetails.operationGroups.some(og => og.operations.some(o => o.isLRO))
) {
modelsIndexFile.addImportDeclaration({
namedImports: ["LROSYM", "LROResponseInfo"],
moduleSpecifier: "../lro/models"
});
}

writeUniontypes(clientDetails, modelsIndexFile);
writeObjects(clientDetails, modelsIndexFile);
writeChoices(clientDetails, modelsIndexFile);
Expand Down Expand Up @@ -144,7 +154,7 @@ function writeOptionsParameter(
* the response body and headers
*/
function writeResponseTypes(
{ responses, name, typeDetails: operationType }: OperationDetails,
{ responses, name, typeDetails: operationType, isLRO }: OperationDetails,
modelsIndexFile: SourceFile,
allModelsNames: Set<string>
) {
Expand Down Expand Up @@ -172,7 +182,7 @@ function writeResponseTypes(
name: responseName,
docs: [`Contains response data for the ${name} operation.`],
isExported: true,
type: buildResponseType(operation),
type: buildResponseType(operation, isLRO),
leadingTrivia: writer => writer.blankLine(),
kind: StructureKind.TypeAlias
});
Expand Down Expand Up @@ -312,15 +322,26 @@ type IntersectionTypeParameters = [
* to create a type that contains all the properties that a response may include
*/
function buildResponseType(
operationResponse: OperationResponseDetails
operationResponse: OperationResponseDetails,
isLro: boolean = false
): WriterFunction {
// First we get the response Headers and Body details
const headersProperties = getHeadersProperties(operationResponse);
const bodyProperties = getBodyProperties(operationResponse);
const lroProperties: OptionalKind<PropertySignatureStructure>[] = isLro
? [
{
name: "[LROSYM]",
docs: ["The parsed HTTP response headers."],
type: "LROResponseInfo"
}
]
: [];

const innerResponseProperties = [
...(bodyProperties?.internalResponseProperties || []),
...(headersProperties?.internalResponseProperties || [])
...(headersProperties?.internalResponseProperties || []),
...lroProperties
];
const innerTypeWriter = Writers.objectType({
properties: [
Expand Down
78 changes: 60 additions & 18 deletions src/lro/azureAsyncOperationStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ import {
BaseResult,
LROOperationStep,
LROResponseInfo,
FinalStateVia
FinalStateVia,
LROSYM
} from "./models";
import { OperationSpec, OperationArguments } from "@azure/core-http";
import {
OperationSpec,
OperationArguments,
OperationResponse
} from "@azure/core-http";
import { terminalStates } from "./constants";
import { SendOperationFn } from ".";

Expand All @@ -14,7 +19,7 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(
sendOperationFn: SendOperationFn<TResult>,
finalStateVia?: FinalStateVia
): LROStrategy<TResult> {
const lroData = initialOperation.result._lroData;
const lroData = initialOperation.result._response[LROSYM];
if (!lroData) {
throw new Error(
"Expected lroData to be defined for Azure-AsyncOperation strategy"
Expand All @@ -27,7 +32,7 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(

return {
isTerminal: () => {
const currentResult = currentOperation.result._lroData;
const currentResult = currentOperation.result._response[LROSYM];

if (!currentResult) {
throw new Error("Expected lroData to determine terminal status");
Expand All @@ -43,24 +48,24 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(
return terminalStates.includes(status.toLowerCase());
},
sendFinalRequest: async () => {
if (!initialOperation.result._lroData) {
if (!initialOperation.result._response[LROSYM]) {
throw new Error("Expected lroData to determine terminal status");
}

if (!currentOperation.result._lroData) {
if (!currentOperation.result._response[LROSYM]) {
throw new Error("Expected lroData to determine terminal status");
}

const initialOperationResult = initialOperation.result._lroData;
const currentOperationResult = currentOperation.result._lroData;
const initialOperationResult = initialOperation.result._response[LROSYM];
const currentOperationResult = currentOperation.result._response[LROSYM];

if (
!shouldPerformFinalGet(initialOperationResult, currentOperationResult)
) {
return currentOperation;
}

if (initialOperationResult.requestMethod === "PUT") {
if (initialOperationResult?.requestMethod === "PUT") {
currentOperation = await sendFinalGet(
initialOperation,
sendOperationFn
Expand All @@ -69,7 +74,7 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(
return currentOperation;
}

if (initialOperationResult.location) {
if (initialOperationResult?.location) {
switch (finalStateVia) {
case "original-uri":
currentOperation = await sendFinalGet(
Expand All @@ -84,7 +89,7 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(
default:
const location =
initialOperationResult.location ||
currentOperationResult.location;
currentOperationResult?.location;

if (!location) {
throw new Error("Couldn't determine final GET URL from location");
Expand All @@ -108,9 +113,11 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(

const pollingArgs = currentOperation.args;
// Make sure we don't send any body to the get request
const { requestBody, ...restSpec } = currentOperation.spec;
const { requestBody, responses, ...restSpec } = currentOperation.spec;

const pollingSpec: OperationSpec = {
...restSpec,
responses: getCompositeMappers(responses),
httpMethod: "GET",
path: lastKnownPollingUrl
};
Expand All @@ -119,8 +126,8 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(

// Update latest polling url
lastKnownPollingUrl =
result._lroData?.azureAsyncOperation ||
result._lroData?.operationLocation ||
result._response[LROSYM]?.azureAsyncOperation ||
result._response[LROSYM]?.operationLocation ||
lastKnownPollingUrl;

// Update lastOperation result
Expand All @@ -135,12 +142,47 @@ export function createAzureAsyncOperationStrategy<TResult extends BaseResult>(
};
}

/**
* Polling calls will always return a status object i.e. {"status": "success"}
* these intermediate responses are not described in the swagger so we need to
* pass custom mappers at runtime.
* This function replaces all the existing mappers to be able to deserialize a status object
* @param responses Original set of responses defined in the operation
*/
function getCompositeMappers(responses: {
[responseCode: string]: OperationResponse;
}): {
[responseCode: string]: OperationResponse;
} {
return Object.keys(responses).reduce((acc, statusCode) => {
return {
...acc,
[statusCode]: {
...responses[statusCode],
bodyMapper: {
type: {
name: "Composite",
modelProperties: {
status: {
serializedName: "status",
type: {
name: "String"
}
}
}
}
}
}
};
}, {} as { [responseCode: string]: OperationResponse });
}

function shouldPerformFinalGet(
initialResult: LROResponseInfo,
currentResult: LROResponseInfo
initialResult?: LROResponseInfo,
currentResult?: LROResponseInfo
) {
const { status } = currentResult;
const { requestMethod: initialRequestMethod, location } = initialResult;
const { status } = currentResult || {};
const { requestMethod: initialRequestMethod, location } = initialResult || {};
if (status && status.toLowerCase() !== "succeeded") {
return false;
}
Expand Down
6 changes: 3 additions & 3 deletions src/lro/bodyPollingStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { LROStrategy, BaseResult, LROOperationStep } from "./models";
import { LROStrategy, BaseResult, LROOperationStep, LROSYM } from "./models";
import { OperationSpec } from "@azure/core-http";
import { terminalStates } from "./constants";
import { SendOperationFn } from "./lroPoller";
Expand All @@ -11,15 +11,15 @@ export function createBodyPollingStrategy<TResult extends BaseResult>(
initialOperation: LROOperationStep<TResult>,
sendOperation: SendOperationFn<TResult>
): LROStrategy<TResult> {
if (!initialOperation.result._lroData) {
if (!initialOperation.result._response[LROSYM]) {
throw new Error("Expected lroData to be defined for BodyPolling strategy");
}

let currentOperation = initialOperation;

return {
isTerminal: () => {
const currentResult = currentOperation.result._lroData;
const currentResult = currentOperation.result._response[LROSYM];
if (!currentResult) {
throw new Error("Expected lroData to determine terminal status");
}
Expand Down
9 changes: 5 additions & 4 deletions src/lro/locationStrategy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { BaseResult, LROOperationStep, LROStrategy } from "./models";
import { BaseResult, LROOperationStep, LROStrategy, LROSYM } from "./models";
import { SendOperationFn } from "./lroPoller";
import { OperationSpec } from "@azure/core-http";

export function createLocationStrategy<TResult extends BaseResult>(
initialOperation: LROOperationStep<TResult>,
sendOperationFn: SendOperationFn<TResult>
): LROStrategy<TResult> {
const lroData = initialOperation.result._lroData;
const lroData = initialOperation.result._response[LROSYM];
if (!lroData) {
throw new Error(
"Expected lroData to be defined for Azure-AsyncOperation strategy"
Expand All @@ -18,7 +18,7 @@ export function createLocationStrategy<TResult extends BaseResult>(

return {
isTerminal: () => {
const currentResult = currentOperation.result._lroData;
const currentResult = currentOperation.result._response[LROSYM];
if (!currentResult) {
throw new Error("Expected lroData to determine terminal status");
}
Expand Down Expand Up @@ -51,7 +51,8 @@ export function createLocationStrategy<TResult extends BaseResult>(
const result = await sendOperationFn(pollingArgs, pollingSpec);

// Update latest polling url
lastKnownPollingUrl = result._lroData?.location || lastKnownPollingUrl;
lastKnownPollingUrl =
result._response[LROSYM]?.location || lastKnownPollingUrl;

// Update lastOperation result
currentOperation = {
Expand Down
11 changes: 6 additions & 5 deletions src/lro/lroPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HttpOperationResponse,
WebResource
} from "@azure/core-http";
import { LROOperationResponse, LROSYM } from "./models";
import { getLROData } from "./requestUtils";

export function lroPolicy() {
Expand All @@ -23,12 +24,12 @@ class LROPolicy extends BaseRequestPolicy {
public async sendRequest(
webResource: WebResource
): Promise<HttpOperationResponse> {
let result = await this._nextPolicy.sendRequest(webResource);
let result: LROOperationResponse = await this._nextPolicy.sendRequest(
webResource
);
const _lroData = getLROData(result);

if (webResource.shouldDeserialize !== undefined) {
const _lroData = getLROData(result);
result.parsedBody = { ...result.parsedBody, _lroData };
}
result[LROSYM] = _lroData;

return result;
}
Expand Down
5 changes: 3 additions & 2 deletions src/lro/lroPoller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import {
BaseResult,
LROOperationState,
LROOperationStep,
FinalStateVia
FinalStateVia,
LROSYM
} from "./models";
import { makeOperation } from "./operation";
import { createBodyPollingStrategy } from "./bodyPollingStrategy";
Expand Down Expand Up @@ -114,7 +115,7 @@ function getPollingStrategy<TResult extends BaseResult>(
sendOperationFn: SendOperationFn<TResult>,
finalStateVia?: FinalStateVia
) {
const lroData = initialOperation.result._lroData;
const lroData = initialOperation.result._response[LROSYM];

if (!lroData) {
const error = new RestError(
Expand Down
19 changes: 17 additions & 2 deletions src/lro/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
OperationArguments,
OperationSpec,
RestResponse,
HttpMethods
HttpMethods,
HttpOperationResponse
} from "@azure/core-http";
import { PollOperationState, PollOperation } from "@azure/core-lro";
export const LROSYM = Symbol("LROData");

export type FinalStateVia =
| "azure-async-operation"
Expand All @@ -22,8 +24,21 @@ export interface LROResponseInfo {
status?: string;
}

/**
* Extended operation response for LROs
*/
export type LROOperationResponse = HttpOperationResponse & {
/**
* Symbol that contains LRO details
*/
[LROSYM]?: LROResponseInfo;
};

export interface BaseResult extends RestResponse {
_lroData?: LROResponseInfo;
/**
* The underlying HTTP response containing both raw and deserialized response data.
*/
_response: LROOperationResponse;
}

export interface LROOperationStep<TResult extends BaseResult> {
Expand Down
4 changes: 2 additions & 2 deletions src/lro/operation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BaseResult, LROOperationState, LROOperation } from "./models";
import { BaseResult, LROOperationState, LROOperation, LROSYM } from "./models";

/**
* Creates a copy of the operation from a given State
Expand Down Expand Up @@ -38,7 +38,7 @@ async function update<TResult extends BaseResult>(

const { sendFinalRequest, poll, isTerminal } = state.pollingStrategy;
const currentResponse = state.lastOperation;
const currentLroData = currentResponse.result._lroData;
const currentLroData = currentResponse.result._response[LROSYM];

if (!currentLroData) {
throw new Error(
Expand Down
Loading

0 comments on commit fce7fe1

Please sign in to comment.