-
Notifications
You must be signed in to change notification settings - Fork 408
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add logic to rerun validations and show validations in correct …
…file
- Loading branch information
Showing
14 changed files
with
280 additions
and
95 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
136 changes: 136 additions & 0 deletions
136
packages/salesforcedx-vscode-apex/src/commands/oasDocumentChecker.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
/* | ||
* Copyright (c) 2025, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
import { notificationService, WorkspaceContextUtil } from '@salesforce/salesforcedx-utils-vscode'; | ||
import { XMLParser } from 'fast-xml-parser'; | ||
import * as fs from 'fs'; | ||
import * as path from 'path'; | ||
import * as vscode from 'vscode'; | ||
import { nls } from '../messages'; | ||
import { getTelemetryService } from '../telemetry/telemetry'; | ||
import { checkIfESRIsDecomposed, createProblemTabEntriesForOasDocument, processOasDocument } from './oasUtils'; | ||
// This class runs the validation and correction logic on Oas Documents | ||
export class OasDocumentChecker { | ||
private isESRDecomposed: boolean = false; | ||
private static _instance: OasDocumentChecker; | ||
|
||
private constructor() {} | ||
|
||
public static get Instance() { | ||
// Do you need arguments? Make it a regular static method instead. | ||
return this._instance || (this._instance = new this()); | ||
} | ||
|
||
public async initialize(extensionContext: vscode.ExtensionContext) { | ||
await WorkspaceContextUtil.getInstance().initialize(extensionContext); | ||
this.isESRDecomposed = await checkIfESRIsDecomposed(); | ||
} | ||
|
||
/** | ||
* Validates an OpenAPI Document. | ||
* @param isClass - Indicates if the action is for a class or a method. | ||
*/ | ||
public validateOasDocument = async (sourceUri: vscode.Uri | vscode.Uri[]): Promise<void> => { | ||
try { | ||
await vscode.window.withProgress( | ||
{ | ||
location: vscode.ProgressLocation.Notification, | ||
title: 'SFDX: Running validations on OAS Document', | ||
cancellable: true | ||
}, | ||
async () => { | ||
if (Array.isArray(sourceUri)) { | ||
throw nls.localize('invalid_file_for_generating_oas_doc'); | ||
} | ||
|
||
const fullPath = sourceUri ? sourceUri.fsPath : vscode.window.activeTextEditor?.document.uri.fsPath || ''; | ||
|
||
// Step 1: Validate eligibility | ||
if (!this.isFilePathEligible(fullPath)) { | ||
throw nls.localize('invalid_file_for_generating_oas_doc'); | ||
} | ||
// Step 2: Extract openAPI document if embedded inside xml | ||
let openApiDocument: string; | ||
if (fullPath.endsWith('.xml')) { | ||
const xmlContent = fs.readFileSync(fullPath, 'utf8'); | ||
const parser = new XMLParser(); | ||
const jsonObj = parser.parse(xmlContent); | ||
openApiDocument = jsonObj.ExternalServiceRegistration?.schema; | ||
} else { | ||
openApiDocument = fs.readFileSync(fullPath, 'utf8'); | ||
} | ||
// Step 3: Process the OAS document | ||
const processedOasResult = await processOasDocument(openApiDocument, undefined, undefined, true); | ||
|
||
// Step 4: Report/Refresh problems found | ||
createProblemTabEntriesForOasDocument(fullPath, processedOasResult, this.isESRDecomposed); | ||
|
||
const telemetryService = await getTelemetryService(); | ||
// Step 5: Notify Success | ||
notificationService.showInformationMessage( | ||
nls.localize('check_openapi_doc_succeeded', path.basename(fullPath)) | ||
); | ||
telemetryService.sendEventData('OasValidationSucceeded'); | ||
} | ||
); | ||
} catch (error: any) { | ||
void this.handleError(error, 'OasValidationFailed'); | ||
} | ||
}; | ||
|
||
/** | ||
* Handles errors by showing a notification and sending telemetry data. | ||
* @param error - The error to handle. | ||
* @param telemetryEvent - The telemetry event name. | ||
*/ | ||
private handleError = async (error: any, telemetryEvent: string): Promise<void> => { | ||
const telemetryService = await getTelemetryService(); | ||
const errorMessage = error instanceof Error ? error.message : String(error); | ||
notificationService.showErrorMessage(`${nls.localize('check_openapi_doc_failed')}: ${errorMessage}`); | ||
telemetryService.sendException(telemetryEvent, errorMessage); | ||
}; | ||
|
||
private isFilePathEligible = (fullPath: string): boolean => { | ||
// check if yaml or xml, else return false | ||
if (!(fullPath.endsWith('.yaml') || fullPath.endsWith('.externalServiceRegistration-meta.xml'))) { | ||
return false; | ||
} | ||
|
||
// if xml, check registrationProviderType to be ApexRest | ||
if (fullPath.endsWith('.xml')) { | ||
const xmlContent = fs.readFileSync(fullPath, 'utf8'); | ||
const parser = new XMLParser(); | ||
const jsonObj = parser.parse(xmlContent); | ||
const registrationProviderType = jsonObj.ExternalServiceRegistration?.registrationProviderType; | ||
if (registrationProviderType === 'Custom' || registrationProviderType === 'ApexRest') { | ||
return true; | ||
} | ||
} | ||
|
||
// if yaml, find the associated xml and look for registrationProviderType to be ApexRest | ||
if (fullPath.endsWith('.yaml')) { | ||
// check folder in which the file is present | ||
const className = path.basename(fullPath).split('.')[0]; | ||
const dirName = path.dirname(fullPath); | ||
const associatedXmlFileName = `${className}.externalServiceRegistration-meta.xml`; | ||
|
||
const xmlContent = fs.readFileSync(path.join(dirName, associatedXmlFileName), 'utf8'); | ||
const parser = new XMLParser(); | ||
const jsonObj = parser.parse(xmlContent); | ||
const registrationProviderType = jsonObj.ExternalServiceRegistration?.registrationProviderType; | ||
if (registrationProviderType === 'Custom' || registrationProviderType === 'ApexRest') { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
}; | ||
} | ||
|
||
export const validateOpenApiDocument = async (sourceUri: vscode.Uri | vscode.Uri[]): Promise<void> => { | ||
const oasDocumentChecker = OasDocumentChecker.Instance; | ||
await oasDocumentChecker.validateOasDocument(sourceUri); | ||
}; |
71 changes: 71 additions & 0 deletions
71
packages/salesforcedx-vscode-apex/src/commands/oasUtils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
/* | ||
* Copyright (c) 2025, salesforce.com, inc. | ||
* All rights reserved. | ||
* Licensed under the BSD 3-Clause license. | ||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause | ||
*/ | ||
|
||
import { SfProject } from '@salesforce/core-bundle'; | ||
import { workspaceUtils } from '@salesforce/salesforcedx-utils-vscode'; | ||
import * as vscode from 'vscode'; | ||
import { parse } from 'yaml'; | ||
import { nls } from '../messages'; | ||
import OasProcessor from '../oas/documentProcessorPipeline'; | ||
import { ProcessorInputOutput } from '../oas/documentProcessorPipeline/processorStep'; | ||
import { ApexClassOASGatherContextResponse, ApexClassOASEligibleResponse } from '../oas/schemas'; | ||
|
||
export const processOasDocument = async ( | ||
oasDoc: string, | ||
context?: ApexClassOASGatherContextResponse, | ||
eligibleResult?: ApexClassOASEligibleResponse, | ||
isRevalidation?: boolean | ||
): Promise<ProcessorInputOutput> => { | ||
if (isRevalidation || context?.classDetail.annotations.find(a => a.name === 'RestResource')) { | ||
const parsed = parse(oasDoc); | ||
const oasProcessor = new OasProcessor(parsed, eligibleResult); | ||
|
||
const processResult = await oasProcessor.process(); | ||
|
||
return processResult; | ||
} | ||
throw nls.localize('invalid_file_for_generating_oas_doc'); | ||
}; | ||
|
||
export const createProblemTabEntriesForOasDocument = ( | ||
fullPath: string, | ||
processedOasResult: ProcessorInputOutput, | ||
isESRDecomposed: boolean | ||
) => { | ||
const uri = vscode.Uri.parse(fullPath); | ||
OasProcessor.diagnosticCollection.clear(); | ||
|
||
const adjustErrors = processedOasResult.errors.map(result => { | ||
// if embedded inside of ESR.xml then position is hardcoded because of `apexActionController.createESRObject` | ||
const lineAdjustment = isESRDecomposed ? 0 : 4; | ||
const startCharacterAdjustment = isESRDecomposed ? 0 : 11; | ||
const range = new vscode.Range( | ||
result.range.start.line + lineAdjustment, | ||
result.range.start.character + result.range.start.line <= 1 ? startCharacterAdjustment : 0, | ||
result.range.end.line + lineAdjustment, | ||
result.range.end.character + result.range.start.line <= 1 ? startCharacterAdjustment : 0 | ||
); | ||
|
||
return new vscode.Diagnostic(range, result.message, result.severity); | ||
}); | ||
OasProcessor.diagnosticCollection.set(uri, adjustErrors); | ||
}; | ||
|
||
/** | ||
* Reads sfdx-project.json and checks if decomposeExternalServiceRegistrationBeta is enabled. | ||
* @returns boolean - true if sfdx-project.json contains decomposeExternalServiceRegistrationBeta | ||
*/ | ||
export const checkIfESRIsDecomposed = async (): Promise<boolean> => { | ||
const projectPath = workspaceUtils.getRootWorkspacePath(); | ||
const sfProject = await SfProject.resolve(projectPath); | ||
const sfdxProjectJson = sfProject.getSfProjectJson(); | ||
if (sfdxProjectJson.getContents().sourceBehaviorOptions?.includes('decomposeExternalServiceRegistrationBeta')) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}; |
Oops, something went wrong.