Source: index.js

import axios from "axios";
import qs from "qs";
import { get } from "lodash";

/**
 * Fetches resource(s) by the given <code>url</code>.
 *
 * @param {String} url - the url
 * @param {Object} [params = {}] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchByUrl(url, params = {}, token, basicAuth) {
  if (!url) {
    throw new Error("Fetching the resource(s) failed because the given url was null or undefined");
  }

  const headers = {
    "Cache-Control": "no-cache"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    params,
    headers
  };
  return axios.get(url, options);
}

/**
 * Fetches the resource from the FHIR server with the given <code>id</code>.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} resourceType - the type of the FHIR resource
 * @param {String} id - id of the resource to be fetched
 * @param {Object} [params = {}] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchResource(fhirBaseUrl, resourceType, id, params = {}, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Fetching the resources failed because the given fhirBaseUrl was null or undefined");
  }

  if (!resourceType) {
    throw new Error("Fetching the resources failed because the given resourceType was null or undefined");
  }

  if (id === null || id === undefined) {
    throw new Error("Fetching the resource failed because the given id was null or undefined");
  }

  const url = `${fhirBaseUrl}/${resourceType}/${id}`;
  const headers = {
    "Cache-Control": "no-cache"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    params,
    headers
  };

  return axios.get(url, options);
}

/**
 * Fetches resources from a FHIR server using a GET request.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} resourceType - the type of the FHIR resource
 * @param {Object} [params = {}] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchResources(fhirBaseUrl, resourceType, params = {}, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Fetching the resources failed because the given fhirBaseUrl was null or undefined");
  }

  if (!resourceType) {
    throw new Error("Fetching the resources failed because the given resourceType was null or undefined");
  }

  let url = `${fhirBaseUrl}/${resourceType}`;
  const headers = {
    "Cache-Control": "no-cache"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  if (!(params instanceof URLSearchParams)) {
    options.params = params;
    options.paramsSerializer = params => qs.stringify(params, { arrayFormat: "repeat" });
  } else {
    url += "?" + serializeUrlParams(params);
  }

  return axios.get(url, options);
}

/**
 * Fetches a ValueSet by the given url and returns the concept.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} [token] - the authentication token
 * @param {String} url - the url of the resource
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Array} - the cpncept
 */
export async function fetchValueSetConceptByUrl(fhirBaseUrl, token, url, basicAuth) {
  const response = await fetchResources(fhirBaseUrl, "ValueSet", { url }, token);
  const valueSet = mapFhirResponse(response)[0];

  if (!valueSet) {
    throw new Error("ValueSet '" + url + "' not found on server.");
  }

  return get(valueSet, "compose.include[0].concept", []);
}

/**
 * Fetches resources from a FHIR server using a POST request.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} resourceType - the type of the FHIR resource
 * @param {Object} [params = {}] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchResourcesPost(fhirBaseUrl, resourceType, params = {}, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Fetching the resources failed because the given fhirBaseUrl was null or undefined");
  }

  if (!resourceType) {
    throw new Error("Fetching the resources failed because the given resourceType was null or undefined");
  }

  const url = `${fhirBaseUrl}/${resourceType}/_search`;
  const headers = {
    "Cache-Control": "no-cache"
  };

  const encodedParams = qs.stringify(params, { indices: false, encode: false });

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  return axios.post(url, encodedParams, options);
}

/**
 * Submits a given resource to the server
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} resource - resource that is supposed to be submitted
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function submitResource(fhirBaseUrl, resource, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Resource was not submitted because the given fhirBaseUrl was null or undefined");
  }

  if (!resource) {
    throw new Error("Resource was not submitted because the given resource was null or undefined");
  }

  if (!resource.resourceType) {
    throw new Error("Invalid JSON content detected, missing required element: 'resourceType'");
  }

  let url;

  if (resource.resourceType === "Bundle" && resource.type === "transaction") {
    url = `${fhirBaseUrl}/`;
  } else {
    url = `${fhirBaseUrl}/${resource.resourceType}`;
  }

  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  return axios.post(url, resource, options);
}

/**
 * Submits a given Resource to a Url
 * @param {*} baseUrl
 * @param {*} resource
 * @param {*} token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 */
