Skip to content

fix: use OData V4 path for OpenAPI server URL if no protocol is set to match @sap/cds default behavior - #175

Open
cronossclk wants to merge 2 commits into
cap-js:mainfrom
cronossclk:fix/openapi-default-server-url
Open

fix: use OData V4 path for OpenAPI server URL if no protocol is set to match @sap/cds default behavior#175
cronossclk wants to merge 2 commits into
cap-js:mainfrom
cronossclk:fix/openapi-default-server-url

Conversation

@cronossclk

@cronossclk cronossclk commented Aug 20, 2026

Copy link
Copy Markdown

Before: When OpenAPI generates a document for an OData V4 service, it may advertise the REST base path:

{
  "x-odata-version": "4.01",
  "servers": [
    { "url": "/rest/interfaces/cdc" }
  ]
}

After: The generated servers.url matches the service’s OData V4 runtime endpoint:

{
  "x-odata-version": "4.01",
  "servers": [
    { "url": "/odata/v4/interfaces/cdc" }
  ]
}

This affects only the URL advertised in the OpenAPI document. It does not change:

  • the service implementation
  • the CDS @path
  • the OData payload or metadata
  • explicitly configured REST services
  • manually supplied --openapi:url values

This PR fixes #174


Test results:

npm test                                                                                                                        

> @cap-js/openapi@1.6.0 test
> node --test

undefined
undefined
Error handling executed successfully
▶ Examples
  ✔ csdl-16.1 (15.547ms)
  ✔ TripPin (3.933708ms)
  ✔ annotations (2.112709ms)
  ✔ containment (2.324ms)
  ✔ authorization (0.497042ms)
  ✔ descriptions (2.034792ms)
  ✔ custom-parameters (0.6785ms)
  ✔ immutable-composition (0.480334ms)
  ✔ description-fallback (0.382292ms)
  ✔ autoexposed-texts (0.487125ms)
✔ Examples (29.167542ms)
▶ Edge cases
  ✔ empty input (0.245417ms)
  ✔ omit unused types (0.112208ms)
  ✔ omit unused types with cyclic references (0.157708ms)
  ✔ InsertRestrictions, UpdateRestrictions, ReadRestrictions (1.03325ms)
  ✔ navigation to entity with all CRUD disabled (0.292708ms)
  ✔ circular reference on collect primitive paths (0.312792ms)
  ✔ type definition with @JSON.Schema (0.228375ms)
  ✔ type definition with @Org.OData.JSON.V1.Schema (0.146459ms)
  ✔ no key (0.137792ms)
  ✔ base type not found (0.341084ms)
  ✔ no inherited key (0.18525ms)
  ✔ inherited key (0.192667ms)
  ✔ key-as-segment (0.3095ms)
  ✔ function without parameters (0.099875ms)
  ✔ action without parameters (0.110208ms)
  ✔ function with complex and optional collection parameter (0.218625ms)
  ✔ function with @ parameter aliases (0.133625ms)
  ✔ return type with facets (0.1505ms)
  ✔ delta link, no $batch (0.194458ms)
  ✔ entity set and singleton with non-existing type (0.461084ms)
  ✔ inheritance (0.357709ms)
  ✔ navigation property in complex type (0.409125ms)
  ✔ key aliases (0.333667ms)
  ✔ FilterRestrictions, NavigationRestrictions, SearchRestrictions, and SortRestrictions (0.453292ms)
  ✔ ExpandRestrictions (0.49275ms)
  ✔ non-expandable properties excluded from schema (0.746ms)
  ✔ Default Namespace (0.527125ms)
  ✔ Deep update on container level (0.378125ms)
  ✔ Unknown authorization type (0.196459ms)
  ✔ various types and fishy annotations (0.238916ms)
✔ Edge cases (9.599209ms)
▶ Bound action path naming
  ✔ Fully qualified action names by default (0.250042ms)
  ✔ Simplified action names with shortActionPaths option (0.174208ms)
  ✔ Simplified names with DefaultNamespace even without shortActionPaths (0.16675ms)
