diff --git a/lib/setModel.js b/lib/setModel.js index dffc99a..da9ca0d 100644 --- a/lib/setModel.js +++ b/lib/setModel.js @@ -31,13 +31,45 @@ async function compileModel(path) { if (!compiled.definitions || Object.keys(compiled.definitions).length === 0) { throw new Error(`Compiled CDS model is invalid or empty for path: ${path}`) } - try { - compiled = cds.compile.for.odata(compiled) // needed for drafts - } catch { - // nothing to do + compiled = cds.compile.for.odata(compiled) // to include drafts + const serviceInfo = cds.compile.to.serviceinfo(compiled) + + // merge with definitions + for (const info of serviceInfo) { + const def = compiled.definitions[info.name] + Object.assign(def, info) + } + + const _entities_in = service => { + const exposed = [], + { entities } = service + for (let each in entities) { + const e = entities[each] + if (e['@cds.autoexposed'] && !e['@cds.autoexpose']) continue + if (/DraftAdministrativeData$/.test(e.name)) continue + if (/[._]texts$/.test(e.name)) continue + if (cds.env.effective.odata.containment && service.definition._containedEntities.has(e.name)) continue + exposed.push(each) + } + return exposed } - augmentModel(compiled) + compiled.services + .flatMap(srv => srv.endpoints.map(endpoint => ({ srv, endpoint }))) + .map(({ srv, endpoint }) => { + const entities = _entities_in(srv) + for (const e of entities) { + const path = endpoint.path + e.replace(/\./g, '_') + const def = compiled.definitions[srv.name + '.' + e] + def.endpoints ??= [] + def.endpoints.push({ kind: endpoint.kind, path }) + // Add fully qualified entity names to each service as 'exposedEntities' + } + }) + + for (const service of compiled.services) { + service.exposedEntities = _entities_in(service, compiled) + } const endTime = Date.now() const compileDuration = endTime - startTime @@ -68,101 +100,6 @@ async function refreshModel(path) { } } -// --- Helper functions below --- - -// Augments the compiled CDS model with endpoints and exposed entities -function augmentModel(compiled) { - for (const defName in compiled.definitions) { - // Add name for each definition - const def = compiled.definitions[defName] - def.name = defName - } - - const _entities_in = (srv, compiled) => { - const exposed = [] - const entities = Object.keys(compiled.definitions).filter(name => name.startsWith(srv.name + '.')) - for (let each of entities) { - const e = compiled.definitions[each] - if (e['@cds.autoexposed'] && !e['@cds.autoexpose']) continue - if (/DraftAdministrativeData$/.test(e.name)) continue - if (/[._]texts$/.test(e.name)) continue - // ignore for now - // if (cds.env.effective.odata.containment && service.definition._containedEntities.has(e.name)) continue - exposed.push(each) - } - return exposed - } - - // construct endpoint for each entity and add it to its definition - Object.keys(compiled.definitions) - .filter(name => compiled.definitions[name].kind === 'service') - .map(name => { - const srv = compiled.definitions[name] - srv.endpoints = getEndpoints(srv) - return srv - }) - .flatMap(srv => srv.endpoints.map(endpoint => ({ srv, endpoint }))) - .map(({ srv, endpoint }) => { - const entities = _entities_in(srv, compiled) - srv.exposedEntities = [] - for (const e of entities) { - const eRelName = e.slice(srv.name.length + 1) - srv.exposedEntities.push(eRelName) - const path = endpoint.path + eRelName.replace(/\./g, '_') - const def = compiled.definitions[e] - def.endpoints ??= [] - def.endpoints.push({ kind: endpoint.kind, path }) - } - }) -} - -// Partially taken over from @sap/cds, to avoid `compile.for.nodejs` and `compile.to.serviceinfo` -// or starting the real application. -// Custom servers (with paths defined in code) are not supported. -// TODO: Check how it works in Java. -const getEndpoints = srv => { - const _slugified = name => - /[^.]+$/ - .exec(name)[0] //> my.very.CatalogService --> CatalogService - .replace(/Service$/, '') //> CatalogService --> Catalog - .replace(/_/g, '-') //> foo_bar_baz --> foo-bar-baz - .replace(/([a-z0-9])([A-Z])/g, (_, c, C) => c + '-' + C) //> ODataFooBarX9 --> OData-Foo-Bar-X9 - .toLowerCase() //> FOO --> foo - let annos = srv['@protocol'] - if (annos) { - if (annos === 'none' || annos['='] === 'none') return [] - if (!annos.reduce) annos = [annos] - } else { - annos = [] - for (const kind of ['odata', 'rest']) { - let path = srv['@' + kind] || srv['@protocol.' + kind] - if (path) annos.push({ kind, path }) - } - } - - if (!annos.length) annos.push({ kind: 'odata' }) - - const endpoints = annos.map(each => { - let { kind = each['='] || each, path } = each - if (typeof path !== 'string') path = srv['@path'] || _slugified(srv.name) - if (path[0] !== '/') - path = - { - 'odata-v4': '/odata/v4', - odata: '/odata/v4', - 'odata-v2': '/odata/v2', - rest: '/rest', - hcql: '/hcql' - }[kind] + - '/' + - path // prefix with protocol path - if (!path.endsWith('/')) path = path + '/' - return { kind, path } - }) - - return endpoints -} - // Global cache object for CDS file timestamps const cache = { cdsFiles: new Map() } let changeWatcher = null diff --git a/tests/tools.test.js b/tests/tools.test.js index 80da385..be281f7 100644 --- a/tests/tools.test.js +++ b/tests/tools.test.js @@ -20,7 +20,7 @@ test.describe('tools', () => { assert(Array.isArray(result[0].endpoints), 'Should contain endpoints') assert.equal(result[0].name, 'AdminService', 'Should find Adminservice.Books service') assert.equal(result[0].endpoints[0].kind, 'odata', 'Should contain odata endpoint kind') - assert.equal(result[0].endpoints[0].path, '/odata/v4/admin/', 'Should contain endpoint path') + assert.equal(result[0].endpoints[0].path, 'odata/v4/admin/', 'Should contain endpoint path') }) test('search_cds_definitions: fuzzy search for Books entity', async () => { @@ -35,7 +35,7 @@ test.describe('tools', () => { assert(books[0].name, 'AdminService.Books', 'Should find AdminService.Books entity') assert(Array.isArray(books[0].endpoints), 'Should contain endpoints') assert.equal(books[0].endpoints[0].kind, 'odata', 'Should contain odata endpoint kind') - assert.equal(books[0].endpoints[0].path, '/odata/v4/admin/Books', 'Should contain endpoint path') + assert.equal(books[0].endpoints[0].path, 'odata/v4/admin/Books', 'Should contain endpoint path') }) test('list_all_cds_definition_names: should list all entities', async () => {