From cc5d67c0efd77b487393d8d7e45522d1248afcf5 Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 15 Oct 2024 10:50:44 -0400 Subject: [PATCH 1/4] OpenApi path should have a leading slash --- examples/open-api/docs/openapi.json | 6 +++--- .../default/classes/rest/SampleRestResource.cls | 2 +- .../rest/SampleRestResourceWithInnerClass.cls | 2 +- .../rest/SampleRestResourceWithoutApexDocs.cls | 2 +- .../__tests__/open-api-docs-processor.spec.ts | 17 ++++++++++++++++- src/core/openapi/open-api-docs-processor.ts | 7 +------ 6 files changed, 23 insertions(+), 13 deletions(-) diff --git a/examples/open-api/docs/openapi.json b/examples/open-api/docs/openapi.json index 43c4b59f..f2e60a56 100644 --- a/examples/open-api/docs/openapi.json +++ b/examples/open-api/docs/openapi.json @@ -10,7 +10,7 @@ } ], "paths": { - "AccountService/": { + "/AccountService/": { "description": "Account related operations", "get": { "tags": [ @@ -338,7 +338,7 @@ } } }, - "Contact/": { + "/Contact/": { "description": "Contact related operations", "get": { "tags": [ @@ -359,7 +359,7 @@ } } }, - "Order/": { + "/Order/": { "description": "Order related operations", "get": { "tags": [ diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls index fa6b63ce..edb4d6a4 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls @@ -1,7 +1,7 @@ /** * @description Account related operations */ -@RestResource(UrlMapping='/AccountService/*') +@RestResource(UrlMapping='AccountService/*') global with sharing class SampleRestResource { /** * @description Sample HTTP Delete method with references to other types. diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls index f1847a67..c3a46488 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithInnerClass.cls @@ -1,7 +1,7 @@ /** * @description Contact related operations */ -@RestResource(urlMapping='/Contact/*') +@RestResource(UrlMapping='/Contact/*') global with sharing class SampleRestResourceWithInnerClass { /** * @description This is a sample HTTP Get method diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls index c0981306..0742b893 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResourceWithoutApexDocs.cls @@ -1,7 +1,7 @@ /** * @description Order related operations */ -@RestResource(urlMapping='/Order/*') +@RestResource(UrlMapping='/Order/*') global with sharing class SampleRestResourceWithoutApexDocs { @HttpGet global static String doGet(String param1, Reference1 param2) { diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 45817899..1185cf27 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -24,7 +24,22 @@ it('should add a path based on the @UrlResource annotation on the class', functi const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths).toHaveProperty('Account/'); + expect(processor.openApiModel.paths).toHaveProperty('/Account/'); +}); + +it('adds a leading slash to the path if it is missing', function () { + const annotationElementValue = { + key: 'urlMapping', + value: "'Account/*'", + }; + const classMirror = new ClassMirrorBuilder() + .addAnnotation(new AnnotationBuilder().addElementValue(annotationElementValue).build()) + .build(); + + const processor = new OpenApiDocsProcessor(noLogger); + processor.onProcess(classMirror); + + expect(processor.openApiModel.paths).toHaveProperty('/Account/'); }); it('should respect slashes', function () { diff --git a/src/core/openapi/open-api-docs-processor.ts b/src/core/openapi/open-api-docs-processor.ts index 5f52ae21..4c802d82 100644 --- a/src/core/openapi/open-api-docs-processor.ts +++ b/src/core/openapi/open-api-docs-processor.ts @@ -83,11 +83,6 @@ export class OpenApiDocsProcessor { this.logger.error(`Type does not contain urlMapping annotation ${type.name}`); return null; } - - let endpointPath = urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/'); - if (endpointPath.startsWith('/')) { - endpointPath = endpointPath.substring(1); - } - return endpointPath; + return `/${urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/').replace(/^\/+/, '')}`; } } From 26dd744fca7b7dee9dce3f8fe436189a2afc38fc Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 15 Oct 2024 10:54:08 -0400 Subject: [PATCH 2/4] Salesforce already requires the leading slash, so no need for the extra check --- .../default/classes/rest/SampleRestResource.cls | 2 +- .../__tests__/open-api-docs-processor.spec.ts | 17 +---------------- src/core/openapi/open-api-docs-processor.ts | 7 ++++++- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls index edb4d6a4..fa6b63ce 100644 --- a/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls +++ b/examples/open-api/force-app/main/default/classes/rest/SampleRestResource.cls @@ -1,7 +1,7 @@ /** * @description Account related operations */ -@RestResource(UrlMapping='AccountService/*') +@RestResource(UrlMapping='/AccountService/*') global with sharing class SampleRestResource { /** * @description Sample HTTP Delete method with references to other types. diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 1185cf27..5a0aa4f6 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -27,25 +27,10 @@ it('should add a path based on the @UrlResource annotation on the class', functi expect(processor.openApiModel.paths).toHaveProperty('/Account/'); }); -it('adds a leading slash to the path if it is missing', function () { - const annotationElementValue = { - key: 'urlMapping', - value: "'Account/*'", - }; - const classMirror = new ClassMirrorBuilder() - .addAnnotation(new AnnotationBuilder().addElementValue(annotationElementValue).build()) - .build(); - - const processor = new OpenApiDocsProcessor(noLogger); - processor.onProcess(classMirror); - - expect(processor.openApiModel.paths).toHaveProperty('/Account/'); -}); - it('should respect slashes', function () { const annotationElementValue = { key: 'urlMapping', - value: "'v1/Account/*'", + value: "'/v1/Account/*'", }; const classMirror = new ClassMirrorBuilder() .addAnnotation(new AnnotationBuilder().addElementValue(annotationElementValue).build()) diff --git a/src/core/openapi/open-api-docs-processor.ts b/src/core/openapi/open-api-docs-processor.ts index 4c802d82..522d3226 100644 --- a/src/core/openapi/open-api-docs-processor.ts +++ b/src/core/openapi/open-api-docs-processor.ts @@ -83,6 +83,11 @@ export class OpenApiDocsProcessor { this.logger.error(`Type does not contain urlMapping annotation ${type.name}`); return null; } - return `/${urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/').replace(/^\/+/, '')}`; + + // The OpenApi path needs to start with a leading slash, but + // Salesforce @RestResource annotations already require a leading slash, + // so no need to check for it. + // See URL Guidelines: https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_rest_resource.htm + return urlMapping.value.replaceAll('"', '').replaceAll("'", '').replaceAll('/*', '/'); } } From 9493b392a8c8b8836ac1932ec52c9fcca331b83a Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 15 Oct 2024 10:54:54 -0400 Subject: [PATCH 3/4] Salesforce already requires the leading slash, so no need for the extra check --- src/core/openapi/__tests__/open-api-docs-processor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 5a0aa4f6..38b2acd7 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -55,5 +55,5 @@ it('should contain a path with a description when the class has an ApexDoc comme const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths['Account/'].description).toBe('My Description'); + expect(processor.openApiModel.paths['/Account/'].description).toBe('My Description'); }); From 0dc59ab59df1fd4aa565fd1fa8c2d8dc530e710e Mon Sep 17 00:00:00 2001 From: cesarParra Date: Tue, 15 Oct 2024 10:55:30 -0400 Subject: [PATCH 4/4] Salesforce already requires the leading slash, so no need for the extra check --- src/core/openapi/__tests__/open-api-docs-processor.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts index 38b2acd7..bdbbeb1b 100644 --- a/src/core/openapi/__tests__/open-api-docs-processor.spec.ts +++ b/src/core/openapi/__tests__/open-api-docs-processor.spec.ts @@ -39,7 +39,7 @@ it('should respect slashes', function () { const processor = new OpenApiDocsProcessor(noLogger); processor.onProcess(classMirror); - expect(processor.openApiModel.paths).toHaveProperty('v1/Account/'); + expect(processor.openApiModel.paths).toHaveProperty('/v1/Account/'); }); it('should contain a path with a description when the class has an ApexDoc comment', function () {