UNPKG

@elastic.io/odata-library

Version:
247 lines (219 loc) 7.01 kB
const debugLib = require('debug'); const debug = debugLib('ODataClient'); const CsdlConverter = require('../metadata/csdlConverter'); const edmNumericTypes = [ 'Edm.Int16', 'Edm.Int32', 'Edm.Int64', 'Edm.Double', 'Edm.Single', 'Edm.Decimal', ]; module.exports = class ODataClient { constructor(cfg, restClient) { this.cfg = cfg; this.restClient = restClient; } async getMetadata() { if (!this.metadata) { this.metadata = await this.restClient.makeRequest({ url: '/$metadata', method: 'GET', isJson: false, }); } return this.metadata; } async getCsdlConverter() { if (!this.csdlConverter) { const metaData = await this.getMetadata(); this.csdlConverter = await new CsdlConverter(metaData).build(); } return this.csdlConverter; } async getKeysForType(objectType) { const csdlConverter = await this.getCsdlConverter(); return csdlConverter.getKeysForObjectType(objectType); } isV4OData() { return this.cfg.odataVersion === 4 || this.cfg.odataVersion === undefined; } placeValueInUrl(value, fieldInfo) { const urlSafeKeyValue = encodeURIComponent(value); if (this.isV4OData()) { return fieldInfo.wrapValueInQuotesInUrls ? `'${urlSafeKeyValue}'` : urlSafeKeyValue; } // OData V3 if (fieldInfo.type === 'Edm.String') { return `'${urlSafeKeyValue}'`; } if (edmNumericTypes.includes(fieldInfo.type)) { return urlSafeKeyValue; } const lastPartOfType = fieldInfo.type.split('.') .pop(); return `${lastPartOfType}'${urlSafeKeyValue}'`; } async getUrlForObject(object, objectType) { const objectKeys = await this.getKeysForType(objectType); if (objectKeys.length === 0) { throw new Error('Can not create URLs for objects that don\'t have any keys.'); } // Check that all keys are defined if (objectKeys.some(objectKey => object[objectKey.name] === undefined)) { throw new Error('Can not create URL for object as the object is missing some values for keys.'); } const keyValues = objectKeys.map((objectKey) => { const keyValue = object[objectKey.name]; const valueForUrl = this.placeValueInUrl(keyValue, objectKey); if (objectKeys.length > 1 && !this.isV4OData()) { return `${objectKey.name}=${valueForUrl}`; } return valueForUrl; }).join(','); return `/${objectType}(${keyValues})`; } async getAll() { return this.restClient.makeRequest({ url: `${this.cfg.objectType}`, method: 'GET', headers: { Prefer: 'return=representation', Accept: 'application/json', }, }); } async getObjectsPollingByDeltaLink(snapshot, objectType) { if (snapshot.deltaLink) { // Follow Delta link debug('Current delta link found'); return this.restClient.makeRequest({ url: snapshot.deltaLink, method: 'GET', urlIsSegment: false, }); } else { // eslint-disable-line no-else-return debug('No delta link detected. Requesting one...'); return this.restClient.makeRequest({ url: objectType, method: 'GET', headers: { Prefer: 'odata.track-changes' }, }); } } async getObjectById(object, objectType) { const objectUrl = await this.getUrlForObject(object, objectType); return this.restClient.makeRequest({ url: objectUrl, method: 'GET', headers: { Prefer: 'return=representation', Accept: 'application/json', }, }); } async getObjectsByCriteria(criteria) { const url = await this.getUrlForObject(criteria, this.cfg.objectType); return this.restClient.makeRequest({ url, method: 'GET', headers: { Prefer: 'return=representation', Accept: 'application/json', }, }); } async postObject(object, objectType) { return this.restClient.makeRequest({ url: objectType, method: 'POST', body: object, headers: { Prefer: 'return=representation', Accept: 'application/json', }, }); } async removeKeysFromObject(object, objectType) { const objectKeys = await this.getKeysForType(objectType); const objectCopy = JSON.parse(JSON.stringify(object)); objectKeys.forEach((objectKey) => { delete objectCopy[objectKey.name]; }); return objectCopy; } async putObject(object, objectType) { const objectUrl = await this.getUrlForObject(object, objectType); const objectWithoutKeys = await this.removeKeysFromObject(object, objectType); return this.restClient.makeRequest({ url: objectUrl, method: 'PUT', body: objectWithoutKeys, headers: { Prefer: 'return=representation', Accept: 'application/json', }, }); } async patchObject(object, objectType) { const objectUrl = await this.getUrlForObject(object, objectType); const objectWithoutKeys = await this.removeKeysFromObject(object, objectType); return this.restClient.makeRequest({ url: objectUrl, method: 'PATCH', body: objectWithoutKeys, headers: { Prefer: 'return=representation', Accept: 'application/json', 'If-Match': '*', }, }); } async upsertObjectById(object, objectType, updateMethod = 'PATCH') { const updatePut = updateMethod.toLocaleUpperCase() === 'PUT'; const objectKeys = await this.getKeysForType(objectType); const allKeysProvided = objectKeys.every(objectKey => object[objectKey.name] !== undefined); const allKeysOmitted = objectKeys.every(objectKey => object[objectKey.name] === undefined); if (!allKeysOmitted && !allKeysProvided) { throw new Error('Unclear if insert or update operation since only some object keys were provided'); } if (allKeysProvided) { debug(`Will perform update to ${objectType}`); if (updatePut) { await this.putObject(object, objectType); } else { await this.patchObject(object, objectType); } return this.getObjectById(object, objectType); } debug(`Will create a(n) ${objectType}`); return this.postObject(object, objectType); } async deleteObjectById(object, objectType) { const objectUrl = await this.getUrlForObject(object, objectType); return this.restClient.makeRequest({ url: objectUrl, method: 'DELETE', headers: { Prefer: 'return=representation', Accept: 'application/json', 'If-Match': '*', }, }); } async deleteObjectsByCriteria(criteria, eTag, objectType) { const url = await this.getUrlForObject(criteria, objectType); const headers = { Prefer: 'return=representation', Accept: 'application/json', }; if (eTag) { headers['If-Match'] = `W/"'${eTag}'"`; } return this.restClient.makeRequest({ url, method: 'DELETE', headers, }); } };