UNPKG

fhirbuilder

Version:
325 lines (324 loc) 14.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ValidateReferenceFormat = exports.validateObject = exports.validateArray = exports.validateEnumValues = exports.validateRequiredFieldByDefinition = exports.validateAdditionalFields = exports.BaseValidator = exports.InternalValidator = void 0; exports.hasValue = hasValue; exports.parseValidator = parseValidator; const resource_list_util_1 = require("../../../commons/utils/resource-list.util"); const remove_undefined_attributes_util_1 = require("../../utils/remove-undefined-attributes.util"); const datatypes_1 = require("../datatypes"); const operation_outcome_exception_1 = require("../../../commons/exceptions/operation-outcome.exception"); const DatatypesValidator_1 = require("./DatatypesValidator"); const PrimitivesValidator_1 = require("./PrimitivesValidator"); const ResourcesValidator_1 = require("./ResourcesValidator"); const BackbonesValidator_1 = require("./BackbonesValidator"); exports.InternalValidator = { ...DatatypesValidator_1.DatatypesValidator, ...PrimitivesValidator_1.PrimitivesValidator, ...ResourcesValidator_1.InternalResourceValidator, ...BackbonesValidator_1.BackbonesValidator, }; /** * @description Validates a model based on the model definition * @param data - the model to validate * @param definitions - the model definition * @param path - the path to the model * @param errors - the errors array * @returns {void} */ const BaseValidator = (data, definitions, path, errors) => { const cleanData = (0, remove_undefined_attributes_util_1.RemoveUndefinedAttributes)(data); if (!cleanData) { return; } //const errors: ValidationError[] = []; // Check if payload has additional fields (0, exports.validateAdditionalFields)(cleanData, definitions, path, errors); // Check if payload has required fields (0, exports.validateRequiredFieldByDefinition)(cleanData, definitions, path, errors); for (const dataKey in cleanData) { if (!Object.prototype.hasOwnProperty.call(cleanData, dataKey)) { continue; } const definition = definitions.find((definition) => definition.name === dataKey); if (!definition) { // throw new Error(`No definition found for ${dataKey}`); continue; } // TODO: Remove this when the contained field is implemented if (dataKey === 'contained') { //extract resource type const resourceType = cleanData[dataKey].resourceType; const validator = exports.InternalValidator[resourceType]; if (!validator) { // TODO fix this continue; } validator(cleanData[dataKey], `${path}.${String(definition.name)}`, errors); continue; } if (definition.type === 'Reference') { (0, exports.ValidateReferenceFormat)(cleanData[dataKey], definition.referenceTypes, `${path}.${String(definition.name)}`, errors); } if (definition.type === 'Resource') { //extract resource type const resourceType = cleanData[dataKey].resourceType; const validator = exports.InternalValidator[resourceType]; // TODO fix this if (!validator) continue; validator(cleanData[dataKey], `${path}.${String(definition.name)}`, errors); continue; } // Check if has value if (!hasValue(cleanData[dataKey])) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invariant', diagnostics: `+ Rule (ele-1). All FHIR elements must have a @value or children`, details: { text: `Path: ${path}.${String(definition.name)}. Value: ${cleanData[dataKey]}`, }, })); } if (definition.enumValues && definition.enumValues.length > 0) { // Check if the field is a valid enum value (0, exports.validateEnumValues)(cleanData[dataKey], definition, path, errors); } if (definition.isArray) { // check if the field is an array (0, exports.validateArray)(cleanData[dataKey], definition, path, errors); } // check if the field is a datatype or backbone element (0, exports.validateObject)(cleanData[dataKey], definition, path, errors); } }; exports.BaseValidator = BaseValidator; const validateAdditionalFields = (data, definitions, path, errors) => { const properties = Object.keys(data); const additionalFields = properties.filter((property) => !definitions.find((definition) => definition.name === property)); if (additionalFields.length > 0) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Invalid fields have been found.`, details: { text: `Path: ${path}. Additional fields: ${additionalFields.join(', ')}`, }, })); // throw new InvalidFieldException(path, additionalFields); } }; exports.validateAdditionalFields = validateAdditionalFields; const validateRequiredFieldByDefinition = (data, definitions, path, errors) => { for (const definition of definitions) { if (definition.isRequired && !hasValue(data[definition.name])) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'required', diagnostics: `Field ${String(definition.name)} is required`, details: { text: `Path: ${path}.${String(definition.name)}. Value: ${data[definition.name]}`, }, })); // throw new RequiredException(`${path}.${String(definition.name)}`, `${String(definition.name)}`); } } }; exports.validateRequiredFieldByDefinition = validateRequiredFieldByDefinition; /** * Validates if the data contains valid enum values according to the definition. * @param data - The data to be validated. * @param definition - The attribute definition to validate against. * @param path - The path to the data being validated (used for error messages). * @param errors * @throws Error if the data does not match the allowed enum values. */ const validateEnumValues = (data, definition, path, errors) => { if (!definition.enumValues || definition.isArray) return; if (definition.type === 'CodeableConcept') { const cc = data; if (cc.coding && cc.coding?.length > 0) { cc.coding.forEach((coding, index) => { if (coding.code && !definition.enumValues?.includes(coding.code)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'code-invalid', diagnostics: `Code must be one of [${definition.enumValues?.join(', ')}]`, details: { text: `Path: ${path}.${String(definition.name)}.coding[${index}].code`, }, })); } }); } } else { if (!definition.enumValues.includes(data)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'code-invalid', diagnostics: `Code must be one of [${definition.enumValues.join(', ')}]`, details: { text: `Path: ${path}.${String(definition.name)}`, }, })); // throw new Error(`Field must be one of [${definition.enumValues.join(', ')}] in ${path}.${String(definition.name)}`); } } }; exports.validateEnumValues = validateEnumValues; /** * Validates if the data is an array when the definition requires it to be an array. * @param data - The data to be validated. * @param definition - The attribute definition to validate against. * @param path - The path to the data being validated (used for error messages). * @param errors * @throws Error if the data is not an array when it should be. */ const validateArray = (data, definition, path, errors) => { // check if the field is an array if (definition.isArray && data && !Array.isArray(data)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Field ${String(definition.name)} must be an array`, details: { text: `Path: ${path}.${String(definition.name)}. Value: ${data}`, }, })); // throw new Error(`Field ${String(definition.name)} must be an array in ${path}`); } }; exports.validateArray = validateArray; const validateObject = (data, definition, path, errors) => { if (!data) { return; } const validator = parseValidator(definition.type); if (!validator) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'fatal', code: 'exception', diagnostics: `No validator found for ${String(definition.type)}`, details: { text: `Path: ${path}.${String(definition.name)}. Value: ${data}`, }, })); //throw new Error(`No validator found for ${String(definition.type)}`); return; } if (Array.isArray(data)) { data.forEach((item, index) => { validator(item, `${path}.${String(definition.name)}[${index}]`, errors); }); return; } validator(data, `${path}.${String(definition.name)}`, errors); }; exports.validateObject = validateObject; /** * @description Validates the format of a reference field. * @param value - The reference value to be validated. * @param resources - The list of valid resource params-types or 'all' to allow all resource params-types. * @param path - The path to the data being validated (used for error messages). * @param errors * @throws ReferenceException if the reference format is invalid or the resource type is not allowed. */ const ValidateReferenceFormat = (value, resources = null, path, errors) => { if (Array.isArray(value)) { value.forEach((val, index) => { (0, exports.ValidateReferenceFormat)(val, resources, `${path}[${index}]`, errors); }); return; } const { reference } = value; const referenceKeys = ['reference', 'type', 'identifier', 'display', '_display', '_reference', '_type']; // Exit if reference or resources are not provided if (!reference || !resources) return; // Determine the valid resource params-types const internalResources = resources.includes('Any') ? resource_list_util_1.resourceListUtil : resources; if (reference.includes('?')) { const [resourceType, ...query] = reference.split('?'); const resourceTypeForCheck = resourceType; // Check if the resource type is allowed if (!internalResources.includes(resourceTypeForCheck)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Invalid reference resource type. Reference must be one of [${internalResources.join(', ')}].`, details: { text: `Path: ${path}.reference. Value: ${reference}`, }, })); } const [referenceKey, value] = query[0].split('='); if (!value) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Invalid reference query. Reference query must be in the format 'key=value'.`, details: { text: `Path: ${path}.reference. Value: ${reference}`, }, })); } if (!referenceKeys.includes(referenceKey)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Invalid reference query key. Reference query key must be one of [${referenceKeys.join(', ')}].`, details: { text: `Path: ${path}.reference. Value: ${reference}`, }, })); } } if (reference.includes('/')) { // Extract the resource type from the reference string const [resourceType, id] = reference.split('/'); if (!id) { return; } const resourceTypeForCheck = resourceType; // Check if the resource type is allowed if (!internalResources.includes(resourceTypeForCheck)) { errors.push(new operation_outcome_exception_1.OperationOutcomeIssueException({ severity: 'error', code: 'invalid', diagnostics: `Invalid reference resource type. Reference must be one of [${internalResources.join(', ')}].`, details: { text: `Path: ${path}.reference. Value: ${reference}`, }, })); // throw new ReferenceException(resourceType, resources, `${path}.reference`); } } }; exports.ValidateReferenceFormat = ValidateReferenceFormat; /** * @description Checks if a value is not null, undefined, an empty string, an empty array, or an empty object. * @param value - The value to be checked. * @returns True if the value is valid (not null, undefined, or empty), otherwise false. */ function hasValue(value) { if (value === null || value === undefined) return false; // Checks for null and undefined if (typeof value === 'string' && value.trim() === '') return false; // Checks for empty strings if (Array.isArray(value) && value.length === 0) return false; // Checks for empty arrays return !(typeof value === 'object' && Object.keys(value).length === 0); // Checks for empty objects } function parseValidator(name) { switch (name) { // TODO check why this is not working case 'Period': return datatypes_1.PeriodValidator; default: return exports.InternalValidator[name]; } }