From 725ff6d63c6ae094784f5432a18f67f9139244e3 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 23 Sep 2026 10:21:53 +0200 Subject: [PATCH 1/4] fix: suppress $batch path for REST services --- CHANGELOG.md | 1 + lib/compile/csdl2openapi.js | 7 ++++++- lib/compile/index.js | 1 + test/lib/compile/openapi.test.js | 13 +++++++++++++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9cbe10..5ceaa3a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Entities that are transitively autoexposed and should still be considered readonly, do not generate documentation for write endpoints anymore - Implicitly auto-exposed composition targets (annotated `@cds.autoexposed` by the CDS compiler) no longer generate top-level GET paths that CAP would reject with 405 - OpenAPI compilation is now pure: the input CSN is no longer mutated during compilation +- Services annotated with `@protocol: 'rest'` no longer include a `/$batch` path in the generated OpenAPI document ### Security ## [1.6.0] - 2026-08-04 diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 78b844d..75a63ee 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -8,6 +8,10 @@ const { Diagram } = require('./diagram') const pluralize = require('pluralize') const DEBUG = cds.debug('openapi'); // Initialize cds.debug with the 'openapi' +function isODataProtocol(protocol) { + return !protocol || protocol.startsWith('odata'); +} + //TODO // - Core.Example for complex types // - reduce number of loops over schemas @@ -96,6 +100,7 @@ module.exports.csdl2openapi = function ( url: serviceRoot, servers: serversObject, odataVersion, + protocol, scheme = 'https', host = 'localhost', basePath = '/service-root', @@ -458,7 +463,7 @@ module.exports.csdl2openapi = function ( DEBUG?.(`Unrecognized entity container child: ${name}`); } }) - if (resources.length > 0) pathItemBatch(paths, container); + if (resources.length > 0 && isODataProtocol(protocol)) pathItemBatch(paths, container); return Object.keys(paths).sort().reduce((p, c) => (p[c] = paths[c], p), {}); } diff --git a/lib/compile/index.js b/lib/compile/index.js index d93acd4..3585800 100644 --- a/lib/compile/index.js +++ b/lib/compile/index.js @@ -94,6 +94,7 @@ function _getOpenApi(csdl, options, serviceName = "") { } sOptions.url = url; + sOptions.protocol = protocol; const openapi = csdl2openapi.csdl2openapi(csdl, sOptions); diff --git a/test/lib/compile/openapi.test.js b/test/lib/compile/openapi.test.js index e21e0b2..ad62e27 100644 --- a/test/lib/compile/openapi.test.js +++ b/test/lib/compile/openapi.test.js @@ -305,6 +305,19 @@ service CatalogService { '@protocol must not be mutated by toOpenApi'); }); + test('REST service does not include /$batch path', () => { + const csn = cds.compile.to.csn(` + @path: '/rest/v1/myRestAPI' + @protocol: 'rest' + service MyRestAPI { + entity Items { key ID : UUID; } + }` + ); + const openapi = toOpenApi(csn); + assert.strictEqual(openapi.paths?.['/$batch'], undefined, + '/$batch must not be present in OpenAPI output for a REST service'); + }); + test('options: Multiple servers', () => { const csn = cds.compile.to.csn(` service A {entity E { key ID : UUID; };};` From 8b0a0de2db3c5cce33ca3d5b229202ea57693dbd Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 23 Sep 2026 10:21:53 +0200 Subject: [PATCH 2/4] fix: suppress $batch path for REST services --- lib/compile/csdl2openapi.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 75a63ee..4ed8909 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -91,7 +91,7 @@ const ER_ANNOTATIONS = Object.freeze( /** * Construct an OpenAPI description from a CSDL document * @param {CSDL} csdl CSDL document - * @param {{ url?: string, servers?: object, odataVersion?: string, scheme?: string, host?: string, basePath?: string, diagram?: boolean, maxLevels?: number, shortActionPaths?: boolean }} options Optional parameters + * @param {{ url?: string, servers?: object, odataVersion?: string, protocol?: string, scheme?: string, host?: string, basePath?: string, diagram?: boolean, maxLevels?: number, shortActionPaths?: boolean }} options Optional parameters * @return {*} OpenAPI description */ module.exports.csdl2openapi = function ( From bd3b00f0e0265ab9cd2d419c3911c1c2e1fdecef Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 23 Sep 2026 12:16:13 +0200 Subject: [PATCH 3/4] Crisper --- lib/compile/csdl2openapi.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index baaf90f..8b7d781 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -9,9 +9,7 @@ const propertyUtil = require('./property-util'); const pluralize = require('pluralize') const DEBUG = cds.debug('openapi'); // Initialize cds.debug with the 'openapi' -function isODataProtocol(protocol) { - return !protocol || protocol.startsWith('odata'); -} +const isODataProtocol = (protocol) => !protocol || protocol.startsWith('odata'); //TODO // - Core.Example for complex types From 4212b8e4a34ab91d6f85995226cdd6a0e10c7214 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Tue, 29 Sep 2026 16:14:41 +0200 Subject: [PATCH 4/4] Remove odata query options in rest scenario --- CHANGELOG.md | 1 + lib/compile/csdl2openapi.js | 22 ++++++++++++---------- test/lib/compile/openapi.test.js | 17 +++++++++++++++++ 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65089ff..03d2d5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). - Implicitly auto-exposed composition targets (annotated `@cds.autoexposed` by the CDS compiler) no longer generate top-level GET paths that CAP would reject with 405 - OpenAPI compilation is now pure: the input CSN is no longer mutated during compilation - Services annotated with `@protocol: 'rest'` no longer include a `/$batch` path in the generated OpenAPI document +- Services annotated with `@protocol: 'rest'` no longer include OData query options (`$filter`, `$top`, `$skip`, `$search`, `$count`, `$orderby`, `$select`, `$expand`) in collection GET operations, as CAP's REST adapter does not support them ### Security ## [1.6.0] - 2026-08-04 diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index e70964c..032ca60 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -806,17 +806,19 @@ module.exports.csdl2openapi = function ( if (target && sourceName != targetName) operation.tags.push(normaliseTag(targetName)); customParameters(operation, byKey ? readByKeyRestrictions || readRestrictions : readRestrictions); - if (collection) { - optionTop(operation.parameters, target, restrictions); - optionSkip(operation.parameters, target, restrictions); - if (csdl.$Version >= '4.0') optionSearch(operation.parameters, target, restrictions); - optionFilter(operation.parameters, target, restrictions); - optionCount(operation.parameters, target); - optionOrderBy(operation.parameters, element, target, restrictions); - } + if (isODataProtocol(protocol)) { + if (collection) { + optionTop(operation.parameters, target, restrictions); + optionSkip(operation.parameters, target, restrictions); + if (csdl.$Version >= '4.0') optionSearch(operation.parameters, target, restrictions); + optionFilter(operation.parameters, target, restrictions); + optionCount(operation.parameters, target); + optionOrderBy(operation.parameters, element, target, restrictions); + } - optionSelect(operation.parameters, element, target, restrictions); - optionExpand(operation.parameters, element, target, nonExpandable); + optionSelect(operation.parameters, element, target, restrictions); + optionExpand(operation.parameters, element, target, nonExpandable); + } pathItem.get = operation; } diff --git a/test/lib/compile/openapi.test.js b/test/lib/compile/openapi.test.js index ad62e27..8a0c7a6 100644 --- a/test/lib/compile/openapi.test.js +++ b/test/lib/compile/openapi.test.js @@ -318,6 +318,23 @@ service CatalogService { '/$batch must not be present in OpenAPI output for a REST service'); }); + test('REST service collection GET does not include OData query options', () => { + const csn = cds.compile.to.csn(` + @path: '/rest/v1/myRestAPI' + @protocol: 'rest' + service MyRestAPI { + entity Items { key ID : UUID; name : String; } + }` + ); + const openapi = toOpenApi(csn); + const getParams = openapi.paths?.['/Items']?.get?.parameters ?? []; + const odataQueryOptions = ['$filter', '$top', '$skip', '$search', '$count', '$orderby', '$select', '$expand']; + const found = getParams + .map(p => p.name ?? p.$ref) + .filter(n => odataQueryOptions.some(o => typeof n === 'string' && (n === o || n.endsWith(o)))); + assert.deepStrictEqual(found, [], `OData query options must not appear on REST collection GET: ${found}`); + }); + test('options: Multiple servers', () => { const csn = cds.compile.to.csn(` service A {entity E { key ID : UUID; };};`