UNPKG

fume-fhir-converter

Version:

FHIR-Utilized Mapping Engine - Community

147 lines (129 loc) 7.82 kB
/** * © 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); } } } } } };