import queryString from 'qs';
import Query from './query';
export function InvalidCriteriaException(message) {
this.message = message;
this.name = 'InvalidCriteriaException';
}
/**
* BusinessEndpoint is the main interaction class against non-resourceful sequoia endpoints.
*
* Business endpoints are simple rest-like endpoints. Due to the nature of them, this SDK
* is not (currently) intending to provide anything other than simple validation for
* paths and query strings. Objects are just the plain JSON returned from the service,
* unlike {@link ResourcefulEndpoint} which will send back {@link ResourceCollection} and
* {@link Resource}s.
*
* This class should not be used directly, but should instead be obtained
* from {@link ServiceDescriptor#businessEndpoint}
*
* @param {Transport} transport - Transport instance to use for fetching
* @param {Object} business - JSON object describing this business endpoint
* (fetched from sequoia services 'descriptor')
*
* @example
* // `contents` is used in the rest of the examples as our reference
* // to a ResourcefulEndpoint
* let feeds;
*
* client.generate().then(() => {
* client.service('feed').then((service) => {
* // Get a business endpoint (this is synchronous as the service passed
* // all the necessary data):
* feeds = service.businessEndpoint('feeds', { name: 'UTV-15246' });
* // whatever
* });
* });
*
* @private
*/
class BusinessEndpoint {
constructor(transport, endpoint, pathOptions) {
this.transport = transport;
this.endpoint = endpoint;
let { path } = endpoint;
const errors = [];
const pathParams = Object.assign({ owner: endpoint.tenant }, pathOptions);
(path.match(/([^{]*?).(?=\})/gim) || []).forEach((pathParam) => {
if (!(pathParam in pathParams) && !pathParam.endsWith('?')) {
errors.push(`Required path parameter '${pathParam}' was not supplied`);
}
});
if (errors.length) {
throw new Error(errors.join('\n'));
}
Object.keys(pathParams).forEach((key) => {
path = path.replace(new RegExp(`{${key}[?]?}`), pathParams[key]);
});
// Strip out any optional params:
path = path.replace(/{[^?]+\?}/g, '');
this.endpoint.location = `${this.endpoint.location}${path}`;
}
/**
* Perform an action against the business endpoint. Note, the HTTP method comes from
* the `routes['name'].method` portion of the descriptor
*
* @param {(string|Query)} criteria - A query string to append to the request
* @param {object} options - [fetch options]{@link https://github.github.io/fetch/#options}
*
* @see {@link Transport#fetchWithDefaults}
*
* @throws InvalidCriteriaException
*
* @returns {Promise} - JSON returned from the endpoint
*/
fetch(criteria, options) {
const { method } = this.endpoint;
const fetchOptions = Object.assign({ method }, options);
return this.transport.fetchWithDefaults(
this.endPointUrl(criteria),
fetchOptions
);
}
/**
* Required query parameters for the business endpoint from the
* `routes['name'].inputs.query` portion of the descriptor
*
* Returns the raw objects
*
* @example
* // Returns e.g.
* [{
* "type": "name",
* "meta": [{
* "sequoiaType": "name"
* }],
* "invalids": ["" ],
* "name": "count",
* "required": false
* },
* {
* "type": "string",
* "description": "language to localise response to",
* "invalids": [""],
* "name": "lang",
* "required": false
* }]
*
* @type {object[]}
*/
get requiredQueryParameters() {
const { inputs } = this.endpoint;
if (inputs && inputs.query) {
return inputs.query.filter(query => query.required === true);
}
return [];
}
/**
* Returns an array of query parameter names
*
* @example
* // Returns e.g.
* ['currency', 'price']
*
* @type {string[]}
*/
get requiredQueryParameterNames() {
return this.requiredQueryParameters.map(q => q.name);
}
// Private methods
/**
* Return a query string to append to the HTTP call. Will default to appending
* `?owner=<owner>`
*
* @param {(string|Query)?} criteria - A (potential) query string to append to the request
*
* @private
*
* @throws InvalidCriteriaException
*
* @returns {string}
*/
criteriaToQuery(criteria) {
let query = '';
if (typeof criteria === 'string') {
if (criteria !== '') {
query += `?${criteria}`;
}
} else if (criteria instanceof Query) {
query += `?${criteria.toQueryString()}`;
}
const requestQueryParams = Object.keys(
queryString.parse(query, { ignoreQueryPrefix: true })
);
this.requiredQueryParameterNames.forEach((requiredValue) => {
if (!requestQueryParams.includes(requiredValue)) {
throw new InvalidCriteriaException(
`Required query parameter '${requiredValue}' was not passed`
);
}
});
return query;
}
/**
* Get the full URL to the business endpoint/item we will send the request to
*
* @param {(string|Query)?} criteria - A (potential) query string to append to the request
*
* @see {@link BusinessEndpoint#criteriaToQuery}
*
* @private
*
* @throws InvalidCriteriaException
*
* @returns {string}
*/
endPointUrl(criteria) {
return `${this.endpoint.location}${this.criteriaToQuery(criteria)}`;
}
}
export default BusinessEndpoint;