@elastic.io/odata-library
Version:
Re-usable OData client library
247 lines (219 loc) • 7.01 kB
JavaScript
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,
});
}
};