const Utils = require('../utils.js');
const Versions = require('../versions.js');
const MigrateCommons = require('./commons.js');
const NO_VERSION = "0.0.0";
/** Migrate capabilities related responses to the latest version. */
class MigrateCapabilities {
/**
* Tries to determine the API version from the capabilities object.
*
* Returns the version number, e.g. "0.4.2", "1.0.0" or "0.0.0" (if unknown).
*
* @param {object} capabilities
* @returns {string}
*/
static guessApiVersion(capabilities) {
// No object passed
if (!Utils.isObject(capabilities)) {
return NO_VERSION;
}
// Get exact info from version fields
if (Versions.validate(capabilities.api_version)) {
return capabilities.api_version;
}
else if (Versions.validate(capabilities.version)) {
return capabilities.version;
}
// Now we are really guessing
else if (Array.isArray(capabilities.endpoints)) {
if (capabilities.endpoints.find(e => e.path === '/file_formats' || e.path === '/conformance' || e.path === '/files')) {
return "1.0.0";
}
else if (capabilities.endpoints.find(e => e.path === '/output_formats' || e.path === '/files/{user_id}')) {
return "0.4.2";
}
else if (!capabilities.backend_version && !capabilities.title && !capabilities.description && !capabilities.links) {
return "0.3.1";
}
}
// Can't determine version
return NO_VERSION;
}
/**
* Converts a `GET /` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {object} response - The response to convert
* @param {string|null} version - Version number of the API, which the response conforms to. If `null`, tries to guess the version with `guessApiVersion()`.
* @param {boolean} updateVersionNumbers - Should version numbers in the response be updated?
* @param {boolean} updateEndpointPaths - Should the endpoint paths be updated to their recent equivalents?
* @param {string} id - If no id is set in the response, sets it to the value specified here. Defaults to `unknown`.
* @param {string} title - If no title is set in the response, sets it to the value specified here. Defaults to `Unknown`.
* @param {string} title - If no backend_version is set in the response, sets it to the value specified here. Defaults to `0.0.0`.
* @returns {object}
*/
static convertCapabilitiesToLatestSpec(originalCapabilities, version = null, updateVersionNumbers = true, updateEndpointPaths = true, id = "unknown", title = "Unknown", backend_version = "0.0.0") {
if (version === null) {
version = this.guessApiVersion(originalCapabilities);
}
// Return empty if version number is not available
if (version === NO_VERSION) {
return {};
}
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
let capabilities = Utils.deepClone(originalCapabilities);
// Fill & Update version number
if (!updateVersionNumbers) {
capabilities.api_version = version;
}
else {
capabilities.api_version = "1.0.0";
}
// Convert billing plans
if (Utils.isObject(capabilities.billing)) {
capabilities.billing = this.convertBillingToLatestSpec(capabilities.billing, version);
}
else {
delete capabilities.billing;
}
// Convert endpoints
capabilities.endpoints = this.convertEndpointsToLatestSpec(capabilities.endpoints, version, updateEndpointPaths);
// Fill STAC Version field
if (!updateVersionNumbers && Versions.compare(version, "0.4.x", "=")) {
capabilities.stac_version = "0.6.2";
}
else if (updateVersionNumbers || typeof capabilities.stac_version !== 'string') {
capabilities.stac_version = "0.9.0";
}
// Add missing fields with somewhat useful data
if (typeof capabilities.production !== 'boolean') {
capabilities.production = Versions.compare(version, "1.0.0-rc.1", "=") || Versions.compare(version, "1.0.0-rc.2", "=") ? true : false;
}
if (typeof capabilities.backend_version !== 'string') {
capabilities.backend_version = backend_version;
}
if (typeof capabilities.id !== 'string') {
capabilities.id = id;
}
if (typeof capabilities.title !== 'string') {
capabilities.title = title;
}
if (typeof capabilities.description !== 'string') {
capabilities.description = "";
}
capabilities.links = MigrateCommons.migrateLinks(capabilities.links, version);
return capabilities;
}
/**
* Converts the billing part of the `GET /` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {object} billing - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @returns {object}
*/
static convertBillingToLatestSpec(billing, version) {
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
if (Utils.isObject(billing)) {
billing = Utils.deepClone(billing);
}
else {
billing = {};
}
if (typeof billing.currency !== 'string') {
billing.currency = null;
}
return billing;
}
/**
* Converts the endpoints part of the `GET /` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {array} endpoints - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @param {boolean} updatePaths - Should the endpoint paths be updated to their recent equivalents?
* @returns {array}
*/
static convertEndpointsToLatestSpec(endpoints, version, updatePaths = false) {
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
if (!Array.isArray(endpoints)) {
return [];
}
endpoints = Utils.deepClone(endpoints);
// convert v0.4 endpoints to v1.0
if (updatePaths) {
let isV04 = Versions.compare(version, "0.4.x", "=");
let isLtV100RC2 = Versions.compare(version, "1.0.0-rc.2", "<");
let addPutToPg = function(endpoints) {
let newPgPath = '/process_graphs/{process_graph_id}';
let i = endpoints.findIndex(e => e.path === newPgPath);
if (i >= 0) {
if (endpoints[i].methods.indexOf('PUT') === -1) {
endpoints[i].methods.push('PUT');
}
}
else {
endpoints.push({
path: newPgPath,
methods: ['PUT']
});
}
return endpoints;
};
for(var i in endpoints) {
let e = endpoints[i];
if (isV04) {
switch (e.path) {
case '/output_formats':
e.path = '/file_formats';
break;
case '/files/{user_id}':
e.path = '/files';
break;
case '/files/{user_id}/{path}':
e.path = '/files/{path}';
break;
}
}
if (isLtV100RC2) {
switch (e.path) {
case '/process_graphs':
let post = e.methods.indexOf('POST');
if (post >= 0) {
e.methods.splice(post, 1);
addPutToPg(endpoints);
}
break;
case '/process_graphs/{process_graph_id}':
let patch = e.methods.indexOf('PATCH');
if (patch >= 0) {
e.methods.splice(patch, 1);
addPutToPg(endpoints);
}
break;
}
}
}
}
return endpoints;
}
/**
* Alias for `convertFileFormatsToLatestSpec()`.
*
* @alias MigrateCapabilities.convertFileFormatsToLatestSpec
* @deprecated
* @param {object} formats - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @returns {object}
*/
static convertOutputFormatsToLatestSpec(formats, version) {
return this.convertFileFormatsToLatestSpec(formats, version);
}
/**
* Converts a `GET /file_formats` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {object} formats - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @returns {object}
*/
static convertFileFormatsToLatestSpec(formats, version) {
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
if (Utils.isObject(formats)) {
formats = Utils.deepClone(formats);
}
else {
formats = {};
}
if (Versions.compare(version, "0.4.x", "=") && Utils.isObject(formats)) {
formats = {
output: formats
};
}
formats.input = upgradeFileFormats(formats.input, version);
formats.output = upgradeFileFormats(formats.output, version);
return formats;
}
/**
* Converts a `GET /service_types` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {object} types - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @returns {object}
*/
static convertServiceTypesToLatestSpec(types, version) {
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
if (!Utils.isObject(types)) {
return {};
}
types = Utils.deepClone(types);
for(let t in types) {
if (!Utils.isObject(types[t])) {
types[t] = {};
}
if (Versions.compare(version, "0.4.x", "=")) {
// Remove attributes
delete types[t].attributes;
// Rename parameters to configuration
if (Utils.isObject(types[t].parameters)) {
types[t].configuration = types[t].parameters;
}
delete types[t].parameters;
// Rename variables to process_parameters
if (Array.isArray(types[t].variables)) {
types[t].process_parameters = types[t].variables.map(v => {
let param = {
name: v.variable_id,
description: typeof v.description === 'string' ? v.description : "",
schema: {
type: [
typeof v.type === 'string' ? v.type : "string",
"null"
]
}
};
if (typeof v.default !== 'undefined') {
param.default = v.default;
}
return param;
});
}
delete types[t].variables;
}
if (!Utils.isObject(types[t].configuration)) {
types[t].configuration = {};
}
else {
types[t].configuration = MigrateCommons.migrateDiscoveryParameters(types[t].configuration, version);
}
if (!Array.isArray(types[t].process_parameters)) {
types[t].process_parameters = [];
}
if (typeof types[t].links !== 'undefined') { // links not required, so only apply if defined anyway
types[t].links = MigrateCommons.migrateLinks(types[t].links, version);
}
}
return types;
}
/**
* Converts a `GET /udf_runtimes` response to the latest version.
*
* Always returns a deep copy of the input object.
*
* @param {object} runtimes - The response to convert
* @param {string} version - Version number of the API, which the response conforms to
* @returns {object}
*/
static convertUdfRuntimesToLatestSpec(runtimes, version) {
if (Versions.compare(version, "0.3.x", "<=")) {
throw "Migrating from API version 0.3.0 and older is not supported.";
}
if (!Utils.isObject(runtimes)) {
return {};
}
runtimes = Utils.deepClone(runtimes);
for(let r in runtimes) {
// Nothing to do, was not supported in 0.3 and nothing changed in 0.4.
if (Versions.compare(version, "0.4.x", "=")) {
if (!Utils.isObject(runtimes[r])) {
delete runtimes[r];
continue;
}
// null is not allowed any longer, replace with empty string
if (runtimes[r].description === null) {
runtimes[r].description = "";
}
}
if (typeof runtimes[r].type !== 'string') {
if (typeof runtimes[r].docker === 'string') {
runtimes[r].type = 'docker';
}
else {
runtimes[r].type = 'language';
}
}
if (typeof runtimes[r].links !== 'undefined') { // links not required, so only apply if defined anyway
runtimes[r].links = MigrateCommons.migrateLinks(runtimes[r].links, version);
}
}
return runtimes;
}
}
const GIS_DATA_TYPES = ['raster', 'vector', 'table', 'other'];
function upgradeFileFormats(formats, version) {
if (!Utils.isObject(formats)) {
formats = {};
}
for(let id in formats) {
if (!Utils.isObject(formats[id].parameters)) {
formats[id].parameters = {};
}
else {
formats[id].parameters = MigrateCommons.migrateDiscoveryParameters(formats[id].parameters, version);
}
// Can be empty: https://github.com/Open-EO/openeo-api/issues/325
if (!Array.isArray(formats[id].gis_data_types)) {
formats[id].gis_data_types = [];
}
else {
formats[id].gis_data_types = formats[id].gis_data_types.filter(t => GIS_DATA_TYPES.includes(t));
}
if (typeof formats[id].links !== 'undefined') { // links not required, so only apply if defined anyway
formats[id].links = MigrateCommons.migrateLinks(formats[id].links, version);
}
}
return formats;
}
module.exports = MigrateCapabilities;