fhirbuilder
Version:
Another FHIR Tool to build FHIR Resources
325 lines (324 loc) • 14.8 kB
JavaScript
;
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];
}
}