Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add resourceId to operationInfo #1055

Merged
merged 14 commits into from
Jan 8, 2025
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
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",
scbedd marked this conversation as resolved.
Show resolved Hide resolved
"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