@sap-cloud-sdk/odata-common
Version:
SAP Cloud SDK for JavaScript common functions of OData client generator and OpenAPI clint generator.
237 lines • 9.73 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ODataRequest = void 0;
const util_1 = require("@sap-cloud-sdk/util");
const connectivity_1 = require("@sap-cloud-sdk/connectivity");
const http_client_1 = require("@sap-cloud-sdk/http-client");
const internal_1 = require("@sap-cloud-sdk/http-client/internal");
const internal_2 = require("@sap-cloud-sdk/connectivity/internal");
const odata_request_traits_1 = require("./odata-request-traits");
/**
* OData request configuration for an entity type.
* @template EntityT - Type of the entity to setup a request for.
*/
class ODataRequest {
/**
* Creates an instance of ODataRequest.
* @param config - Configuration of the request.
* @param _destination - Destination to setup the request against.
*/
constructor(config, _destination) {
this.config = config;
this._destination = _destination;
}
set destination(dest) {
if (dest) {
const sanitized = (0, connectivity_1.sanitizeDestination)(dest);
(0, internal_2.assertHttpDestination)(sanitized);
this._destination = sanitized;
}
}
get destination() {
return this._destination;
}
/**
* Constructs an absolute URL for the given request.
* @returns The absolute URL for the request.
*/
url() {
return `${(0, util_1.removeTrailingSlashes)(this.resourceUrl())}${this.config.appendedPaths.join('')}${this.query()}`;
}
/**
* Constructs a URL relative to the destination.
* @param includeBasePath - Whether or not to include the base path in the URL.
* @param includeQueryParameters - Whether or not to include the query parameters in the URL.
* @returns The relative URL for the request.
*/
relativeUrl(includeBasePath = true, includeQueryParameters = true) {
const query = includeQueryParameters ? this.query() : '';
return `${(0, util_1.removeTrailingSlashes)(this.relativeResourceUrl(includeBasePath))}${this.config.appendedPaths.join('')}${query}`;
}
/**
* Specifies whether the destination needs a specific authentication or not.
* @returns A boolean value that specifies whether the destination needs authentication or not.
*/
needsAuthentication() {
return (!!this.destination &&
this.destination.authentication !== 'NoAuthentication');
}
/**
* Returns the service URL for a given OData request.
* @returns The URL of the service the given entity belongs to.
*/
serviceUrl() {
if (!this.destination) {
throw Error('The destination is undefined.');
}
const systemUrl = this.destination.url;
const basePath = typeof this.config.basePath === 'undefined'
? this.config.defaultBasePath
: this.config.basePath;
return `${(0, util_1.removeTrailingSlashes)(systemUrl)}/${(0, util_1.removeSlashes)(basePath)}`;
}
/**
* Returns the service URL relative to the url of the destination for a given OData request.
* @returns The relative URL of the service the given entity belongs to.
*/
relativeServiceUrl() {
const basePath = typeof this.config.basePath === 'undefined'
? this.config.defaultBasePath
: this.config.basePath;
return `${(0, util_1.removeSlashes)(basePath)}`;
}
/**
* Returns the URL to a specific OData .resource, i.e. the entity collection.
* @returns The URL of the resource.
*/
resourceUrl() {
return `${(0, util_1.removeTrailingSlashes)(this.serviceUrl())}/${this.config.resourcePath()}`;
}
/**
* Returns the relative URL to a specific OData resource.
* @param includeBasePath - Whether or not to include the base path in the URL.
* @returns The relative URL of the resource.
*/
relativeResourceUrl(includeBasePath = true) {
const baseUrl = includeBasePath
? (0, util_1.removeTrailingSlashes)(this.relativeServiceUrl())
: '';
const url = `${baseUrl}/${this.config.resourcePath()}`;
return (0, util_1.removeLeadingSlashes)(url);
}
/**
* Get query parameters as string. Leads with `?` if there are parameters to return.
* @returns Query parameter string.
*/
query() {
const parameters = (0, internal_1.mergeOptionsWithPriority)(this.queryParameters());
if (parameters) {
const query = Object.entries(parameters)
.map(([key, value]) => `${key}=${value}`)
.join('&');
return query.length ? `?${query}` : '';
}
return '';
}
/**
* Create object containing all headers, including custom headers for the given request.
* @returns Key-value pairs where the key is the name of a header property and the value is the respective value.
*/
async headers() {
try {
if (!this.destination) {
throw Error('The destination is undefined.');
}
if (Object.keys(this.customHeaders()).length > 0) {
return {
custom: this.customHeaders(),
requestConfig: { ...this.defaultHeaders(), ...this.eTagHeaders() }
};
}
return {
requestConfig: { ...this.defaultHeaders(), ...this.eTagHeaders() }
};
}
catch (error) {
throw new util_1.ErrorWithCause('Constructing headers for OData request failed!', error);
}
}
/**
* Get all custom headers.
* @returns Key-value pairs where the key is the name of a header property and the value is the respective value.
*/
customHeaders() {
return this.config.customHeaders;
}
/**
* Get all default headers. If custom headers are set, those take precedence.
* @returns Key-value pairs where the key is the name of a header property and the value is the respective value.
*/
defaultHeaders() {
const additionalHeaders = this.getAdditionalHeadersForKeys(...Object.keys(this.config.defaultHeaders));
return (0, util_1.mergeIgnoreCase)((0, util_1.pickNonNullish)(this.config.defaultHeaders), additionalHeaders);
}
/**
* Get the eTag related headers, e.g. `if-match`.
* @returns Key-value pairs where the key is the name of a header property and the value is the respective value.
*/
eTagHeaders() {
const additionalIfMatchHeader = this.getAdditionalHeadersForKeys('if-match');
if (Object.keys(additionalIfMatchHeader).length) {
return additionalIfMatchHeader;
}
const eTag = (0, odata_request_traits_1.isWithETag)(this.config)
? this.config.versionIdentifierIgnored
? '*'
: this.config.eTag
: undefined;
return (0, util_1.pickNonNullish)({ 'if-match': eTag });
}
/**
* Execute the given request and return the according promise.
* @returns Promise resolving to the requested data.
*/
async execute() {
if (!this.destination) {
throw Error('The destination cannot be undefined.');
}
return (0, http_client_1.executeHttpRequest)(this.destination, await this.requestConfig(), {
fetchCsrfToken: this.config.fetchCsrfToken
}).catch(error => {
throw constructError(error, this.config.method, this.serviceUrl());
});
}
/**
* Get http request config.
* @returns Promise of http request config with origin.
*/
async requestConfig() {
const defaultConfig = {
headers: await this.headers(),
params: this.queryParameters(),
url: this.relativeUrl(true, false),
method: this.config.method,
parameterEncoder: this.config.parameterEncoder,
middleware: this.config.middlewares,
data: this.config.payload
};
return {
...defaultConfig,
...(0, internal_1.filterCustomRequestConfig)(this.config.customRequestConfiguration)
};
}
getAdditionalHeadersForKeys(...keys) {
const destinationHeaders = (0, util_1.pickIgnoreCase)(this.destination?.headers, ...keys);
const customHeaders = (0, util_1.pickIgnoreCase)(this.customHeaders(), ...keys);
return (0, util_1.mergeIgnoreCase)(destinationHeaders, customHeaders);
}
queryParameters() {
if (Object.keys(this.config.customQueryParameters).length > 0) {
return {
custom: this.config.customQueryParameters,
requestConfig: this.config.queryParameters()
};
}
return {
requestConfig: this.config.queryParameters()
};
}
}
exports.ODataRequest = ODataRequest;
function constructError(error, requestMethod, url) {
if (error.isAxiosError) {
const defaultMessage = `${requestMethod} request to ${url} failed!`;
const s4SpecificMessage = (0, util_1.propertyExists)(error, 'response', 'data', 'error')
? messageFromS4ErrorResponse(error)
: '';
const message = [defaultMessage, s4SpecificMessage].join(' ');
return new util_1.ErrorWithCause(message, error);
}
return error;
}
function messageFromS4ErrorResponse(error) {
return `${(0, util_1.propertyExists)(error.response.data.error, 'message', 'value')
? error.response.data.error.message.value
: ''}${util_1.unixEOL}${JSON.stringify(error.response.data.error)}`;
}
//# sourceMappingURL=odata-request.js.map