export function submitResourceToUrl(baseUrl, resource, token, basicAuth) {
  if (!baseUrl) {
    throw new Error("Resource was not submitted because the given fhirBaseUrl was null or undefined");
  }

  if (!resource) {
    throw new Error("Resource was not submitted because the given resource was null or undefined");
  }
  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };
  return axios.post(baseUrl, resource, options);
}

/**
 * Updates an existing FHIR resource on the FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} resource - resource that is supposed to be submitted
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function updateResource(fhirBaseUrl, resource, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Resource was not submitted because the given fhirBaseUrl was null or undefined");
  }

  if (!resource) {
    throw new Error("Resource was not submitted because the given resource was null or undefined");
  }

  if (!resource.resourceType) {
    throw new Error("Invalid JSON content detected, missing required element: 'resourceType'");
  }

  if (resource.id === null || resource.id === undefined) {
    throw new Error("Can not update resource, resource body must contain an ID element for update (PUT) operation");
  }

  const url = `${fhirBaseUrl}/${resource.resourceType}/${resource.id}`;
  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  return axios.put(url, resource, options);
}

/**
 * Updates an existing FHIR resource on the FHIR server or creates a new resource if no id is in the given FHIR resource.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} resource - resource that is supposed to be submitted
 * @param {Object} [params = {}] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function updateResourceByUrl(fhirBaseUrl, resource, params = {}, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Resource was not submitted because the given fhirBaseUrl was null or undefined");
  }

  if (!resource) {
    throw new Error("Resource was not submitted because the given resource was null or undefined");
  }

  if (!resource.resourceType) {
    throw new Error("Invalid JSON content detected, missing required element: 'resourceType'");
  }

  const url = `${fhirBaseUrl}/${resource.resourceType}`;
  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    params,
    headers
  };

  return axios.put(url, resource, options);
}

/**
 * Deletes a FHIR resource from the FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} resource - resource that is supposed to be deleted
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function deleteResource(fhirBaseUrl, resource, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Resource was not deleted because the given fhirBaseUrl was null or undefined");
  }

  if (!resource) {
    throw new Error("Resource was not deleted because the given resource was null or undefined");
  }

  if (!resource.resourceType) {
    throw new Error("Invalid JSON content detected, missing required element: 'resourceType'");
  }

  if (resource.id === null || resource.id === undefined) {
    throw new Error("Can not delete resource, resource body must contain an ID element for delete (DELETE) operation");
  }

  const url = `${fhirBaseUrl}/${resource.resourceType}/${resource.id}`;
  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  return axios.delete(url, options);
}

/**
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} resourceType - the type of the FHIR resource
 * @param {String} id - id of the resource to be deleted
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function deleteResourceById(fhirBaseUrl, resourceType, id, token, basicAuth) {
  if (!fhirBaseUrl) {
    throw new Error("Resource was not deleted because the given fhirBaseUrl was null or undefined");
  }

  if (!resourceType) {
    throw new Error("Resource was not deleted because the given resourceType was null or undefined");
  }

  if (id === null || id === undefined) {
    throw new Error("Can not delete resource, resource body must contain an ID element for delete (DELETE) operation");
  }

  const url = `${fhirBaseUrl}/${resourceType}/${id}`;
  const headers = {
    "Cache-Control": "no-cache",
    "Content-Type": "application/json"
  };

  if (token) {
    if (basicAuth) {
      headers.Authorization = "Basic " + token;
    } else {
      headers.Authorization = "Bearer " + token;
    }
  }

  const options = {
    headers
  };

  return axios.delete(url, options);
}

/**
 * Maps the response of a FHIR search query bundle to the actual FHIR resources.
 *
 * @example
 * <caption>Basic usage</caption>
 * import fhirApi from "@molit/fhir-api";
 *
 * console.log(bundle);
 * // ->
 * // {
 * //   "resourceType": "Bundle",
 * //   "type": "searchset",
 * //   "total": 2,
 * //   "entry": [
 * //     {
 * //       "fullUrl": "\thttps://fhir.molit.eu/baseDstu3/Patient/6",
 * //       "resource": {
 * //         "resourceType": "Patient",
 * //         "id": "6"
 * //       }
 * //     },
 * //     {
 * //       "fullUrl": "\thttps://fhir.molit.eu/baseDstu3/Patient/8",
 * //       "resource": {
 * //         "resourceType": "Patient",
 * //         "id": "8"
 * //       }
 * //     }
 * //   ]
 * // }
 *
 * let patients = fhirApi.mapFhirData(bundle);
 * console.log(patients)
 * // ->
 * // [
 * //   {
 * //     "resourceType": "Patient",
 * //     "id": "6"
 * //   },
 * //   {
 * //     "resourceType": "Patient",
 * //     "id": "8"
 * //   }
 * // ]
 *
 * @param {Object} data - a FHIR search bundle.
 * @returns {Array} the resources in an array
 */
