Skip to content

Commit

Permalink
Add resourceId to operationInfo (#1055)
Browse files Browse the repository at this point in the history
* attempt to resolve related resourceIds from the jsonPath provided with each error
* add resourcesIds to LiveValidationIssue type
  • Loading branch information
scbedd authored Jan 8, 2025
1 parent bf50f77 commit e6a0c2b
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 28 deletions.
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

## 10/23/2024 3.6.0

- Update `LiveValidationIssue` to include `resourceIds`, an array of the located resourceIds associated with the erroneous jsonPaths.
- Bump `jsonpath-plus` dependency to `^10.2.0`.

## 10/15/2024 3.5.1

- During example generation, include min/max in default titles.
Expand Down
1 change: 1 addition & 0 deletions lib/liveValidation/liveValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export interface RequestResponseLiveValidationResult {
export type LiveValidationIssue = {
code: ApiValidationErrorCode;
pathsInPayload: string[];
resourceIds?: string[];
documentationUrl?: string;
} & Omit<SchemaValidateIssue, "code">;

Expand Down
84 changes: 78 additions & 6 deletions lib/liveValidation/operationValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ export const validateSwaggerLiveRequest = async (
result,
operationContext,
isArmCall,
logging
logging,
body
);

return result;
Expand Down Expand Up @@ -211,7 +212,8 @@ export const validateSwaggerLiveResponse = async (
result,
operationContext,
isArmCall,
logging
logging,
body
);

return result;
Expand Down Expand Up @@ -278,18 +280,71 @@ const validateContentType = (
}
};

/**
* Finds the resource ID for a given JSON path. Example inputs:
* - $.properties.lastname
* - $.properties.groups[0].variable
* @param bodyPayload The full payload object.
* @param jsonPath The JSON path referring to the problematic property.
* @returns The resource ID, or undefined if not found.
*/
const findResourceId = (bodyPayload: any, jsonPath: string): string | undefined => {
// schemaValidateIssueToLiveValidationIssue will provide a valid jsonPath to this function
const keys = jsonPath
.replace(/^\$\./, "") // Remove the leading "$."
.split(/\.|\[(\d+)\]/) // Split by dots or array brackets
.filter((key) => key !== undefined && key !== "");

let current: any = bodyPayload;
const stack: any[] = [];

for (const key of keys) {
if (current && typeof current === "object") {
stack.push(current);

if (Array.isArray(current)) {
// Handle array indices
const index = parseInt(key, 10);
if (!isNaN(index) && index < current.length) {
current = current[index];
} else {
return undefined;
}
} else if (key in current) {
current = current[key];
} else {
return undefined;
}
} else {
return undefined;
}
}

// notice we only ever check for parent id. This means that we will never accidentally grab the value of an id
// that has been added erroneously to the payload. (for instance if payload is to an id field that SHOULD NOT be set.)
for (let i = stack.length - 1; i >= 0; i--) {
const parent = stack[i];
if (parent && typeof parent === "object" && "id" in parent) {
return parent.id;
}
}

return undefined; // No `id` field found
};

export const schemaValidateIssueToLiveValidationIssue = (
input: SchemaValidateIssue[],
operation: Operation,
ctx: SchemaValidateContext,
output: LiveValidationIssue[],
_operationContext: OperationContext,
_isArmCall?: boolean,
_logging?: LoggingFn
_logging?: LoggingFn,
_bodyPayload?: any
) => {
for (const i of input) {
const issue = i as Writable<LiveValidationIssue>;

issue.resourceIds = [];
issue.documentationUrl = "";

const source = issue.source as Writable<SourceLocation>;
Expand All @@ -313,7 +368,16 @@ export const schemaValidateIssueToLiveValidationIssue = (
if (isBodyIssue && (path.length > 5 || !isMissingRequiredProperty)) {
path = "$" + path.substr(5);
issue.jsonPathsInPayload[idx] = path;
return jsonPathToPointer(path);
const resolvedJsonPath = jsonPathToPointer(path);

if (ctx.isResponse && isBodyIssue) {
const resourceId = findResourceId(_bodyPayload, path);
if (resourceId) {
issue.resourceIds!.push(resourceId);
}
}

return resolvedJsonPath;
}

if (isMissingRequiredProperty) {
Expand All @@ -340,7 +404,15 @@ export const schemaValidateIssueToLiveValidationIssue = (
issue.message = meta.message;
}

return jsonPathToPointer(path);
const resolvedJsonPath = jsonPathToPointer(path);

if (ctx.isResponse && isBodyIssue) {
const resourceId = findResourceId(_bodyPayload, path);
if (resourceId) {
issue.resourceIds!.push(resourceId);
}
}
return resolvedJsonPath;
});

if (!skipIssue) {
Expand Down
36 changes: 18 additions & 18 deletions package-lock.json

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

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oav",
"version": "3.5.1",
"version": "3.6.0",
"author": {
"name": "Microsoft Corporation",
"email": "[email protected]",
Expand Down Expand Up @@ -31,7 +31,7 @@
"json-merge-patch": "^1.0.2",
"json-pointer": "^0.6.2",
"json-schema-traverse": "^0.4.1",
"jsonpath-plus": "^10.0.0",
"jsonpath-plus": "^10.2.0",
"junit-report-builder": "^3.0.0",
"lodash": "^4.17.21",
"md5-file": "^5.0.0",
Expand Down Expand Up @@ -138,4 +138,4 @@
"jest-junit": {
"output": "test-results.xml"
}
}
}
Loading

0 comments on commit e6a0c2b

Please sign in to comment.