fume-fhir-converter
Version:
FHIR-Utilized Mapping Engine - Community
147 lines (129 loc) • 7.82 kB
text/typescript
/**
* © Copyright Outburn Ltd. 2022-2024 All Rights Reserved
* Project name: FUME-COMMUNITY
*/
/* eslint-disable @typescript-eslint/restrict-plus-operands */
import _ from 'lodash';
import config from '../../config';
import { getCache } from '../cache';
import { getFhirPackageIndex } from '../conformance';
import { getLogger } from '../logger';
import { initCapOnce, replaceColonsWithBrackets } from '../stringFunctions';
import thrower from '../thrower';
import { getCurrElement } from './getCurrElement';
import { getSnapshot } from './getSnapshot';
import { returnPathWithoutX } from './returnPathWithoutX';
const dev = process.env.NODE_ENV === 'dev';
export interface FshPathObject {
originPath: string
newPath: string
};
export const getElementDefinition = async (rootType: string, path: FshPathObject | string) => {
if (dev) console.log({ func: getElementDefinition, rootType, path });
if (typeof path === 'string') {
path = { originPath: path, newPath: path };
if (dev) console.log({ 'typeof path === \'string\'': true, path });
}
const { elementDefinition } = getCache();
// The current root type element determains the existing paths to look out path in.
const currTypeStructureDefinition = await getSnapshot(rootType, config.getFhirVersion(), getFhirPackageIndex(), getLogger());
// Creating a list of all the current path nodes.
const pathNodes = path.newPath.split('.');
if (dev) console.log({ pathNodes });
// Iterating the nodes, and for each node implementing the logic. The path is being built from the begining to the end
// for the fact that a diversion in the main path can lead to a "leaf", if we're not jumping to another Base element.
for (let nodes = 1; nodes <= pathNodes.length; nodes++) {
// Getting the path up to the current node (to check if it exists and then we can move on)
let currPath = path.newPath.split('.').slice(0, nodes).join('.');
let currElement;
// First checking the definition cache
const cachedElementDefinition = elementDefinition.get(rootType + '-' + currPath);
if (dev) console.log({ 'fetching from elementDefinition cache': rootType + '-' + currPath, found: !!cachedElementDefinition });
if (cachedElementDefinition) {
currElement = cachedElementDefinition;
if (dev) console.log({ cachedElementDefinition });
} else {
// Checking if the current element path exists in the current sd
if (dev) console.log('(getElementDefinition): getCurrElement()', { currTypeStructureDefinition: currTypeStructureDefinition.id, currPath, nodes, 'path.newPath.split(\'.\')': path.newPath.split('.'), rootType });
currElement = getCurrElement(currTypeStructureDefinition, currPath, nodes, path.newPath.split('.'), rootType);
if (currElement) {
// If an element has a contentReference field, ), it will not have a type or a profile, since those are defined in the referenced target element.
// The value of the contentReference field will be the target element’s id prefixed by ‘#’.
if (_.get(currElement, 'contentReference')) {
path.newPath = currElement.contentReference.split('.').slice(1).concat(path.newPath.split('.').slice(nodes)).join('.');
return getElementDefinition(currElement.contentReference.split('.')[0].slice(1), path);
} else {
elementDefinition.set(rootType + '-' + currPath, currElement);
}
}
}
let baseElem;
// Checking if the current node includes [x], and thats why we didnt find the relevant element.
// so we need to check for all the options.
if (!currElement && pathNodes[nodes - 1].includes('[') && !(nodes >= 2 && pathNodes[nodes - 2].includes('['))) {
baseElem = _.cloneDeep(currTypeStructureDefinition.snapshot.element.find(e => {
return replaceColonsWithBrackets(e.id) === String(currTypeStructureDefinition?.type) + '.' + String(currPath.split('[')[0]);
}));
if (baseElem) {
// If there’s a type.profile, fetching its snapshot and adding the url to the type.
if (_.get(baseElem, ['type', '0'])) {
let baseElemSnapshot;
try {
baseElemSnapshot = await getSnapshot(pathNodes[nodes - 1].split('[')[1].split(']')[0], config.getFhirVersion(), getFhirPackageIndex(), getLogger());
if (baseElemSnapshot) {
// Fixing the fsh to the element id standard if relevant. (bracets to colon)
const preFixed = currPath;
currPath = currPath.slice(0, -1).split('[').join(':');
path.newPath = path.newPath.replace(preFixed, currPath);
// Making sure we are not referencing a pointer to the element we're changing, but a deepClone of it.
currElement = _.cloneDeep(baseElem);
currElement.type[0].profile = [baseElemSnapshot.url];
currElement.id = currElement.id + ':' + currPath.split(currElement.id.split('.')[currElement.id.split('.').length - 1] + ':')[1];
elementDefinition.set(rootType + '-' + currPath, currElement);
elementDefinition.set(currTypeStructureDefinition.url + '-' + currPath, currElement);
}
} catch (e) {}
}
}
}
// Checking wether to move on to the next node, to a content reference or throw an error
if (currElement) {
// We found the wanted element - the whole path exists.
if (currPath === path.newPath) {
currElement.__fromDefinition = currTypeStructureDefinition.url;
return currElement;
} else if (_.get(currElement, ['type', '0', 'code'], '').includes('System')) { // The path is invalid
return thrower.throwParseError(`The element '${currPath}' does not have child elements.`);
} else {
// Applying the changes we made in the current fshPath in order to find it in the elements ids, before moving on to the next node.
path.newPath = currElement.id.split('.').length > 1 ? String(currElement.id.split('.').slice(1).join('.')) + '.' + String(pathNodes.slice(nodes).join('.')) : pathNodes.slice(nodes).join('.');
}
} else {
if (!baseElem && currPath.split('.').length === 1) {
return thrower.throwParseError(`The element '${path.newPath}' was not found in '${rootType}'`);
} else {
const prevPath = path.newPath.split('.').slice(0, nodes - 1).join('.');
baseElem = getCurrElement(currTypeStructureDefinition, prevPath, nodes - 1, path.newPath.split('.'), rootType);
// Moving on to the different Root type - all paths in the current one are not relevant, but there might be in another one.
if (_.get(baseElem, 'contentReference')) {
path.newPath = baseElem.contentReference.split('.').slice(1).join('.');
return getElementDefinition(baseElem.contentReference.split('.')[0].slice(1), path);
}
path.newPath = path.newPath.split('.').slice(nodes - 1).join('.');
// Going to the profile type if exists.
if (_.get(baseElem, ['type', '0', 'profile'])) {
return getElementDefinition(baseElem.type[0].profile[0], path);
} else {
// Selecting the relevant type for polymorphic fields.
if (baseElem?.type.length > 1) {
const splittedOriginPath = path.originPath.split('.').slice(0, -1 * path.newPath.split('.').length);
const specificType = baseElem.type.find(t => splittedOriginPath[splittedOriginPath.length - 1] === returnPathWithoutX(baseElem.id.split('.')[baseElem.id.split('.').length - 1]) + initCapOnce(t.code));
return getElementDefinition(specificType.code, path);
} else {
return getElementDefinition(baseElem.type[0].code, path);
}
}
}
}
}
};