Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/validate-examples-regression.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pool:

trigger:
- master
- develop

pr:
branches:
Expand Down
5 changes: 5 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Change Log - oav

## 02/08/2021 2.2.7

- Add new rules of 'LRO_RESPONSE_HEADER' and 'LRO_RESPONSE_CODE'.
- Add option of 'isArmCall' to differentiate rulesets for Arm and RpaaS in validation apis.

## 03/12/2021 2.2.6

- Fixed the mock value of 'location' header and 'azure_AsyncOperation' header.
Expand Down
4 changes: 3 additions & 1 deletion lib/liveValidation/liveValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ interface Meta {
export interface ValidateOptions {
readonly includeErrors?: ExtendedErrorCode[];
readonly includeOperationMatch?: boolean;
isArmCall?: boolean;
}

export enum LiveValidatorLoggingLevels {
Expand Down Expand Up @@ -383,7 +384,8 @@ export class LiveValidator {
liveResponse,
info,
this.loader,
options.includeErrors
options.includeErrors,
options.isArmCall
);
} catch (resValidationError) {
const msg =
Expand Down
58 changes: 57 additions & 1 deletion lib/liveValidation/operationValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ export const validateSwaggerLiveResponse = async (
response: LiveResponse,
info: OperationContext,
loader?: LiveValidatorLoader,
includeErrors?: ExtendedErrorCode[]
includeErrors?: ExtendedErrorCode[],
isArmCall?: boolean
) => {
const { operation } = info.operationMatch!;
const { statusCode, body } = response;
Expand Down Expand Up @@ -126,6 +127,9 @@ export const validateSwaggerLiveResponse = async (
const headers = transformLiveHeader(response.headers ?? {}, rsp);
if (rsp.schema !== undefined) {
validateContentType(operation.produces!, headers, false, result);
if (isArmCall && realCode >= 200 && realCode < 300) {
validateLroOperation(operation, statusCode, headers, result);
}
}

const ctx = {
Expand Down Expand Up @@ -256,6 +260,58 @@ const schemaValidateIssueToLiveValidationIssue = (
}
};

const validateLroOperation = (
operation: Operation,
statusCode: string,
headers: StringMap<string>,
result: LiveValidationIssue[]
) => {
if (operation["x-ms-long-running-operation"] === true) {
if (operation._method === "patch" || operation._method === "post") {
if (statusCode !== "202" && statusCode !== "201") {
result.push(issueFromErrorCode("LRO_RESPONSE_CODE", { statusCode }, operation.responses));
} else {
validateLroHeader(operation, headers, result);
}
} else if (operation._method === "delete") {
if (statusCode !== "202" && statusCode !== "204") {
result.push(issueFromErrorCode("LRO_RESPONSE_CODE", { statusCode }, operation.responses));
}
if (statusCode === "202") {
validateLroHeader(operation, headers, result);
}
} else if (operation._method === "put") {
if (statusCode === "202" || statusCode === "201") {
validateLroHeader(operation, headers, result);
} else if (statusCode !== "200") {
result.push(issueFromErrorCode("LRO_RESPONSE_CODE", { statusCode }, operation.responses));
}
}
}
};

const validateLroHeader = (
operation: Operation,
headers: StringMap<string>,
result: LiveValidationIssue[]
) => {
if (
(headers.location === undefined || headers.location === "") &&
(headers["azure-AsyncOperation"] === undefined || headers["azure-AsyncOperation"] === "") &&
(headers["azure-asyncoperation"] === undefined || headers["azure-asyncoperation"] === "")
) {
result.push(
issueFromErrorCode(
"LRO_RESPONSE_HEADER",
{
header: "location or azure-AsyncOperation",
},
operation.responses
)
);
}
};

export const issueFromErrorCode = (
code: ExtendedErrorCode,
param: any,
Expand Down
3 changes: 3 additions & 0 deletions lib/swagger/swaggerTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
xmsSkipUrlEncoding,
xNullable,
xmsAzureResource,
xmsLongRunningOperationOptions,
xmsLongRunningOperationOptionsField,
} from "../util/constants";
import { $id } from "./jsonLoader";

Expand Down Expand Up @@ -143,6 +145,7 @@ export interface Operation {
security?: Security[];
tags?: string[];
[xmsLongRunningOperation]?: boolean;
[xmsLongRunningOperationOptions]?: { [xmsLongRunningOperationOptionsField]: string };
[xmsExamples]?: { [description: string]: SwaggerExample };

// TODO check why do we need provider
Expand Down
2 changes: 2 additions & 0 deletions lib/swaggerValidator/schemaValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ export const validateErrorMessages: { [key in ExtendedErrorCode]?: (params: any)
INVALID_RESPONSE_BODY: strTemplate`Body is required in response but not provided`,
INVALID_RESPONSE_HEADER: strTemplate`Header ${"missingProperty"} is required in response but not provided`,
MISSING_RESOURCE_ID: strTemplate`id is required to return in response of GET/PUT resource calls but not provided`,
LRO_RESPONSE_CODE: strTemplate`Patch/Post long running operation must return 201 or 202, Delete long running operation must return 202 or 204, Put long running operation must return 202 or 201 or 200, but ${"statusCode"} returned`,
LRO_RESPONSE_HEADER: strTemplate`Long running operation should return ${"header"} in header but not provided`,

DISCRIMINATOR_VALUE_NOT_FOUND: strTemplate`Discriminator value "${"data"}" not found`,
ANY_OF_MISSING: strTemplate`Data does not match any schemas from 'anyOf'`,
Expand Down
4 changes: 4 additions & 0 deletions lib/util/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export const xmsSkipUrlEncoding = "x-ms-skip-url-encoding";

export const xmsLongRunningOperation = "x-ms-long-running-operation";

export const xmsLongRunningOperationOptions = "x-ms-long-running-operation-options";

export const xmsLongRunningOperationOptionsField = "final-state-via";

export const xmsDiscriminatorValue = "x-ms-discriminator-value";

export const xmsEnum = "x-ms-enum";
Expand Down
2 changes: 2 additions & 0 deletions lib/util/validationError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ const errorConstants = {
INVALID_RESPONSE_HEADER: { severity: Severity.Error, docUrl: "" },
INVALID_RESPONSE_BODY: { severity: Severity.Critical, docUrl: "" },
MISSING_RESOURCE_ID: { severity: Severity.Critical, docUrl: "" },
LRO_RESPONSE_CODE: { severity: Severity.Critical, docUrl: "" },
LRO_RESPONSE_HEADER: { severity: Severity.Critical, docUrl: "" },
};

const wrapperErrorConstants = {
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oav",
"version": "2.2.6",
"version": "2.2.7",
"author": {
"name": "Microsoft Corporation",
"email": "azsdkteam@microsoft.com",
Expand Down
Loading