From eb8a196a168aca1c82d56bde876e9dd7dd446f8f Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 10 Jun 2026 13:06:49 +0200 Subject: [PATCH 1/7] Support MediaType annotation --- lib/compile/csdl.js | 2 +- lib/compile/csdl2openapi.js | 88 ++++++++++++++++++ test/lib/compile/csdl2openapi.test.js | 89 +++++++++++++++++++ test/lib/compile/data/TripPin.openapi3.json | 62 ++++++++++++- test/lib/compile/data/csdl-16.1.openapi3.json | 53 ++++++++++- 5 files changed, 291 insertions(+), 3 deletions(-) diff --git a/lib/compile/csdl.js b/lib/compile/csdl.js index ae0b5eb..0b113d5 100644 --- a/lib/compile/csdl.js +++ b/lib/compile/csdl.js @@ -12,7 +12,7 @@ const CDS_TERMS = Object.freeze({ 'FilterRestrictions', 'IndexableByKey', 'InsertRestrictions', 'KeyAsSegmentSupported', 'NavigationRestrictions', 'OperationRestrictions', 'ReadRestrictions', 'SearchRestrictions', 'SelectSupport', 'SkipSupported', 'SortRestrictions', 'TopSupported', 'UpdateRestrictions'], Core: ['AcceptableMediaTypes', 'Computed', 'ComputedDefaultValue', 'DefaultNamespace', 'Description', 'Example', 'Immutable', 'LongDescription', - 'OptionalParameter', 'Permissions', 'SchemaVersion'], + 'MediaType', 'OptionalParameter', 'Permissions', 'SchemaVersion'], JSON: ['Schema'], Validation: ['AllowedValues', 'Exclusive', 'Maximum', 'Minimum', 'Pattern'] }) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index d5f57ae..348da6b 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -653,6 +653,7 @@ module.exports.csdl2openapi = function ( level, navigationPrefix: navigationPath }); + pathItemsForMediaStream({ paths, prefix: path, prefixParameters: parameters, type, name, sourceName }); if (Object.keys(pathItem).filter((i) => i !== "parameters").length === 0) delete paths[path]; @@ -1300,6 +1301,93 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot } } + /** + * Add path and Path Item Object for media stream access ($value) and stream properties of an entity type + * @param {object} options + * @param {object} options.paths The Paths Object to augment + * @param {string} options.prefix Prefix for path (the key-qualified entity path) + * @param {Array} options.prefixParameters Parameter Objects for prefix + * @param {object} options.type Entity type object + * @param {string} options.name Name of the entity set + * @param {string} options.sourceName Name of path source + */ + function pathItemsForMediaStream({ paths, prefix, prefixParameters, type, name, sourceName }) { + if (!type) return; + + if (type.$HasStream) { + const mediaTypes = type[meta.voc.Core.AcceptableMediaTypes]?.map(t => t['$EnumMember'] ?? t) ?? []; + const contentTypes = mediaTypes.length > 0 ? mediaTypes : ['*/*']; + const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); + const lname = splitName(name); + const valuePath = `${prefix}/$value`; + const pathItem = prefixParameters.length > 0 ? { parameters: prefixParameters } : {}; + paths[valuePath] = pathItem; + + pathItem.get = { + summary: `Retrieves the media stream of a single ${pluralize.singular(lname)}.`, + tags: [normaliseTag(sourceName)], + responses: { + 200: { + description: `Retrieved ${pluralize.singular(lname)}`, + content: mediaContent + }, + '4XX': { $ref: '#/components/responses/error' } + } + }; + pathItem.put = { + summary: `Changes the media stream of a single ${pluralize.singular(lname)}.`, + tags: [normaliseTag(sourceName)], + requestBody: { + description: `New media stream for ${pluralize.singular(lname)}`, + required: true, + content: mediaContent + }, + responses: { + 204: { description: 'Success' }, + '4XX': { $ref: '#/components/responses/error' } + } + }; + } + + const properties = propertiesOfStructuredType(type); + Object.keys(properties).forEach(propName => { + const prop = properties[propName]; + if (prop.$Kind === 'NavigationProperty' || prop.$Type !== 'Edm.Stream') return; + const mediaType = prop[meta.voc.Core.MediaType] ?? prop['@Core.MediaType']; + const contentTypes = mediaType ? [mediaType] : ['*/*']; + const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); + const lname = splitName(name); + const propPath = `${prefix}/${propName}`; + const pathItem = prefixParameters.length > 0 ? { parameters: prefixParameters } : {}; + paths[propPath] = pathItem; + + pathItem.get = { + summary: `Retrieves ${splitName(propName)} of a single ${pluralize.singular(lname)}.`, + tags: [normaliseTag(sourceName)], + responses: { + 200: { + description: `Retrieved ${splitName(propName)}`, + content: mediaContent + }, + '4XX': { $ref: '#/components/responses/error' } + } + }; + pathItem.put = { + summary: `Changes ${splitName(propName)} of a single ${pluralize.singular(lname)}.`, + tags: [normaliseTag(sourceName)], + requestBody: { + description: `New value for ${splitName(propName)}`, + required: true, + content: mediaContent + }, + responses: { + 204: { description: 'Success' }, + '4XX': { $ref: '#/components/responses/error' } + } + }; + }); + } + /** * Add paths and Path Item Objects for navigation segments * @param {object} options diff --git a/test/lib/compile/csdl2openapi.test.js b/test/lib/compile/csdl2openapi.test.js index a830567..ebfd341 100644 --- a/test/lib/compile/csdl2openapi.test.js +++ b/test/lib/compile/csdl2openapi.test.js @@ -2900,6 +2900,95 @@ it("Error Logging when name and title are missing", () => { console.log("Error handling executed successfully"); }); +describe("Media stream paths", () => { + it("generates /$value GET and PUT for media entity ($HasStream)", () => { + const csdl = { + $Version: "4.0", + $EntityContainer: "this.Container", + this: { + doc: { + $Kind: "EntityType", + $HasStream: true, + $Key: ["ID"], + ID: { $Type: "Edm.Guid" }, + name: {}, + }, + Container: { + $Kind: "EntityContainer", + docs: { $Collection: true, $Type: "this.doc", $ContainsTarget: true }, + }, + }, + }; + const openapi = lib.csdl2openapi(csdl, {}); + const valuePath = "/docs({ID})/$value"; + assert.ok(openapi.paths[valuePath], `Expected path ${valuePath}`); + assert.ok(openapi.paths[valuePath].get, "Expected GET on $value"); + assert.ok(openapi.paths[valuePath].put, "Expected PUT on $value"); + assert.deepStrictEqual( + Object.keys(openapi.paths[valuePath].get.responses[200].content), + ["*/*"], + "Default content type is */*" + ); + }); + + it("uses @Core.AcceptableMediaTypes for content type in /$value", () => { + const csdl = { + $Version: "4.0", + $EntityContainer: "this.Container", + $Reference: { + dummy: { $Include: [{ $Namespace: "Org.OData.Core.V1", $Alias: "Core" }] }, + }, + this: { + doc: { + $Kind: "EntityType", + $HasStream: true, + "@Core.AcceptableMediaTypes": ["application/pdf", "image/png"], + $Key: ["ID"], + ID: { $Type: "Edm.Guid" }, + }, + Container: { + $Kind: "EntityContainer", + docs: { $Collection: true, $Type: "this.doc", $ContainsTarget: true }, + }, + }, + }; + const openapi = lib.csdl2openapi(csdl, {}); + const valuePath = "/docs({ID})/$value"; + assert.ok(openapi.paths[valuePath], `Expected path ${valuePath}`); + const contentKeys = Object.keys(openapi.paths[valuePath].get.responses[200].content); + assert.deepStrictEqual(contentKeys, ["application/pdf", "image/png"]); + }); + + it("generates GET and PUT for Edm.Stream property", () => { + const csdl = { + $Version: "4.0", + $EntityContainer: "this.Container", + this: { + doc: { + $Kind: "EntityType", + $Key: ["ID"], + ID: { $Type: "Edm.Guid" }, + content: { $Type: "Edm.Stream" }, + }, + Container: { + $Kind: "EntityContainer", + docs: { $Collection: true, $Type: "this.doc", $ContainsTarget: true }, + }, + }, + }; + const openapi = lib.csdl2openapi(csdl, {}); + const propPath = "/docs({ID})/content"; + assert.ok(openapi.paths[propPath], `Expected path ${propPath}`); + assert.ok(openapi.paths[propPath].get, "Expected GET on stream property"); + assert.ok(openapi.paths[propPath].put, "Expected PUT on stream property"); + assert.deepStrictEqual( + Object.keys(openapi.paths[propPath].put.requestBody.content), + ["*/*"], + "Default content type is */*" + ); + }); +}); + describe("CAP / CS01", () => { it("FilterRestrictions, NavigationRestrictions, and SortRestrictions", () => { const csdl = { diff --git a/test/lib/compile/data/TripPin.openapi3.json b/test/lib/compile/data/TripPin.openapi3.json index 90623be..57e2086 100644 --- a/test/lib/compile/data/TripPin.openapi3.json +++ b/test/lib/compile/data/TripPin.openapi3.json @@ -3231,6 +3231,66 @@ } } }, + "/Photos({Id})/$value": { + "parameters": [ + { + "description": "key: Id", + "in": "path", + "name": "Id", + "required": true, + "schema": { + "anyOf": [ + { + "type": "integer", + "format": "int64" + }, + { + "type": "string" + } + ], + "example": "42" + } + } + ], + "get": { + "summary": "Retrieves the media stream of a single photo.", + "tags": [ + "Photos" + ], + "responses": { + "200": { + "description": "Retrieved photo", + "content": { + "image/jpeg": {} + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "put": { + "summary": "Changes the media stream of a single photo.", + "tags": [ + "Photos" + ], + "requestBody": { + "description": "New media stream for photo", + "required": true, + "content": { + "image/jpeg": {} + } + }, + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, "/ResetDataSource": { "post": { "summary": "Invokes action ResetDataSource", @@ -4495,4 +4555,4 @@ } } } -} +} \ No newline at end of file diff --git a/test/lib/compile/data/csdl-16.1.openapi3.json b/test/lib/compile/data/csdl-16.1.openapi3.json index 0405b44..08f4c6d 100644 --- a/test/lib/compile/data/csdl-16.1.openapi3.json +++ b/test/lib/compile/data/csdl-16.1.openapi3.json @@ -1274,6 +1274,57 @@ } } }, + "/Products('{ID}')/$value": { + "parameters": [ + { + "description": "key: ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "get": { + "summary": "Retrieves the media stream of a single product.", + "tags": [ + "Products" + ], + "responses": { + "200": { + "description": "Retrieved product", + "content": { + "*/*": {} + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "put": { + "summary": "Changes the media stream of a single product.", + "tags": [ + "Products" + ], + "requestBody": { + "description": "New media stream for product", + "required": true, + "content": { + "*/*": {} + } + }, + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, "/Products('{ID}')/Category": { "parameters": [ { @@ -2492,4 +2543,4 @@ } } } -} +} \ No newline at end of file From 97e250c004f9df1da8d4794c4789330b716262dd Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Wed, 10 Jun 2026 13:08:16 +0200 Subject: [PATCH 2/7] Changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f2381a..a96d916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added +- Generate `PUT` request for `media data` ### Changed ### Deprecated ### Removed From 962e3182f9ca51700ec77b7d891c4e76a7fee2ce Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Thu, 11 Jun 2026 08:17:09 +0200 Subject: [PATCH 3/7] Test coverage --- lib/compile/csdl2openapi.js | 6 ++---- test/lib/compile/csdl2openapi.test.js | 29 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 348da6b..bc35092 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -1312,8 +1312,6 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot * @param {string} options.sourceName Name of path source */ function pathItemsForMediaStream({ paths, prefix, prefixParameters, type, name, sourceName }) { - if (!type) return; - if (type.$HasStream) { const mediaTypes = type[meta.voc.Core.AcceptableMediaTypes]?.map(t => t['$EnumMember'] ?? t) ?? []; const contentTypes = mediaTypes.length > 0 ? mediaTypes : ['*/*']; @@ -1353,12 +1351,12 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot Object.keys(properties).forEach(propName => { const prop = properties[propName]; if (prop.$Kind === 'NavigationProperty' || prop.$Type !== 'Edm.Stream') return; - const mediaType = prop[meta.voc.Core.MediaType] ?? prop['@Core.MediaType']; + const mediaType = prop[meta.voc.Core.MediaType]; const contentTypes = mediaType ? [mediaType] : ['*/*']; const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); const lname = splitName(name); const propPath = `${prefix}/${propName}`; - const pathItem = prefixParameters.length > 0 ? { parameters: prefixParameters } : {}; + const pathItem = { parameters: prefixParameters }; paths[propPath] = pathItem; pathItem.get = { diff --git a/test/lib/compile/csdl2openapi.test.js b/test/lib/compile/csdl2openapi.test.js index ebfd341..07067f1 100644 --- a/test/lib/compile/csdl2openapi.test.js +++ b/test/lib/compile/csdl2openapi.test.js @@ -2987,6 +2987,35 @@ describe("Media stream paths", () => { "Default content type is */*" ); }); + + it("uses @Core.MediaType annotation on Edm.Stream property", () => { + const csdl = { + $Version: "4.0", + $EntityContainer: "this.Container", + $Reference: { + dummy: { $Include: [{ $Namespace: "Org.OData.Core.V1", $Alias: "Core" }] }, + }, + this: { + doc: { + $Kind: "EntityType", + $Key: ["ID"], + ID: { $Type: "Edm.Guid" }, + content: { $Type: "Edm.Stream", "@Core.MediaType": "application/pdf" }, + }, + Container: { + $Kind: "EntityContainer", + docs: { $Collection: true, $Type: "this.doc", $ContainsTarget: true }, + }, + }, + }; + const openapi = lib.csdl2openapi(csdl, {}); + const propPath = "/docs({ID})/content"; + assert.ok(openapi.paths[propPath], `Expected path ${propPath}`); + assert.deepStrictEqual( + Object.keys(openapi.paths[propPath].get.responses[200].content), + ["application/pdf"] + ); + }); }); describe("CAP / CS01", () => { From 0f6090029304eba2acb676ff9dc7d30a109a442e Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Thu, 11 Jun 2026 09:02:09 +0200 Subject: [PATCH 4/7] Introduce fallback --- lib/compile/csdl2openapi.js | 3 +-- test/lib/compile/csdl2openapi.test.js | 31 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index bc35092..ba3f62a 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -1351,8 +1351,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot Object.keys(properties).forEach(propName => { const prop = properties[propName]; if (prop.$Kind === 'NavigationProperty' || prop.$Type !== 'Edm.Stream') return; - const mediaType = prop[meta.voc.Core.MediaType]; - const contentTypes = mediaType ? [mediaType] : ['*/*']; + const contentTypes = [prop[meta.voc.Core.MediaType] ?? '*/*']; const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); const lname = splitName(name); const propPath = `${prefix}/${propName}`; diff --git a/test/lib/compile/csdl2openapi.test.js b/test/lib/compile/csdl2openapi.test.js index 07067f1..eec0126 100644 --- a/test/lib/compile/csdl2openapi.test.js +++ b/test/lib/compile/csdl2openapi.test.js @@ -3016,6 +3016,37 @@ describe("Media stream paths", () => { ["application/pdf"] ); }); + + it("falls back to */* when @Core.MediaType is a path expression on Edm.Stream property", () => { + const csdl = { + $Version: "4.0", + $EntityContainer: "this.Container", + $Reference: { + dummy: { $Include: [{ $Namespace: "Org.OData.Core.V1", $Alias: "Core" }] }, + }, + this: { + doc: { + $Kind: "EntityType", + $Key: ["ID"], + ID: { $Type: "Edm.Guid" }, + content: { $Type: "Edm.Stream", "@Core.MediaType": { $Path: "mimeType" } }, + mimeType: { default: "application/octet-stream" }, + }, + Container: { + $Kind: "EntityContainer", + docs: { $Collection: true, $Type: "this.doc", $ContainsTarget: true }, + }, + }, + }; + const openapi = lib.csdl2openapi(csdl, {}); + const propPath = "/docs({ID})/content"; + assert.ok(openapi.paths[propPath], `Expected path ${propPath}`); + assert.deepStrictEqual( + Object.keys(openapi.paths[propPath].get.responses[200].content), + ["*/*"], + "Path expression MediaType falls back to */*" + ); + }); }); describe("CAP / CS01", () => { From 74e720f9aed22bc645368494827167b28362cb73 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Thu, 11 Jun 2026 09:44:59 +0200 Subject: [PATCH 5/7] Proper coalesce --- lib/compile/csdl2openapi.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index ba3f62a..c2fcc9e 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -1351,7 +1351,8 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot Object.keys(properties).forEach(propName => { const prop = properties[propName]; if (prop.$Kind === 'NavigationProperty' || prop.$Type !== 'Edm.Stream') return; - const contentTypes = [prop[meta.voc.Core.MediaType] ?? '*/*']; + const mediaTypeAnnotation = prop[meta.voc.Core.MediaType]; + const contentTypes = [typeof mediaTypeAnnotation === 'string' ? mediaTypeAnnotation : '*/*']; const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); const lname = splitName(name); const propPath = `${prefix}/${propName}`; From e01e5140d0c48cae28ed35231548543605b55b25 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Thu, 11 Jun 2026 16:21:26 +0200 Subject: [PATCH 6/7] Apply suggestions --- CHANGELOG.md | 2 +- lib/compile/csdl2openapi.js | 6 +++--- test/lib/compile/data/TripPin.openapi3.json | 14 ++++++++++++-- test/lib/compile/data/csdl-16.1.openapi3.json | 14 ++++++++++++-- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a100fae..9e42a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ## [Unreleased] ### Added -- Generate `PUT` request for `media data` +- Generate media stream paths (GET and PUT) for media entities and `Edm.Stream` properties ### Changed ### Deprecated ### Removed diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index c2fcc9e..0e7b9f3 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -1315,7 +1315,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot if (type.$HasStream) { const mediaTypes = type[meta.voc.Core.AcceptableMediaTypes]?.map(t => t['$EnumMember'] ?? t) ?? []; const contentTypes = mediaTypes.length > 0 ? mediaTypes : ['*/*']; - const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); + const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, { schema: { type: 'string', format: 'binary' } }])); const lname = splitName(name); const valuePath = `${prefix}/$value`; const pathItem = prefixParameters.length > 0 ? { parameters: prefixParameters } : {}; @@ -1353,10 +1353,10 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot if (prop.$Kind === 'NavigationProperty' || prop.$Type !== 'Edm.Stream') return; const mediaTypeAnnotation = prop[meta.voc.Core.MediaType]; const contentTypes = [typeof mediaTypeAnnotation === 'string' ? mediaTypeAnnotation : '*/*']; - const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, {}])); + const mediaContent = Object.fromEntries(contentTypes.map(ct => [ct, { schema: { type: 'string', format: 'binary' } }])); const lname = splitName(name); const propPath = `${prefix}/${propName}`; - const pathItem = { parameters: prefixParameters }; + const pathItem = prefixParameters.length > 0 ? { parameters: prefixParameters } : {}; paths[propPath] = pathItem; pathItem.get = { diff --git a/test/lib/compile/data/TripPin.openapi3.json b/test/lib/compile/data/TripPin.openapi3.json index 57e2086..8b790d9 100644 --- a/test/lib/compile/data/TripPin.openapi3.json +++ b/test/lib/compile/data/TripPin.openapi3.json @@ -3261,7 +3261,12 @@ "200": { "description": "Retrieved photo", "content": { - "image/jpeg": {} + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } } }, "4XX": { @@ -3278,7 +3283,12 @@ "description": "New media stream for photo", "required": true, "content": { - "image/jpeg": {} + "image/jpeg": { + "schema": { + "type": "string", + "format": "binary" + } + } } }, "responses": { diff --git a/test/lib/compile/data/csdl-16.1.openapi3.json b/test/lib/compile/data/csdl-16.1.openapi3.json index 08f4c6d..4a6f1ff 100644 --- a/test/lib/compile/data/csdl-16.1.openapi3.json +++ b/test/lib/compile/data/csdl-16.1.openapi3.json @@ -1295,7 +1295,12 @@ "200": { "description": "Retrieved product", "content": { - "*/*": {} + "*/*": { + "schema": { + "type": "string", + "format": "binary" + } + } } }, "4XX": { @@ -1312,7 +1317,12 @@ "description": "New media stream for product", "required": true, "content": { - "*/*": {} + "*/*": { + "schema": { + "type": "string", + "format": "binary" + } + } } }, "responses": { From 77b6bb9a23167336743ac483955ba0939abf36e5 Mon Sep 17 00:00:00 2001 From: Daniel O'Grady Date: Thu, 11 Jun 2026 16:43:34 +0200 Subject: [PATCH 7/7] Add trailing newline --- test/lib/compile/data/TripPin.openapi3.json | 2 +- test/lib/compile/data/csdl-16.1.openapi3.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/lib/compile/data/TripPin.openapi3.json b/test/lib/compile/data/TripPin.openapi3.json index 8b790d9..31bb1a5 100644 --- a/test/lib/compile/data/TripPin.openapi3.json +++ b/test/lib/compile/data/TripPin.openapi3.json @@ -4565,4 +4565,4 @@ } } } -} \ No newline at end of file +} diff --git a/test/lib/compile/data/csdl-16.1.openapi3.json b/test/lib/compile/data/csdl-16.1.openapi3.json index 4a6f1ff..9daaa5b 100644 --- a/test/lib/compile/data/csdl-16.1.openapi3.json +++ b/test/lib/compile/data/csdl-16.1.openapi3.json @@ -2553,4 +2553,4 @@ } } } -} \ No newline at end of file +}