fhirbuilder
Version:
Another FHIR Tool to build FHIR Resources
225 lines (224 loc) • 9.2 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateValidators = generateValidators;
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const yaml_1 = __importDefault(require("yaml"));
/**
* Parses a YAML file and returns the corresponding JSON object.
* @param {string} filePath - Relative path to the YAML file.
* @returns {Object} The parsed JSON object.
*/
function parseYamlFile(filePath) {
const absolutePath = path_1.default.resolve(__dirname, '..', filePath);
const fileContent = fs_1.default.readFileSync(absolutePath, 'utf-8');
return yaml_1.default.parse(fileContent);
}
/**
* Loads resources from a specified directory, filters them based on the given criteria,
* and maps them using a provided key builder function.
* @param {LoadResourcesConfig} config - Configuration object for loading resources.
* @returns {Map<string, Object>} A map containing the loaded resources, keyed by the generated key.
*/
function loadResourcesFromDirectory(config) {
const { directory, fileFilter, keyBuilder } = config;
const dirPath = path_1.default.resolve(__dirname, '..', directory);
const files = fs_1.default.readdirSync(dirPath).filter(fileFilter);
const resourceMap = new Map();
files.forEach((fileName) => {
const json = parseYamlFile(path_1.default.join(directory, fileName));
resourceMap.set(keyBuilder(json), json);
});
return resourceMap;
}
/**
* Loads value sets into memory by filtering files and mapping them using their URL and version.
* @returns {Map<string, Object>} A map containing the loaded value sets.
*/
function loadInMemoryValueSets() {
return loadResourcesFromDirectory({
directory: 'valuesets',
fileFilter: (fileName) => fileName.includes('Valueset-'),
keyBuilder: (json) => `${json.url}|${json.version}`,
});
}
/**
* Loads code systems into memory by filtering files and mapping them using their URL.
* @returns {Map<string, Object>} A map containing the loaded code systems.
*/
function loadInMemoryCodeSystems() {
return loadResourcesFromDirectory({
directory: 'codesystems',
fileFilter: (fileName) => fileName.includes('Codesystem-'),
keyBuilder: (json) => `${json.url}`,
});
}
/**
* Loads profiles into memory by filtering files and mapping them using their resource type and ID.
* @returns {Map<string, Object>} A map containing the loaded profiles.
*/
function loadInMemoryProfiles() {
return loadResourcesFromDirectory({
directory: 'profiles',
fileFilter: (fileName) => fileName.includes('profile'),
keyBuilder: (json) => `${json.resourceType}|${json.id}`,
});
}
/**
* Utility function to load value sets, code systems, and profiles into memory.
* @returns {{ valueSetMap: Map<string, object>, codeSystemMap: Map<string, object>, resourceMap: Map<string, object> }}
* An object containing maps for value sets, code systems, and profiles.
*/
function LoadInMemoryUtil() {
const valueSetMap = loadInMemoryValueSets();
const codeSystemMap = loadInMemoryCodeSystems();
const resourceMap = loadInMemoryProfiles();
return { valueSetMap, codeSystemMap, resourceMap };
}
/**
* Generates validators for the loaded resources.
* @returns {Map<string, any>} A map containing validators keyed by resource ID.
*/
function generateValidators() {
const { valueSetMap, codeSystemMap, resourceMap } = LoadInMemoryUtil();
const validators = new Map();
for (const [key, value] of resourceMap.entries()) {
const [, id] = key.split('|');
const resourceValidator = new ResourceValidator(value, valueSetMap, codeSystemMap);
const structure = resourceValidator.generateStructure();
validators.set(id, structure);
}
return validators;
}
/**
* Generates the structure of the resource.
* @returns {Array<Object>} An array of elements representing the resource structure.
*/
class ResourceValidator {
resource;
valueSetMap;
codeSystemMap;
constructor(resource, valueSetMap, codeSystemMap) {
this.resource = resource;
this.valueSetMap = valueSetMap;
this.codeSystemMap = codeSystemMap;
}
/**
* Generates the structure of the resource.
* @returns {Array<Object>} An array of elements representing the resource structure.
*/
generateStructure() {
const elements = this.resource.snapshot.element.concat(...this.resource.snapshot.element.map((element) => this.createElementStructureForValueX(element)));
return elements.map((element) => this.createElementStructure(element)).filter((el) => el !== null);
}
createElementStructureForValueX(element) {
const [base, mainAttr, subAttr] = element.id.split('.');
const innerAttr = `${mainAttr}.${subAttr}`;
const elements = [];
const innerElements = [];
if (mainAttr?.includes('[x]')) {
const types = element.type.map((type) => type.code);
types.forEach((type) => {
const newElement = { ...element };
newElement.id = `${base}.${mainAttr.replace('[x]', '')}${type.charAt(0).toUpperCase() + type.slice(1)}`;
newElement.type = [{ code: type }];
newElement.min = 0;
elements.push(newElement);
});
}
if (innerAttr?.includes('[x]')) {
const types = element.type.map((type) => type.code);
types.forEach((type) => {
const newElement = { ...element };
newElement.id = `${base}.${innerAttr.replace('[x]', '')}${type.charAt(0).toUpperCase() + type.slice(1)}`;
newElement.type = [{ code: type }];
newElement.min = 0;
innerElements.push(newElement);
});
}
return elements.concat(innerElements);
}
/**
* Creates the structure for a single element.
* @param {Object} element - The element to structure.
* @returns {Object|null} The structured element, or null if the element is not valid.
*/
createElementStructure(element) {
const [, mainAttr, subAttr] = element.id.split('.');
if (!mainAttr) {
return null;
}
if (element.id.includes('[x]')) {
return null;
}
const isRequired = element.min === 1;
const isArray = element.max === '*';
const isBlocked = element.max === '0';
const type = this.determineElementType(element);
const references = this.getReferenceTypes(element);
const enumValues = this.getEnumValues(element);
if (isBlocked) {
return null;
}
return {
name: subAttr ? `${mainAttr}.${subAttr}` : mainAttr,
type: type,
isRequired: isRequired,
isArray: isArray,
referenceTypes: references.length > 0 ? references : undefined,
enumValues: enumValues.length > 0 ? enumValues : undefined,
};
}
/**
* Determines the element type.
* @param {string} base - The base element name.
* @param {string} attr - The attribute name.
* @param {Object} element - The element being processed.
* @returns {string} The determined element type.
*/
determineElementType(element) {
const [base, mainAttr, subAttr] = element.id.split('.');
const innerAttr = `${mainAttr}.${subAttr}`;
let type = element.type[0].code;
if (type === 'BackboneElement') {
type = `${base}${mainAttr.charAt(0).toUpperCase() + mainAttr.slice(1)}`;
}
else if (innerAttr.includes('id')) {
type = 'string';
}
return type;
}
/**
* Retrieves reference types for an element.
* @param {Object} element - The element being processed.
* @returns {Array<string>} An array of reference types.
*/
getReferenceTypes(element) {
if (element.type[0]?.targetProfile && element.type[0].code === 'Reference') {
return element.type[0]?.targetProfile?.map((profile) => profile.split('/').pop());
}
return [];
}
/**
* Retrieves enumeration values for an element.
* @param {Object} element - The element being processed.
* @returns {Array<string>} An array of enumeration values.
*/
getEnumValues(element) {
if (element?.binding?.strength && element.type[0].code === 'code' && element.binding.strength === 'required') {
const url = element.binding.valueSet;
const valueSet = this.valueSetMap.get(url);
if (valueSet) {
const system = valueSet.compose.include[0].system;
const codeSystem = this.codeSystemMap.get(system);
if (codeSystem) {
return codeSystem.concept.map((concept) => concept.code);
}
}
}
return [];
}
}