diff --git a/src/export/CodeSystemExporter.ts b/src/export/CodeSystemExporter.ts index 2220afd12..13f24f2b4 100644 --- a/src/export/CodeSystemExporter.ts +++ b/src/export/CodeSystemExporter.ts @@ -158,14 +158,19 @@ export class CodeSystemExporter { } } + applyInsertRules(): void { + const codeSystems = this.tank.getAllCodeSystems(); + for (const cs of codeSystems) { + applyInsertRules(cs, this.tank); + } + } + exportCodeSystem(fshDefinition: FshCodeSystem): CodeSystem { if (this.pkg.codeSystems.some(cs => cs.name === fshDefinition.name)) { return; } const codeSystem = new CodeSystem(); this.setMetadata(codeSystem, fshDefinition); - // fshDefinition.rules may include insert rules, which must be expanded before applying other rules - applyInsertRules(fshDefinition, this.tank); const csStructureDefinition = StructureDefinition.fromJSON( this.fisher.fishForFHIR('CodeSystem', Type.Resource) ); diff --git a/src/export/FHIRExporter.ts b/src/export/FHIRExporter.ts index 2d9150d85..372d58213 100644 --- a/src/export/FHIRExporter.ts +++ b/src/export/FHIRExporter.ts @@ -36,6 +36,12 @@ export class FHIRExporter { } export(): Package { + this.structureDefinitionExporter.applyInsertRules(); + this.codeSystemExporter.applyInsertRules(); + this.valueSetExporter.applyInsertRules(); + this.instanceExporter.applyInsertRules(); + this.mappingExporter.applyInsertRules(); + this.structureDefinitionExporter.export(); this.codeSystemExporter.export(); this.valueSetExporter.export(); diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts index 34994adfa..a4119425a 100644 --- a/src/export/InstanceExporter.ts +++ b/src/export/InstanceExporter.ts @@ -64,8 +64,6 @@ export class InstanceExporter implements Fishable { instanceDef: InstanceDefinition, instanceOfStructureDefinition: StructureDefinition ): InstanceDefinition { - // The fshInstanceDef.rules list may contain insert rules, which will be expanded to AssignmentRules - applyInsertRules(fshInstanceDef, this.tank); resolveSoftIndexing(fshInstanceDef.rules); let rules = fshInstanceDef.rules.map(r => cloneDeep(r)) as AssignmentRule[]; // Normalize all rules to not use the optional [0] index @@ -429,8 +427,15 @@ export class InstanceExporter implements Fishable { return this.fisher.fishForMetadata(item, Type.Instance); } + applyInsertRules(): void { + const instances = this.tank.getAllInstances(); + for (const instance of instances) { + applyInsertRules(instance, this.tank); + } + } + exportInstance(fshDefinition: Instance): InstanceDefinition { - if (this.pkg.instances.some(i => i._instanceMeta.name === fshDefinition.id)) { + if (this.pkg.instances.some(i => i._instanceMeta.name === fshDefinition.name)) { return; } @@ -475,7 +480,7 @@ export class InstanceExporter implements Fishable { const instanceOfStructureDefinition = StructureDefinition.fromJSON(json); let instanceDef = new InstanceDefinition(); - instanceDef._instanceMeta.name = fshDefinition.id; // This is name of the instance in the FSH + instanceDef._instanceMeta.name = fshDefinition.name; // This is name of the instance in the FSH if (fshDefinition.title == '') { logger.warn(`Instance ${fshDefinition.name} has a title field that should not be empty.`); } diff --git a/src/export/MappingExporter.ts b/src/export/MappingExporter.ts index 8f435e380..4779cb673 100644 --- a/src/export/MappingExporter.ts +++ b/src/export/MappingExporter.ts @@ -42,8 +42,6 @@ export class MappingExporter { * @param {Mapping} fshDefinition - The Mapping definition */ private setMappingRules(structDef: StructureDefinition, fshDefinition: Mapping): void { - // Before applying mapping rules, applyInsertRules will expand any insert rules into mapping rules - applyInsertRules(fshDefinition, this.tank); resolveSoftIndexing(fshDefinition.rules); for (const rule of fshDefinition.rules as MappingRule[]) { const element = structDef.findElementByPath(rule.path, this.fisher); @@ -62,6 +60,13 @@ export class MappingExporter { } } + applyInsertRules(): void { + const mappings = this.tank.getAllMappings(); + for (const mapping of mappings) { + applyInsertRules(mapping, this.tank); + } + } + /** * Exports a Mapping by finding the Source StructureDefinition and applying rules * @param {Mapping} fshDefinition - The Mapping definition to apply diff --git a/src/export/StructureDefinitionExporter.ts b/src/export/StructureDefinitionExporter.ts index dad248736..1f38a6e36 100644 --- a/src/export/StructureDefinitionExporter.ts +++ b/src/export/StructureDefinitionExporter.ts @@ -864,6 +864,13 @@ export class StructureDefinitionExporter implements Fishable { return this.fisher.fishForMetadata(item, ...types); } + applyInsertRules(): void { + const structureDefinitions = this.tank.getAllStructureDefinitions(); + for (const sd of structureDefinitions) { + applyInsertRules(sd, this.tank); + } + } + /** * Exports Profile, Extension, Logical model, and custom Resource to StructureDefinition * @param {Profile | Extension | Logical | Resource} fshDefinition - The Profile or Extension @@ -917,9 +924,6 @@ export class StructureDefinitionExporter implements Fishable { this.pkg.profiles.push(structDef); } - // fshDefinition.rules may include insert rules, which must be expanded before applying other rules - applyInsertRules(fshDefinition, this.tank); - this.preprocessStructureDefinition(fshDefinition, structDef.type === 'Extension'); this.setRules(structDef, fshDefinition); diff --git a/src/export/ValueSetExporter.ts b/src/export/ValueSetExporter.ts index 8ed5c7a06..0175966fc 100644 --- a/src/export/ValueSetExporter.ts +++ b/src/export/ValueSetExporter.ts @@ -195,6 +195,13 @@ export class ValueSetExporter { } } + applyInsertRules(): void { + const valueSets = this.tank.getAllValueSets(); + for (const vs of valueSets) { + applyInsertRules(vs, this.tank); + } + } + export(): Package { const valueSets = this.tank.getAllValueSets(); for (const valueSet of valueSets) { @@ -216,8 +223,6 @@ export class ValueSetExporter { } const vs = new ValueSet(); this.setMetadata(vs, fshDefinition); - // fshDefinition.rules may include insert rules, which must be expanded before applying other rules - applyInsertRules(fshDefinition, this.tank); this.setCaretRules( vs, fshDefinition.rules.filter(rule => rule instanceof CaretValueRule) as CaretValueRule[] diff --git a/src/fhirtypes/common.ts b/src/fhirtypes/common.ts index 97a2a9a6f..93ce44585 100644 --- a/src/fhirtypes/common.ts +++ b/src/fhirtypes/common.ts @@ -429,14 +429,9 @@ export function replaceReferences( ); // If we can't find a matching instance, just leave the reference as is if (instance && instanceMeta) { - // If the instance has a rule setting id, that overrides instance.id - const idRule = instance.rules.find( - r => r.path === 'id' && r instanceof AssignmentRule - ) as AssignmentRule; - const id = idRule?.value ?? instance.id; clone = cloneDeep(rule); const assignedReference = getRuleValue(clone) as FshReference; - assignedReference.reference = `${instanceMeta.sdType}/${id}`; + assignedReference.reference = `${instanceMeta.sdType}/${instance.id}`; assignedReference.sdType = instanceMeta.sdType; } } else if (value instanceof FshCode) { diff --git a/src/fhirtypes/mixins.ts b/src/fhirtypes/mixins.ts index d9458ae39..aaffb7c29 100644 --- a/src/fhirtypes/mixins.ts +++ b/src/fhirtypes/mixins.ts @@ -1,8 +1,9 @@ import _ from 'lodash'; -import { CaretValueRule } from '../fshtypes/rules'; +import { CaretValueRule, AssignmentRule } from '../fshtypes/rules'; import { FshValueSet, FshStructure, FshCodeSystem, Instance } from '../fshtypes'; import { logger } from '../utils'; import { FHIRId, idRegex } from './primitiveTypes'; +import { findIdAssignmentRule, findIdCaretRule } from '../fshtypes/common'; const nameRegex = /^[A-Z]([A-Za-z0-9_]){0,254}$/; @@ -61,10 +62,12 @@ export class HasId { * @param {FshStructure | FshCodeSystem | FshValueSet} fshDefinition - The entity who's id is being set */ validateId(fshDefinition: FshStructure | FshCodeSystem | FshValueSet | Instance) { - const idRule = _.findLast( - fshDefinition.rules, - rule => rule instanceof CaretValueRule && rule.caretPath === 'id' && rule.path === '' - ) as CaretValueRule; + let idRule: AssignmentRule | CaretValueRule; + if (fshDefinition instanceof Instance) { + idRule = findIdAssignmentRule(fshDefinition.rules); + } else { + idRule = findIdCaretRule(fshDefinition.rules); + } const idToCheck = idRule ? (idRule.value as string) : this.id; let validId = idRegex.test(idToCheck); if (!validId && !idRule && nameRegex.test(this.id) && !(fshDefinition instanceof Instance)) { diff --git a/src/fshtypes/FshCodeSystem.ts b/src/fshtypes/FshCodeSystem.ts index 56b78d3d8..b19dcf2b1 100644 --- a/src/fshtypes/FshCodeSystem.ts +++ b/src/fshtypes/FshCodeSystem.ts @@ -3,7 +3,7 @@ import { CodeSystemDuplicateCodeError } from '../errors/CodeSystemDuplicateCodeE import { CodeSystemIncorrectHierarchyError } from '../errors/CodeSystemIncorrectHierarchyError'; import { CaretValueRule, InsertRule, ConceptRule } from './rules'; import { EOL } from 'os'; -import { fshifyString } from './common'; +import { fshifyString, findIdCaretRule } from './common'; import isEqual from 'lodash/isEqual'; /** @@ -11,7 +11,7 @@ import isEqual from 'lodash/isEqual'; * @see {@link http://hl7.org/fhir/codesystem-definitions.html} */ export class FshCodeSystem extends FshEntity { - id: string; + private _id: string; title?: string; description?: string; rules: (ConceptRule | CaretValueRule | InsertRule)[]; @@ -26,6 +26,18 @@ export class FshCodeSystem extends FshEntity { return 'FshCodeSystem'; } + get id() { + const idCaretRule = findIdCaretRule(this.rules); + if (idCaretRule) { + return idCaretRule.value.toString(); + } + return this._id; + } + + set id(id: string) { + this._id = id; + } + addConcept(newConcept: ConceptRule) { if (this.checkConcept(newConcept)) { this.rules.push(newConcept); diff --git a/src/fshtypes/FshStructure.ts b/src/fshtypes/FshStructure.ts index aa908b2e8..27885cae5 100644 --- a/src/fshtypes/FshStructure.ts +++ b/src/fshtypes/FshStructure.ts @@ -1,10 +1,10 @@ import { FshEntity } from './FshEntity'; import { Rule } from './rules'; import { EOL } from 'os'; -import { fshifyString } from './common'; +import { fshifyString, findIdCaretRule } from './common'; export abstract class FshStructure extends FshEntity { - id: string; + private _id: string; parent?: string; title?: string; description?: string; @@ -20,6 +20,18 @@ export abstract class FshStructure extends FshEntity { return 'FshStructure'; } + get id() { + const idCaretRule = findIdCaretRule(this.rules); + if (idCaretRule) { + return idCaretRule.value.toString(); + } + return this._id; + } + + set id(id: string) { + this._id = id; + } + metadataToFSH(): string { const resultLines: string[] = []; resultLines.push(`${this.constructorName}: ${this.name}`); diff --git a/src/fshtypes/FshValueSet.ts b/src/fshtypes/FshValueSet.ts index 049e48b48..6f3b5d62a 100644 --- a/src/fshtypes/FshValueSet.ts +++ b/src/fshtypes/FshValueSet.ts @@ -1,14 +1,14 @@ import { FshEntity } from './FshEntity'; import { CaretValueRule, InsertRule, ValueSetComponentRule } from './rules'; import { EOL } from 'os'; -import { fshifyString } from './common'; +import { fshifyString, findIdCaretRule } from './common'; /** * For more information about the composition of a ValueSet, * @see {@link http://hl7.org/fhir/valueset-definitions.html#ValueSet.compose} */ export class FshValueSet extends FshEntity { - id: string; + private _id: string; title?: string; description?: string; rules: (ValueSetComponentRule | CaretValueRule | InsertRule)[]; @@ -23,6 +23,18 @@ export class FshValueSet extends FshEntity { return 'FshValueSet'; } + get id() { + const idCaretRule = findIdCaretRule(this.rules); + if (idCaretRule) { + return idCaretRule.value.toString(); + } + return this._id; + } + + set id(id: string) { + this._id = id; + } + metadataToFSH(): string { const resultLines: string[] = []; resultLines.push(`ValueSet: ${this.name}`); diff --git a/src/fshtypes/Instance.ts b/src/fshtypes/Instance.ts index 854d2a992..69143d220 100644 --- a/src/fshtypes/Instance.ts +++ b/src/fshtypes/Instance.ts @@ -1,11 +1,11 @@ import { AssignmentRule, InsertRule } from './rules'; import { FshEntity } from './FshEntity'; import { EOL } from 'os'; -import { fshifyString } from './common'; +import { fshifyString, findIdAssignmentRule } from './common'; import { InstanceUsage } from './InstanceUsage'; export class Instance extends FshEntity { - id: string; + private _id: string; title?: string; instanceOf: string; description?: string; @@ -23,6 +23,18 @@ export class Instance extends FshEntity { return 'Instance'; } + get id() { + const idAssigmentRule = findIdAssignmentRule(this.rules); + if (idAssigmentRule) { + return idAssigmentRule.value.toString(); + } + return this._id; + } + + set id(id: string) { + this._id = id; + } + metadataToFSH() { const resultLines: string[] = []; resultLines.push(`Instance: ${this.name}`); diff --git a/src/fshtypes/common.ts b/src/fshtypes/common.ts index 99c1ee04a..c1345fb39 100644 --- a/src/fshtypes/common.ts +++ b/src/fshtypes/common.ts @@ -1,4 +1,5 @@ -import { OnlyRuleType } from './rules/OnlyRule'; +import { CaretValueRule, Rule, OnlyRuleType, AssignmentRule } from './rules'; +import { findLast } from 'lodash'; export function typeString(types: OnlyRuleType[]): string { const references: OnlyRuleType[] = []; @@ -32,3 +33,21 @@ export function fshifyString(input: string): string { .replace(/\r/g, '\\r') .replace(/\t/g, '\\t'); } + +export function findIdCaretRule(rules: Rule[]): CaretValueRule | undefined { + return findLast( + rules, + rule => + rule instanceof CaretValueRule && + rule.path === '' && + rule.caretPath === 'id' && + typeof rule.value === 'string' + ) as CaretValueRule; +} + +export function findIdAssignmentRule(rules: Rule[]): AssignmentRule | undefined { + return findLast( + rules, + rule => rule instanceof AssignmentRule && rule.path === 'id' && typeof rule.value === 'string' + ) as AssignmentRule; +} diff --git a/src/import/FSHTank.ts b/src/import/FSHTank.ts index ed2b35462..7aa6b34c2 100644 --- a/src/import/FSHTank.ts +++ b/src/import/FSHTank.ts @@ -15,7 +15,7 @@ import { } from '../fshtypes'; import { AssignmentRule } from '../fshtypes/rules'; import { Type, Metadata, Fishable } from '../utils/Fishable'; -import { getUrlFromFshDefinition, applyInsertRules } from '../fhirtypes/common'; +import { getUrlFromFshDefinition } from '../fhirtypes/common'; import flatMap from 'lodash/flatMap'; export class FSHTank implements Fishable { @@ -394,8 +394,6 @@ export class FSHTank implements Fishable { meta.resourceType = 'CodeSystem'; } } else if (result instanceof Instance) { - // the url may be added in a RuleSet, so apply insert rules - applyInsertRules(result, this); result.rules?.forEach(r => { if (r.path === 'url' && r instanceof AssignmentRule && typeof r.value === 'string') { meta.url = r.value; diff --git a/test/export/CodeSystemExporter.test.ts b/test/export/CodeSystemExporter.test.ts index 87d32f1c1..01387feed 100644 --- a/test/export/CodeSystemExporter.test.ts +++ b/test/export/CodeSystemExporter.test.ts @@ -813,6 +813,7 @@ describe('CodeSystemExporter', () => { insertRule.ruleSet = 'Bar'; cs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.title).toBe('Wow fancy'); }); @@ -844,6 +845,7 @@ describe('CodeSystemExporter', () => { insertRule.ruleSet = 'Bar'; cs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.contact).toEqual([ { @@ -878,6 +880,7 @@ describe('CodeSystemExporter', () => { insertRule.pathArray = ['bear']; cs.rules.push(conceptRule, insertRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.concept[0]).toEqual({ code: 'bear', @@ -907,6 +910,7 @@ describe('CodeSystemExporter', () => { insertRule.ruleSet = 'Bar'; cs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.concept[0].code).toBe('lion'); expect(exported.count).toBe(1); @@ -932,6 +936,7 @@ describe('CodeSystemExporter', () => { insertRule.ruleSet = 'Bar'; cs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); // CaretRule is still applied expect(exported.title).toBe('Wow fancy'); @@ -959,6 +964,7 @@ describe('CodeSystemExporter', () => { const alligatorRule = new ConceptRule('alligator'); cs.rules.push(bearRule, insertRule, alligatorRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.concept[0].code).toBe('bear'); expect(exported.concept[1].code).toBe('lion'); @@ -988,6 +994,7 @@ describe('CodeSystemExporter', () => { const alligatorRule = new ConceptRule('alligator', 'Alligator'); cs.rules.push(bearRule, insertRule, alligatorRule); + exporter.applyInsertRules(); const exported = exporter.exportCodeSystem(cs); expect(exported.concept[0].code).toBe('bear'); expect(exported.concept[0].display).toBe('Regular Bear'); diff --git a/test/export/InstanceExporter.test.ts b/test/export/InstanceExporter.test.ts index e9023da0f..8af14e468 100644 --- a/test/export/InstanceExporter.test.ts +++ b/test/export/InstanceExporter.test.ts @@ -689,7 +689,9 @@ describe('InstanceExporter', () => { .withFile('Some.fsh') .withLocation([3, 6, 6, 45]); myExamplePatient.instanceOf = 'Patient'; - const assignedValRule = new AssignmentRule('id'); + const assignedValRule = new AssignmentRule('id') + .withFile('Some.fsh') + .withLocation([5, 0, 6, 45]); assignedValRule.value = 'Some Patient'; myExamplePatient.rules.push(assignedValRule); const exported = exportInstance(myExamplePatient); @@ -699,13 +701,15 @@ describe('InstanceExporter', () => { }; expect(exported.toJSON()).toEqual(expectedInstanceJSON); expect(loggerSpy.getLastMessage()).toMatch(/does not represent a valid FHIR id/s); - expect(loggerSpy.getLastMessage()).toMatch(/File: Some\.fsh.*Line: 3 - 6\D*/s); + expect(loggerSpy.getLastMessage()).toMatch(/File: Some\.fsh.*Line: 5 - 6\D*/s); }); it('should sanitize the id and log a message when a valid name is used to make an invalid id', () => { const instance = new Instance('Foo').withFile('Wrong.fsh').withLocation([2, 8, 5, 18]); instance.instanceOf = 'Patient'; - const assignedValRule = new AssignmentRule('id'); + const assignedValRule = new AssignmentRule('id') + .withFile('Wrong.fsh') + .withLocation([5, 0, 5, 18]); assignedValRule.value = 'Some_Patient'; instance.rules.push(assignedValRule); const exported = exportInstance(instance); @@ -717,13 +721,15 @@ describe('InstanceExporter', () => { expect(loggerSpy.getLastMessage('error')).toMatch( /The string "Some_Patient" does not represent a valid FHIR id\. FHIR ids only allow ASCII letters \(A-Z, a-z\), numbers \(0-9\), hyphens \(-\), and dots \(\.\), with a length limit of 64 characters\. Avoid this warning by changing the Instance declaration to follow the FHIR id requirements\./s ); - expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 2 - 5\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 5\D*/s); }); it('should log a message when a long valid name is used to make an invalid id', () => { const instance = new Instance('Foo').withFile('Wrong.fsh').withLocation([2, 8, 5, 18]); instance.instanceOf = 'Patient'; - const assignedValRule = new AssignmentRule('id'); + const assignedValRule = new AssignmentRule('id') + .withFile('Wrong.fsh') + .withLocation([4, 0, 5, 18]); let longId = 'Toolong'; while (longId.length < 65) longId += 'longer'; assignedValRule.value = longId; @@ -737,7 +743,7 @@ describe('InstanceExporter', () => { expect(loggerSpy.getLastMessage('error')).toMatch( /The string "Toolong(longer)+" does not represent a valid FHIR id\. FHIR ids only allow ASCII letters \(A-Z, a-z\), numbers \(0-9\), hyphens \(-\), and dots \(\.\), with a length limit of 64 characters\. Avoid this warning by changing the Instance declaration to follow the FHIR id requirements\./s ); - expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 2 - 5\D*/s); + expect(loggerSpy.getLastMessage('error')).toMatch(/File: Wrong\.fsh.*Line: 4 - 5\D*/s); }); it('should log an error when multiple instances of the same type have the same id', () => { @@ -2588,6 +2594,7 @@ describe('InstanceExporter', () => { libraryInstance.rules.push(libraryInsert, libraryStatus, libraryType); doc.instances.set(libraryInstance.name, libraryInstance); + exporter.applyInsertRules(); const instances = exporter.export().instances; const exportedActivity = instances.find( instanceDefinition => instanceDefinition.id === 'MyActivity' @@ -5174,6 +5181,7 @@ describe('InstanceExporter', () => { insertRule.ruleSet = 'Bar'; instance.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportInstance(instance); expect(exported.id).toBe('my-id'); }); @@ -5195,6 +5203,7 @@ describe('InstanceExporter', () => { const insertRule = new InsertRule(''); insertRule.ruleSet = 'Bar'; patientInstance.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportInstance(patientInstance); expect(exported.name).toEqual([ { @@ -5227,6 +5236,7 @@ describe('InstanceExporter', () => { insertRule.ruleSet = 'Bar'; instance.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportInstance(instance); // valueRule is still applied expect(exported.id).toBe('my-id'); diff --git a/test/export/MappingExporter.test.ts b/test/export/MappingExporter.test.ts index 0c9b5592f..908b5019b 100644 --- a/test/export/MappingExporter.test.ts +++ b/test/export/MappingExporter.test.ts @@ -487,6 +487,7 @@ describe('MappingExporter', () => { insertRule.ruleSet = 'Bar'; mapping.rules.push(insertRule); + exporter.applyInsertRules(); exporter.export(); const status = observation.elements.find(e => e.id === 'Observation.status'); const exported = status.mapping.slice(-1)[0]; @@ -514,6 +515,7 @@ describe('MappingExporter', () => { insertRule.ruleSet = 'Bar'; mapping.rules.push(insertRule); + exporter.applyInsertRules(); exporter.export(); // mapping rule is still applied const status = observation.elements.find(e => e.id === 'Observation.status'); diff --git a/test/export/StructureDefinitionExporter.test.ts b/test/export/StructureDefinitionExporter.test.ts index 3243b8fee..6d6e012bd 100644 --- a/test/export/StructureDefinitionExporter.test.ts +++ b/test/export/StructureDefinitionExporter.test.ts @@ -1116,7 +1116,7 @@ describe('StructureDefinitionExporter R4', () => { it('should properly set/clear all metadata properties for an extension', () => { const extension = new Extension('Foo'); extension.parent = 'patient-mothersMaidenName'; - doc.profiles.set(extension.name, extension); + doc.extensions.set(extension.name, extension); exporter.exportStructDef(extension); const exported = pkg.extensions[0]; @@ -7912,6 +7912,7 @@ describe('StructureDefinitionExporter R4', () => { insertRule.ruleSet = 'Bar'; profile.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportStructDef(profile); expect(exported.title).toBe('Wow fancy'); }); @@ -7934,6 +7935,7 @@ describe('StructureDefinitionExporter R4', () => { insertRule.ruleSet = 'Bar'; profile.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportStructDef(profile); // CaretRule is still applied expect(exported.title).toBe('Wow fancy'); diff --git a/test/export/ValueSetExporter.test.ts b/test/export/ValueSetExporter.test.ts index 2e549224c..aca36b6df 100644 --- a/test/export/ValueSetExporter.test.ts +++ b/test/export/ValueSetExporter.test.ts @@ -1747,6 +1747,7 @@ describe('ValueSetExporter', () => { insertRule.ruleSet = 'Bar'; vs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportValueSet(vs); expect(exported.title).toBe('Wow fancy'); }); @@ -1769,6 +1770,7 @@ describe('ValueSetExporter', () => { insertRule.ruleSet = 'Bar'; vs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportValueSet(vs); expect(exported.contact).toEqual([ { @@ -1803,6 +1805,7 @@ describe('ValueSetExporter', () => { insertRule.ruleSet = 'Bar'; vs.rules.push(insertRule); + exporter.applyInsertRules(); const exported = exporter.exportValueSet(vs); // CaretRule is still applied expect(exported.title).toBe('Wow fancy'); diff --git a/test/import/FSHTank.test.ts b/test/import/FSHTank.test.ts index 7a0ea7bb1..da4617d75 100644 --- a/test/import/FSHTank.test.ts +++ b/test/import/FSHTank.test.ts @@ -15,7 +15,7 @@ import { } from '../../src/fshtypes'; import { Metadata, Type } from '../../src/utils/Fishable'; import { minimalConfig } from '../utils/minimalConfig'; -import { AssignmentRule } from '../../src/fshtypes/rules'; +import { AssignmentRule, CaretValueRule } from '../../src/fshtypes/rules'; describe('FSHTank', () => { let tank: FSHTank; @@ -28,6 +28,13 @@ describe('FSHTank', () => { doc1.profiles.set('Profile2', new Profile('Profile2')); doc1.profiles.get('Profile2').id = 'prf2'; doc1.profiles.get('Profile2').parent = 'Observation'; + const profile3 = new Profile('Profile3'); + profile3.parent = 'Observation'; + const profile3Id = new CaretValueRule(''); + profile3Id.caretPath = 'id'; + profile3Id.value = 'prf3'; + profile3.rules.push(profile3Id); + doc1.profiles.set(profile3.name, profile3); doc1.instances.set('ProfileInstance', new Instance('ProfileInstance')); doc1.instances.get('ProfileInstance').instanceOf = 'StructureDefinition'; doc1.instances.get('ProfileInstance').usage = 'Definition'; @@ -41,6 +48,12 @@ describe('FSHTank', () => { doc2.aliases.set('BAR', 'http://bar.com'); doc2.extensions.set('Extension2', new Extension('Extension2')); doc2.extensions.get('Extension2').id = 'ext2'; + const ext3 = new Extension('Extension3'); + const ext3Id = new CaretValueRule(''); + ext3Id.caretPath = 'id'; + ext3Id.value = 'ext3'; + ext3.rules.push(ext3Id); + doc2.extensions.set(ext3.name, ext3); doc2.instances.set('ExtensionInstance', new Instance('ExtensionInstance')); doc2.instances.get('ExtensionInstance').instanceOf = 'StructureDefinition'; doc2.instances.get('ExtensionInstance').usage = 'Definition'; @@ -94,11 +107,47 @@ describe('FSHTank', () => { .rules.push(resourceInstanceDerivation, resourceInstanceKind); doc3.valueSets.set('ValueSet2', new FshValueSet('ValueSet2')); doc3.valueSets.get('ValueSet2').id = 'vs2'; + const vs3 = new FshValueSet('ValueSet3'); + const vs3Id = new CaretValueRule(''); + vs3Id.caretPath = 'id'; + vs3Id.value = 'vs3'; + vs3.rules.push(vs3Id); + doc3.valueSets.set(vs3.name, vs3); doc3.codeSystems.set('CodeSystem2', new FshCodeSystem('CodeSystem2')); doc3.codeSystems.get('CodeSystem2').id = 'cs2'; + const cs3 = new FshCodeSystem('CodeSystem3'); + const cs3Id = new CaretValueRule(''); + cs3Id.caretPath = 'id'; + cs3Id.value = 'cs3'; + cs3.rules.push(cs3Id); + doc3.codeSystems.set(cs3.name, cs3); + const log3 = new Logical('Logical3'); + const log3Id = new CaretValueRule(''); + log3Id.caretPath = 'id'; + log3Id.value = 'log3'; + log3.rules.push(log3Id); + doc3.logicals.set(log3.name, log3); + const res3 = new Resource('Resource3'); + const res3Id = new CaretValueRule(''); + res3Id.caretPath = 'id'; + res3Id.value = 'res3'; + res3.rules.push(res3Id); + doc3.resources.set(res3.name, res3); doc3.instances.set('Instance1', new Instance('Instance1')); doc3.instances.get('Instance1').id = 'inst1'; doc3.instances.get('Instance1').instanceOf = 'Condition'; + const inst2 = new Instance('Instance2'); + inst2.instanceOf = 'Condition'; + const inst2Id = new AssignmentRule('id'); + inst2Id.value = 'inst2'; + inst2.rules.push(inst2Id); + doc3.instances.set(inst2.name, inst2); + const idRuleset = new RuleSet('IdRuleset'); + const idAssignment = new AssignmentRule('id'); + idAssignment.value = 'inst3'; + idRuleset.rules.push(idAssignment); + doc3.ruleSets.set(idRuleset.name, idRuleset); + doc3.invariants.set('Invariant1', new Invariant('Invariant1')); doc3.invariants.get('Invariant1').description = 'first invariant'; doc3.invariants.get('Invariant1').severity = new FshCode('error'); @@ -153,7 +202,7 @@ describe('FSHTank', () => { }); it('should not find fish when fishing by invalid name/id/url', () => { - expect(tank.fish('Profile3')).toBeUndefined(); + expect(tank.fish('ProfileFake')).toBeUndefined(); }); it('should only find profiles when profiles are requested', () => { @@ -203,6 +252,10 @@ describe('FSHTank', () => { expect(tank.fish('ProfileInstance', Type.Profile)).toBeUndefined(); }); + it('should find a profile when fishing by id when the profile id is set by a caret rule', () => { + expect(tank.fish('prf3').name).toBe('Profile3'); + }); + it('should only find extensions when extensions are requested', () => { expect(tank.fish('ext1', Type.Extension).name).toBe('Extension1'); expect( @@ -264,6 +317,10 @@ describe('FSHTank', () => { expect(tank.fish('ExtensionInstance', Type.Extension)).toBeUndefined(); }); + it('should find an extension when fishing by id when the extension id is set by a caret rule', () => { + expect(tank.fish('ext3').name).toBe('Extension3'); + }); + it('should only find logical models when logical models are requested', () => { expect(tank.fish('log1', Type.Logical).name).toBe('Logical1'); expect( @@ -301,6 +358,10 @@ describe('FSHTank', () => { ).toBeUndefined(); }); + it('should find a logical model when fishing by id when the logical model id is set by a caret rule', () => { + expect(tank.fish('log3').name).toBe('Logical3'); + }); + it('should only find resources when resources are requested', () => { expect(tank.fish('res2', Type.Resource).name).toBe('Resource2'); expect( @@ -338,6 +399,10 @@ describe('FSHTank', () => { ).toBeUndefined(); }); + it('should find a resource when fishing by id when the resource id is set by a caret rule', () => { + expect(tank.fish('res3').name).toBe('Resource3'); + }); + it('should only find valuesets when valuesets are requested', () => { expect(tank.fish('vs1', Type.ValueSet).name).toBe('ValueSet1'); expect( @@ -375,6 +440,10 @@ describe('FSHTank', () => { ).toBeUndefined(); }); + it('should find a valueset when fishing by id when the valueset id is set by a caret rule', () => { + expect(tank.fish('vs3').name).toBe('ValueSet3'); + }); + it('should only find codesystems when codesystems are requested', () => { expect(tank.fish('cs1', Type.CodeSystem).name).toBe('CodeSystem1'); expect( @@ -412,6 +481,10 @@ describe('FSHTank', () => { ).toBeUndefined(); }); + it('should find a codesystem when fishing by id when the codesystem id is set by a caret rule', () => { + expect(tank.fish('cs3').name).toBe('CodeSystem3'); + }); + it('should only find instances when instances are requested', () => { expect(tank.fish('inst1', Type.Instance).name).toBe('Instance1'); expect( @@ -431,6 +504,10 @@ describe('FSHTank', () => { ).toBeUndefined(); }); + it('should find an instance when fishing by id when the instance id is set by an assignment rule', () => { + expect(tank.fish('inst2').name).toBe('Instance2'); + }); + it('should only find invariants when invariants are requested', () => { expect(tank.fish('Invariant1', Type.Invariant).name).toBe('Invariant1'); expect( @@ -592,7 +669,7 @@ describe('FSHTank', () => { }); it('should not find fish when fishing by invalid name/id/url', () => { - expect(tank.fishForMetadata('Profile3')).toBeUndefined(); + expect(tank.fishForMetadata('ProfileFake')).toBeUndefined(); }); it('should only find profiles when profiles are requested', () => {