import 'isomorphic-fetch';
import { OpenSearchDescription } from './description';
import { OpenSearchPaginator } from './paginator';
import { search, createBaseRequest } from './search';
import { getSupportedTypes } from './formats/';
import { fetchAndCheck, createXHR } from './utils';
import { config } from './config';
/**
* Class to perform searches.
*/
export class OpenSearchService {
/**
* Create an OpenSearchDescription object
* @param {OpenSearchDescription} descriptionDocument The parsed description document
*/
constructor(descriptionDocument) {
this.descriptionDocument = descriptionDocument;
}
/**
* Get the underlying {@link OpenSearchDescription} object.
* @returns {OpenSearchDescription}
*/
getDescription() {
return this.descriptionDocument;
}
/**
* Get the URL for the given parameters.
* @param {object} parameters An object mapping the name or type to the value
* @param {string} [type=null] The preferred transfer type.
* @param {string} [method=null] The preferred HTTP method type.
* @returns {OpenSearchUrl} The resulting URL objec.
*/
getUrl(parameters, type, method) {
const url = this.descriptionDocument.getUrl(parameters, type, method);
if (!url) {
throw new Error(`No URL found for type '${type}' and the given parameters.`);
}
return url;
}
/**
* Returns a base request object for the given parameters. This allows to
* inspect the request values before sending them to the server.
* @param {object} parameters An object mapping the name or type to the value
* @param {string} [type=null] The preferred transfer type.
* @param {string} [method=null] The preferred HTTP method type.
* @returns {object} The search request
*/
createSearchRequest(parameters, type = null, method = null) {
let url = null;
if (!type) {
// try to find a suitable URL
const supportedTypes = getSupportedTypes();
for (let i = 0; i < supportedTypes.length; ++i) {
url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
if (url && url.isCompatible(parameters)) {
break;
}
}
if (!url) {
throw new Error('No compatible URL found.');
}
} else {
url = this.getUrl(parameters, type, method);
}
return createBaseRequest(url, parameters);
}
/**
* Checks whether this URL is compatible with the given parameters
* @param {object} parameters An object mapping the name or type to the value
* @param {string} [type=null] The preferred transfer type.
* @param {string} [method=null] The preferred HTTP method type.
* @param {boolean} [raw=false] Whether the response shall be parsed or returned raw.
* @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
will result in errors.
* @returns {Promise<array>|Promise<Response>} The search result as a Promise
*/
search(parameters, type = null, method = null, raw = false, maxUrlLength = undefined) {
let url = null;
if (!type) {
// try to find a suitable URL
const supportedTypes = getSupportedTypes();
for (let i = 0; i < supportedTypes.length; ++i) {
url = this.descriptionDocument.getUrl(parameters, supportedTypes[i], method);
if (url && url.isCompatible(parameters)) {
break;
}
}
if (!url) {
throw new Error('No compatible URL found.');
}
} else {
url = this.getUrl(parameters, type, method);
}
return search(url, parameters, type, raw, maxUrlLength);
}
/**
* Gets the suggestions for the current search parameters.
* @param {object} parameters An object mapping the name or type to the value
* @param {string} [method=null] The preferred HTTP method type.
* @param {number} [maxUrlLength=undefined] The maximum URL length. URLs longer than that
* will result in errors.
* @returns {Promise<Suggestion[]>} The fetched suggestions.
*/
getSuggestions(parameters, method = null, maxUrlLength = undefined) {
const type = 'application/x-suggestions+json';
let url;
try {
url = this.getUrl(parameters, type, method);
} catch (error) {
return Promise.reject(new Error('No suggestion URL found.'));
}
return search(url, parameters, type, false, maxUrlLength);
}
/**
* Creates a new Paginator object to enable a simpler search result handling
* for multi-page results.
* @param {object} parameters An object mapping the name or type to the value
* @param {string} [type=null] The preferred transfer type.
* @param {string} [method=null] The preferred HTTP method type.
* @param {object} [options] Additional options for the paginator
* @returns {OpenSearchPaginator} The created Paginator object.
*/
getPaginator(parameters, type = null, method = null, options = undefined) {
return new OpenSearchPaginator(
this.getUrl(parameters, type, method), parameters, options
);
}
/**
* Accesses an OpenSearch service and discovers it.
* @param {object} url The URL to find the OpenSearchDescription XML document
* @returns {Promise<OpenSearchService>} The {@link OpenSearchService} as a Promise
*/
static discover(url) {
const { useXHR } = config();
if (useXHR) {
return new Promise((resolve, reject, onCancel) => {
const xhr = createXHR(url);
xhr.onload = () => {
try {
resolve(OpenSearchService.fromXml(xhr.responseText));
} catch (error) {
reject(error);
}
};
xhr.onerror = () => {
reject(new TypeError('Failed to fetch'));
};
if (onCancel && typeof onCancel === 'function') {
onCancel(() => {
xhr.abort();
});
}
});
}
return fetchAndCheck(url)
.then(response => response.text())
.then(response => OpenSearchService.fromXml(response));
}
/**
* Create a new {@link OpenSearchService} from an OSDD XML string.
* @param {string} xml The XML string to parse the description from
* @returns {OpenSearchService} The created service object
*/
static fromXml(xml) {
return new OpenSearchService(OpenSearchDescription.fromXml(xml));
}
/**
* Serialize the service to a simple object.
* @returns {object} The serialized service description
*/
serialize() {
return {
description: this.descriptionDocument.serialize(),
};
}
/**
* Deserialize an OpenSearch description from an object.
* @param {object} values The serialized service description
* @returns {OpenSearchService} The deserialized service
*/
static deserialize(values) {
return new OpenSearchService(
OpenSearchDescription.deserialize(values.description)
);
}
}