export function mapFhirData(data) {
  if (!data || !data.entry || !Array.isArray(data.entry)) {
    return [];
  }
  return data.entry.map(element => element.resource);
}

/**
 * Maps the response of a FHIR search query to the actual FHIR resources.
 * @see {@link mapFhirData} for further information.
 *
 * @param {Object} res - the response of a FHIR search query.
 * @returns {Array} the resources in an array
 */
export function mapFhirResponse(res) {
  if (!res || !res.data || !res.data.entry || !Array.isArray(res.data.entry)) {
    return [];
  }
  return mapFhirData(res.data);
}

/**
 * Fetches the conformance statement.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchConformanceStatement(fhirBaseUrl, params, token, basicAuth) {
  const resourceType = `metadata`;
  return fetchResources(fhirBaseUrl, resourceType, params, token);
}

/**
 * Fetches the Patient with the given <code>id</code> from a FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} id - id of the Patient to be fetched
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchPatient(fhirBaseUrl, id, params, token, basicAuth) {
  const resourceType = `Patient`;
  return fetchResource(fhirBaseUrl, resourceType, id, params, token, basicAuth);
}

/**
 * Fetches Patients from a FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchPatients(fhirBaseUrl, params, token, basicAuth) {
  return fetchResources(fhirBaseUrl, "Patient", params, token, basicAuth);
}

/**
 * Fetches the Questionnaire with the given <code>id</code> from a FHIR server.
 *
 * @param {String} fhirBaseUrl url of the FHIR-Server
 * @param {String} id - id of the Questionnaire to be fetched
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchQuestionnaire(fhirBaseUrl, id, params, token, basicAuth) {
  const resourceType = `Questionnaire`;
  return fetchResource(fhirBaseUrl, resourceType, id, params, token, basicAuth);
}

/**
 * Fetches Questionnaires from a FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchQuestionnaires(fhirBaseUrl, params, token, basicAuth) {
  return fetchResources(fhirBaseUrl, "Questionnaire", params, token, basicAuth);
}

/**
 * Fetches ValueSet from a FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {String} id - id of the ValueSet to be fetched
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchValueSet(fhirBaseUrl, id, params, token, basicAuth) {
  const resourceType = `ValueSet`;
  return fetchResource(fhirBaseUrl, resourceType, id, params, token, basicAuth);
}

/**
 * Fetches ValueSets from a FHIR server.
 *
 * @param {String} fhirBaseUrl - the base URL of the FHIR server
 * @param {Object} [params] - the FHIR query params
 * @param {String} [token] - the authentication token
 * @param {Boolean} basicAuth - boolean parameter that changes the authorization header to Basic if true
 * @returns {Promise} Promise object representing the response to the http call
 */
export function fetchValueSets(fhirBaseUrl, params, token, basicAuth) {
  return fetchResources(fhirBaseUrl, "ValueSet", params, token, basicAuth);
}

/**
 * Converts URLSearchParams in a string that a FHIR server can understand.
 *
 * @param {URLSearchParams} params - the URL search params
 */
export function serializeUrlParams(params) {
  if (!(params instanceof URLSearchParams)) {
    throw new Error("Params not of type URLSearchParams.");
  }
  return params.toString();
}