✔ Bound action path naming (0.661792ms)
✔ AllowedValues on various Edm types (0.272958ms)
✔ Minimum and Maximum on plain integer and float types (non-anyOf schemas) (0.490208ms)
✔ Error Logging when name and title are missing (0.126209ms)
▶ Media stream paths
  ✔ generates /$value GET and PUT for media entity ($HasStream) (0.343042ms)
  ✔ uses @Core.AcceptableMediaTypes for content type in /$value (0.211958ms)
  ✔ generates GET and PUT for Edm.Stream property (0.19825ms)
  ✔ uses @Core.MediaType annotation on Edm.Stream property (0.168709ms)
  ✔ falls back to */* when @Core.MediaType is a path expression on Edm.Stream property (0.160708ms)
✔ Media stream paths (1.165667ms)
▶ CAP / CS01
  ✔ FilterRestrictions, NavigationRestrictions, and SortRestrictions (0.291834ms)
✔ CAP / CS01 (0.3415ms)
▶ OpenAPI export
  ✔ one service (135.987792ms)
  ✔ one service, namespace (2.82525ms)
  ✔ one service, multiple protocols (3.750667ms)
  ✔ Check for tags object having any duplicate entries  (9.699208ms)
  ✔ multiple services (5.381417ms)
  ✔ multiple services, namespace (5.36125ms)
  ✔ multiple services, multiple protocols (2.746125ms)
  ✔ options: url (3.5885ms)
  ✔ options: diagram (2.32825ms)
  ✔ options: servers (2.750666ms)
  ✔ options: odata-version check server URL (2.432334ms)
  ✔ default service without @protocol renders server URL with odata/v4 (2.970875ms)
  ✔ options: Multiple servers (1.553959ms)
  ✔ options: servers - wrong JSON (1.303ms)
  ✔ options: config-file - without inline options (1.798917ms)
  ✔ options: config-file - with inline options, inline options given precedence (1.4775ms)
  ✔ annotations: root entity property (9.577292ms)
  ✔ odm annotations: entity name and oid property (2.300208ms)
  ✔ odm annotations: not valid names (3.012792ms)
  ✔ odm annotations: @ODM.oid value has no matching property (3.000583ms)
  ✔ odm annotations: @ODM.entityName value not matching the entity name (3.128ms)
  ✔ odm annotations: @ODM.oidReference.entityName annotation is added to the schema (4.0605ms)
  ▶ ER annotations
    ✔ er annotations is correct (3.297625ms)
  ✔ ER annotations (3.37625ms)
  ✔ OpenAPI annotations: @OpenAPI.externalDocs annotation is added to the schema (1.866833ms)
  ✔ OpenAPI annotations: @OpenAPI.Extensions annotation is added to the openapi document (5.934708ms)
  ✔ emits *:cds.compile.to.openapi events (1.605834ms)
  ✔ allows modifying result in event handler (1.291375ms)
  ✔ propagates errors from event handlers (1.266958ms)
  ✔ @mandatory on function parameters with nullable types compiles without error (1.724875ms)
✔ OpenAPI export (237.059958ms)
▶ string-util
  ▶ camelCaseToWords
    ✔ converts underscores to spaces (0.749625ms)
    ✔ converts camelCase to words (0.448459ms)
    ✔ preserves acronyms followed by words (0.061708ms)
    ✔ preserves multiple acronyms followed by words (0.057333ms)
    ✔ handles single word with no changes needed (0.050291ms)
    ✔ handles all uppercase words (0.046333ms)
    ✔ handles mixed underscores and camelCase (0.062542ms)
    ✔ handles PascalCase (0.0605ms)
    ✔ handles consecutive uppercase letters (0.055792ms)
    ✔ handles empty string (0.092667ms)
    ✔ handles string with only underscores (0.058458ms)
    ✔ handles complex combinations (0.041833ms)
    ✔ handles single uppercase letter (0.039834ms)
    ✔ handles numbers in string (0.04675ms)
  ✔ camelCaseToWords (2.457458ms)
✔ string-util (2.655625ms)
✔ test/util.js (35.679ms)
ℹ tests 96
ℹ suites 9
ℹ pass 96
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Lukas Schwabe added 2 commits August 20, 2026 16:42
…o match @sap/cds default behavior

Signed-off-by: Lukas Schwabe <lukas.schwabe@amag.ch>
@daogrady

daogrady commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Hi @cronossclk ,

thank you for reporting this behaviour and for providing a fix!
I'm afraid this change could be considered a breaking change and would thus require us to include it in a major release. In order to get this merged and included in an upcoming minor release, we would have to at least provide a way to restore the former behaviour.
Luckily, we have a certain level of configuration-control via cds.env.openapi, which is already loaded when triggering the compilation to openapi.

I thus propose the following addition to your PR:

  1. after loading the envOptions, check if the user has provided a defaultProtocol option. If not, fall back to "odata"
  2. sanitize the passed value and error out if the user passed an invalid protocol.
  3. use the determined default protocol for services where no protocol is explicitly annotated.

Users can then restore the old behaviour by providing cds.env.openapi.defaultProtocol = "rest", for example by adding it to the cds section in their package.json.

Best,
Daniel

@daogrady
daogrady requested a review from tim-sh August 21, 2026 10:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] openapi compilation defaults to rest on url creation, while cap defaults to odata-v4 when starting the service

2 participants