diff --git a/CHANGELOG.md b/CHANGELOG.md index c7be42e..9107986 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Removed ### Fixed - Entities that are transitively autoexposed and should still be considered readonly, do not generate documentation for write endpoints anymore +- Back-reference navigation properties on child entities in compositions (the generated `parent` nav pointing back up) are no longer emitted in the OpenAPI read schema ### Security ## [1.6.0] - 2026-08-04 diff --git a/lib/compile/csdl2openapi.js b/lib/compile/csdl2openapi.js index 2fddefd..d3c8fdf 100644 --- a/lib/compile/csdl2openapi.js +++ b/lib/compile/csdl2openapi.js @@ -5,6 +5,7 @@ const cds = require('@sap/cds'); const { CSDLMeta, nameParts, isIdentifier } = require('./csdl'); const { camelCaseToWords: splitName } = require('./string-util'); const { Diagram } = require('./diagram') +const propertyUtil = require('./property-util'); const pluralize = require('pluralize') const DEBUG = cds.debug('openapi'); // Initialize cds.debug with the 'openapi' @@ -109,6 +110,7 @@ module.exports.csdl2openapi = function ( csdl = structuredClone(csdl) csdl.$Version = odataVersion ? odataVersion : '4.01' const meta = new CSDLMeta(csdl) + const { isNavBackReference, isNavWritable, isScalarReadOnly, isScalarWritable, isMandatoryField, isDeltaSupported } = propertyUtil(meta); serviceRoot = serviceRoot ?? (`${scheme}://${host}${basePath}`); const queryOptionPrefix = csdl.$Version <= '4.01' ? '$' : ''; const typesToInline = {}; // filled in schema() and used in inlineTypes() @@ -784,7 +786,7 @@ module.exports.csdl2openapi = function ( isCount: !countRestrictions }) }; - const deltaSupported = element[meta.voc.Capabilities.ChangeTracking] && element[meta.voc.Capabilities.ChangeTracking].Supported; + const deltaSupported = isDeltaSupported(element); if (!byKey && deltaSupported) { // @ts-expect-error - set above operation.responses[200].content['application/json'].schema.properties['@odata.deltaLink'] = { @@ -2136,35 +2138,24 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot const expandRestrictions = type[meta.voc.Capabilities.ExpandRestrictions] ?? {}; const nonExpandableProperties = expandRestrictions.NonExpandableProperties ?? []; Object.keys(properties).forEach(iName => { - if (nonExpandableProperties.includes(iName)) { - return; - } + if (nonExpandableProperties.includes(iName)) return; + const property = properties[iName]; - if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property); - if ((Object.prototype.hasOwnProperty.call(property, '@Common.FieldControl')) && property['@Common.FieldControl'] === 'Mandatory') { required.push(iName) } + + if (isMandatoryField(property)) required.push(iName); + if (property.$Kind == 'NavigationProperty') { - if (property.$Collection && suffix === "" && isCount === true) { - schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count'); - } - if (property[meta.voc.Core.Permissions] != "Read" && !property[meta.voc.Core.Computed] && (property.$ContainsTarget || property.$OnDelete === 'Cascade')) { - if (suffix === SUFFIX.create) { - schemaProperties[iName] = getSchema(property, SUFFIX.create); - } - if (suffix === SUFFIX.update && !property[meta.voc.Core.Immutable]) { - schemaProperties[iName] = getSchema(property, SUFFIX.create); - } - } + if (isNavBackReference(property)) return; + + if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property); + if (property.$Collection && suffix === SUFFIX.read && isCount) schemaProperties[`${iName}@${csdl.$Version === '4.0' ? 'odata.' : ''}count`] = ref('count'); + if (isNavWritable(property) && suffix === SUFFIX.create) schemaProperties[iName] = getSchema(property, SUFFIX.create); + if (isNavWritable(property) && suffix === SUFFIX.update && !property[meta.voc.Core.Immutable]) schemaProperties[iName] = getSchema(property, SUFFIX.create); } else { - if (property[meta.voc.Core.Permissions] === "Read" || property[meta.voc.Core.Computed] || property[meta.voc.Core.ComputedDefaultValue]) { - const index = required.indexOf(iName); - if (index != -1) required.splice(index, 1); - } - if (!(property[meta.voc.Core.Permissions] === "Read" || property[meta.voc.Core.Computed])) { - if (suffix === SUFFIX.create) - schemaProperties[iName] = getSchema(property, SUFFIX.create); - if (suffix === SUFFIX.update && !isKey[iName] && !property[meta.voc.Core.Immutable]) - schemaProperties[iName] = getSchema(property, SUFFIX.update); - } + if (suffix === SUFFIX.read) schemaProperties[iName] = getSchema(property); + if (isScalarReadOnly(property)) required.splice(required.indexOf(iName), 1); + if (isScalarWritable(property) && suffix === SUFFIX.create) schemaProperties[iName] = getSchema(property, SUFFIX.create); + if (isScalarWritable(property) && suffix === SUFFIX.update && !isKey[iName] && !property[meta.voc.Core.Immutable]) schemaProperties[iName] = getSchema(property, SUFFIX.update); } }); diff --git a/lib/compile/property-util.js b/lib/compile/property-util.js new file mode 100644 index 0000000..a014473 --- /dev/null +++ b/lib/compile/property-util.js @@ -0,0 +1,51 @@ +/** + * @typedef {object} CSDLProperty + * @property {string} [$Kind] + * @property {string} [$Type] + * @property {string} [$Partner] + * @property {boolean} [$ContainsTarget] + * @property {boolean} [$Collection] + * @property {string} [$OnDelete] + */ + +/** + * @typedef {object} PropertyPredicates + * @property {(property: CSDLProperty) => boolean} isNavBackReference - True when the nav property is the back-reference side of a composition (child -> parent) + * @property {(property: CSDLProperty) => boolean} isNavWritable - True when the nav property may be written by the client (create/update) + * @property {(property: CSDLProperty) => boolean} isScalarReadOnly - True when the scalar property is read-only or computed (must be removed from required) + * @property {(property: CSDLProperty) => boolean} isScalarWritable - True when the scalar property may be written by the client (create/update) + * @property {(property: CSDLProperty) => boolean} isMandatoryField - True when the property is annotated as a mandatory field control + * @property {(element: CSDLProperty) => boolean} isDeltaSupported - True when the element has change tracking with delta support enabled + */ + +/** + * Returns property predicate functions bound to the given CSDL metadata instance. + * @param {InstanceType} meta + * @returns {PropertyPredicates} + */ +module.exports = (meta) => ({ + isNavBackReference: (property) => + !!property.$Partner + && !!property.$Type + && !!meta.modelElement(property.$Type)?.[property.$Partner]?.$ContainsTarget, + + isNavWritable: (property) => + property[meta.voc.Core.Permissions] !== "Read" + && !property[meta.voc.Core.Computed] + && (property.$ContainsTarget || property.$OnDelete === 'Cascade'), + + isScalarReadOnly: (property) => + property[meta.voc.Core.Permissions] === "Read" + || property[meta.voc.Core.Computed] + || property[meta.voc.Core.ComputedDefaultValue], + + isScalarWritable: (property) => + property[meta.voc.Core.Permissions] !== "Read" + && !property[meta.voc.Core.Computed], + + isMandatoryField: (property) => + property['@Common.FieldControl'] === 'Mandatory', + + isDeltaSupported: (element) => + !!element[meta.voc.Capabilities.ChangeTracking]?.Supported, +}); diff --git a/test/lib/compile/csdl2openapi.test.js b/test/lib/compile/csdl2openapi.test.js index e50aec8..2d00d15 100644 --- a/test/lib/compile/csdl2openapi.test.js +++ b/test/lib/compile/csdl2openapi.test.js @@ -52,6 +52,9 @@ const result12 = require("./data/autoexposed-texts.openapi3.json"); const example13 = require("./data/autoexposed-direct.json"); const result13 = require("./data/autoexposed-direct.openapi3.json"); +const exampleBackRef = require("./data/back-reference.json"); +const resultBackRef = require("./data/back-reference.openapi3.json"); + describe("Examples", () => { test("csdl-16.1", () => { const openapi = lib.csdl2openapi(example1, { diagram: true }); @@ -111,6 +114,11 @@ describe("Examples", () => { const openapi = lib.csdl2openapi(example13, { url: "https://localhost/service-root" }); check(openapi, result13); }); + + test("back-reference", () => { + const openapi = lib.csdl2openapi(exampleBackRef, { diagram: false }); + check(openapi, resultBackRef); + }); }); describe("Edge cases", () => { diff --git a/test/lib/compile/data/back-reference.json b/test/lib/compile/data/back-reference.json new file mode 100644 index 0000000..ba41d19 --- /dev/null +++ b/test/lib/compile/data/back-reference.json @@ -0,0 +1,60 @@ +{ + "$Version": "4.01", + "$Reference": { + "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.json": { + "$Include": [ + { + "$Namespace": "Org.OData.Core.V1", + "$Alias": "Core", + "@Core.DefaultNamespace": true + } + ] + }, + "https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Capabilities.V1.json": { + "$Include": [ + { + "$Namespace": "Org.OData.Capabilities.V1", + "$Alias": "Capabilities" + } + ] + } + }, + "$EntityContainer": "UsageService.Container", + "UsageService": { + "$Alias": "self", + "UsageRecords": { + "$Kind": "EntityType", + "$Key": ["ID"], + "ID": { "$Type": "Edm.Guid" }, + "description": { "$Nullable": true }, + "customReferences": { + "$Kind": "NavigationProperty", + "$Collection": true, + "$Type": "self.UsageRecordCustomReferences", + "$ContainsTarget": true, + "$Partner": "usageRecord" + } + }, + "UsageRecordCustomReferences": { + "$Kind": "EntityType", + "$Key": ["ID"], + "ID": { "$Type": "Edm.Guid" }, + "type": { "$Nullable": true }, + "value": { "$Nullable": true }, + "usageRecord": { + "$Kind": "NavigationProperty", + "$Type": "self.UsageRecords", + "$Nullable": false, + "$Partner": "customReferences" + } + }, + "Container": { + "$Kind": "EntityContainer", + "@Core.Description": "Usage Record Service", + "UsageRecords": { + "$Collection": true, + "$Type": "self.UsageRecords" + } + } + } +} diff --git a/test/lib/compile/data/back-reference.openapi3.json b/test/lib/compile/data/back-reference.openapi3.json new file mode 100644 index 0000000..66d7602 --- /dev/null +++ b/test/lib/compile/data/back-reference.openapi3.json @@ -0,0 +1,911 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Use the title annotation on your CDS service to provide a meaningful title.", + "description": "Usage Record Service", + "version": "" + }, + "x-sap-api-type": "ODATAV4", + "x-odata-version": "4.01", + "x-sap-shortText": "Usage Record Service", + "servers": [ + { + "url": "https://localhost/service-root" + } + ], + "tags": [ + { + "name": "Usage Records" + } + ], + "paths": { + "/$batch": { + "post": { + "summary": "Sends a group of requests", + "description": "Group multiple requests into a single request payload, see [Batch Requests](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_BatchRequests).\n\n*Please note that \"Try it out\" is not supported for this request.*", + "tags": [ + "Batch Requests" + ], + "requestBody": { + "required": true, + "description": "Batch request", + "content": { + "multipart/mixed;boundary=request-separator": { + "schema": { + "type": "string" + }, + "example": "--request-separator\nContent-Type: application/http\nContent-Transfer-Encoding: binary\n\nGET UsageRecords HTTP/1.1\nAccept: application/json\n\n\n--request-separator--" + } + } + }, + "responses": { + "200": { + "description": "Batch response", + "content": { + "multipart/mixed": { + "schema": { + "type": "string" + }, + "example": "--response-separator\nContent-Type: application/http\n\nHTTP/1.1 200 OK\nContent-Type: application/json\n\n{...}\n--response-separator--" + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/UsageRecords": { + "get": { + "summary": "Retrieves a list of usage records.", + "tags": [ + "Usage Records" + ], + "parameters": [ + { + "$ref": "#/components/parameters/top" + }, + { + "$ref": "#/components/parameters/skip" + }, + { + "$ref": "#/components/parameters/search" + }, + { + "name": "$filter", + "description": "Filter items by property values, see [Filtering](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionfilter)", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/count" + }, + { + "name": "$orderby", + "description": "Order items by property values, see [Sorting](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionorderby)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "ID desc", + "description", + "description desc" + ] + } + } + }, + { + "name": "$select", + "description": "Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "description" + ] + } + } + }, + { + "name": "$expand", + "description": "The value of $expand query option is a comma-separated list of navigation property names, stream property names, or $value indicating the stream content of a media-entity. The corresponding related entities and stream values will be represented inline, see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "*", + "customReferences" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Retrieved usage records", + "content": { + "application/json": { + "schema": { + "type": "object", + "title": "Collection of UsageRecords", + "properties": { + "@count": { + "$ref": "#/components/schemas/count" + }, + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageService.UsageRecords" + } + } + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "post": { + "summary": "Creates a single usage record.", + "tags": [ + "Usage Records" + ], + "requestBody": { + "description": "New usage record", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecords-create" + } + } + } + }, + "responses": { + "201": { + "description": "Created usage record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecords" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/UsageRecords({ID})": { + "parameters": [ + { + "description": "key: ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + } + ], + "get": { + "summary": "Retrieves a single usage record.", + "tags": [ + "Usage Records" + ], + "parameters": [ + { + "name": "$select", + "description": "Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "description" + ] + } + } + }, + { + "name": "$expand", + "description": "The value of $expand query option is a comma-separated list of navigation property names, stream property names, or $value indicating the stream content of a media-entity. The corresponding related entities and stream values will be represented inline, see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "*", + "customReferences" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Retrieved usage record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecords" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "patch": { + "summary": "Changes a single usage record.", + "tags": [ + "Usage Records" + ], + "requestBody": { + "description": "New property values", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecords-update" + } + } + } + }, + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "delete": { + "summary": "Deletes a single usage record.", + "tags": [ + "Usage Records" + ], + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/UsageRecords({ID})/customReferences": { + "parameters": [ + { + "description": "key: ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + } + ], + "get": { + "summary": "Retrieves a list of custom references of a usage record.", + "tags": [ + "Usage Records" + ], + "parameters": [ + { + "$ref": "#/components/parameters/top" + }, + { + "$ref": "#/components/parameters/skip" + }, + { + "$ref": "#/components/parameters/search" + }, + { + "name": "$filter", + "description": "Filter items by property values, see [Filtering](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionfilter)", + "in": "query", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/count" + }, + { + "name": "$orderby", + "description": "Order items by property values, see [Sorting](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionorderby)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "ID desc", + "type", + "type desc", + "value", + "value desc" + ] + } + } + }, + { + "name": "$select", + "description": "Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "type", + "value" + ] + } + } + }, + { + "name": "$expand", + "description": "The value of $expand query option is a comma-separated list of navigation property names, stream property names, or $value indicating the stream content of a media-entity. The corresponding related entities and stream values will be represented inline, see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "*", + "usageRecord" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Retrieved custom references", + "content": { + "application/json": { + "schema": { + "type": "object", + "title": "Collection of UsageRecordCustomReferences", + "properties": { + "@count": { + "$ref": "#/components/schemas/count" + }, + "value": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences" + } + } + } + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "post": { + "summary": "Creates a single custom reference of a usage record.", + "tags": [ + "Usage Records" + ], + "requestBody": { + "description": "New custom reference", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences-create" + } + } + } + }, + "responses": { + "201": { + "description": "Created custom reference", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/UsageRecords({ID})/customReferences({ID_1})": { + "parameters": [ + { + "description": "key: ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + }, + { + "description": "key: ID", + "in": "path", + "name": "ID_1", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + } + ], + "get": { + "summary": "Retrieves a single custom reference of a usage record.", + "tags": [ + "Usage Records" + ], + "parameters": [ + { + "name": "$select", + "description": "Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "type", + "value" + ] + } + } + }, + { + "name": "$expand", + "description": "The value of $expand query option is a comma-separated list of navigation property names, stream property names, or $value indicating the stream content of a media-entity. The corresponding related entities and stream values will be represented inline, see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "*", + "usageRecord" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Retrieved custom reference", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "patch": { + "summary": "Changes a single custom reference of a usage record.", + "tags": [ + "Usage Records" + ], + "requestBody": { + "description": "New property values", + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences-update" + } + } + } + }, + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + }, + "delete": { + "summary": "Deletes a single custom reference of a usage record.", + "tags": [ + "Usage Records" + ], + "responses": { + "204": { + "description": "Success" + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/UsageRecords({ID})/customReferences({ID_1})/usageRecord": { + "parameters": [ + { + "description": "key: ID", + "in": "path", + "name": "ID", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + }, + { + "description": "key: ID", + "in": "path", + "name": "ID_1", + "required": true, + "schema": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + } + } + ], + "get": { + "summary": "Retrieves usage record of a usage record.", + "tags": [ + "Usage Records" + ], + "parameters": [ + { + "name": "$select", + "description": "Select properties to be returned, see [Select](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionselect)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "ID", + "description" + ] + } + } + }, + { + "name": "$expand", + "description": "The value of $expand query option is a comma-separated list of navigation property names, stream property names, or $value indicating the stream content of a media-entity. The corresponding related entities and stream values will be represented inline, see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionexpand)", + "in": "query", + "explode": false, + "schema": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "*", + "customReferences" + ] + } + } + } + ], + "responses": { + "200": { + "description": "Retrieved usage record", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UsageService.UsageRecords" + } + } + } + }, + "4XX": { + "$ref": "#/components/responses/error" + } + } + } + } + }, + "components": { + "schemas": { + "UsageService.UsageRecordCustomReferences": { + "title": "UsageRecordCustomReferences", + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + }, + "type": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "UsageService.UsageRecordCustomReferences-create": { + "title": "UsageRecordCustomReferences (for create)", + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + }, + "type": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + }, + "required": [ + "ID" + ] + }, + "UsageService.UsageRecordCustomReferences-update": { + "title": "UsageRecordCustomReferences (for update)", + "type": "object", + "properties": { + "type": { + "type": "string", + "nullable": true + }, + "value": { + "type": "string", + "nullable": true + } + } + }, + "UsageService.UsageRecords": { + "title": "UsageRecords", + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + }, + "description": { + "type": "string", + "nullable": true + }, + "customReferences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences" + } + }, + "customReferences@count": { + "$ref": "#/components/schemas/count" + } + } + }, + "UsageService.UsageRecords-create": { + "title": "UsageRecords (for create)", + "type": "object", + "properties": { + "ID": { + "type": "string", + "format": "uuid", + "example": "01234567-89ab-cdef-0123-456789abcdef" + }, + "description": { + "type": "string", + "nullable": true + }, + "customReferences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences-create" + } + } + }, + "required": [ + "ID" + ] + }, + "UsageService.UsageRecords-update": { + "title": "UsageRecords (for update)", + "type": "object", + "properties": { + "description": { + "type": "string", + "nullable": true + }, + "customReferences": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UsageService.UsageRecordCustomReferences-create" + } + } + } + }, + "count": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ], + "description": "The number of entities in the collection. Available when using the [$count](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptioncount) query option." + }, + "error": { + "type": "object", + "required": [ + "error" + ], + "properties": { + "error": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "target": { + "type": "string" + }, + "details": { + "type": "array", + "items": { + "type": "object", + "required": [ + "code", + "message" + ], + "properties": { + "code": { + "type": "string" + }, + "message": { + "type": "string" + }, + "target": { + "type": "string" + } + } + } + }, + "innererror": { + "type": "object", + "description": "The structure of this object is service-specific" + } + } + } + } + } + }, + "parameters": { + "top": { + "name": "$top", + "in": "query", + "description": "Show only the first n items, see [Paging - Top](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptiontop)", + "schema": { + "type": "integer", + "minimum": 0 + }, + "example": 50 + }, + "skip": { + "name": "$skip", + "in": "query", + "description": "Skip the first n items, see [Paging - Skip](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionskip)", + "schema": { + "type": "integer", + "minimum": 0 + } + }, + "count": { + "name": "$count", + "in": "query", + "description": "Include count of items, see [Count](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptioncount)", + "schema": { + "type": "boolean" + } + }, + "search": { + "name": "$search", + "in": "query", + "description": "Search items by search phrases, see [Searching](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_SystemQueryOptionsearch)", + "schema": { + "type": "string" + } + } + }, + "responses": { + "error": { + "description": "Error", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } + } +} \ No newline at end of file