From fa627f7557d74af0c811a9bb443364111ba0e417 Mon Sep 17 00:00:00 2001 From: sebg-mio42 Date: Tue, 16 Jul 2024 16:58:33 +0200 Subject: [PATCH 1/3] versionId --- src/export/InstanceExporter.ts | 5 +++++ src/fhirtypes/InstanceDefinition.ts | 5 ++++- src/fshtypes/Instance.ts | 2 ++ src/import/FSHImporter.ts | 19 ++++++++++++------- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts index f2a9bb6f6..4020254f2 100644 --- a/src/export/InstanceExporter.ts +++ b/src/export/InstanceExporter.ts @@ -815,6 +815,9 @@ export class InstanceExporter implements Fishable { if (fshDefinition.title) { instanceDef._instanceMeta.title = fshDefinition.title; } + if (fshDefinition.versionId) { + instanceDef._instanceMeta.versionId = fshDefinition.versionId; + } if (fshDefinition.description) { instanceDef._instanceMeta.description = fshDefinition.description; } @@ -952,6 +955,8 @@ export class InstanceExporter implements Fishable { instanceDef.resourceType === instance.resourceType && (instanceDef.id ?? instanceDef._instanceMeta.name) === (instance.id ?? instance._instanceMeta.name) && + (instanceDef.versionId) === + (instance.versionId) && instanceDef !== instance ) ) { diff --git a/src/fhirtypes/InstanceDefinition.ts b/src/fhirtypes/InstanceDefinition.ts index 70f3a1c2d..3dc124626 100644 --- a/src/fhirtypes/InstanceDefinition.ts +++ b/src/fhirtypes/InstanceDefinition.ts @@ -26,7 +26,9 @@ export class InstanceDefinition { getFileName(): string { // Logical instances should use Binary type. See: https://fshschool.org/docs/sushi/tips/#instances-of-logical-models const type = this._instanceMeta.sdKind === 'logical' ? 'Binary' : this.resourceType; - return sanitize(`${type}-${this.id ?? this._instanceMeta.name}.json`, { + let versionString = (this._instanceMeta.versionId) ? `_v${this._instanceMeta.versionId}` : `` + let filename = `${type}-${this.id ?? this._instanceMeta.name}${versionString}.json`; + return sanitize(filename, { replacement: '-' }); } @@ -61,6 +63,7 @@ type InstanceMeta = { sdType?: string; sdKind?: string; instanceOfUrl?: string; + versionId?: string; }; // eslint-disable-next-line @typescript-eslint/no-empty-interface diff --git a/src/fshtypes/Instance.ts b/src/fshtypes/Instance.ts index a9d5aaab9..5a6138a14 100644 --- a/src/fshtypes/Instance.ts +++ b/src/fshtypes/Instance.ts @@ -10,6 +10,7 @@ export class Instance extends FshEntity { instanceOf: string; description?: string; usage?: InstanceUsage; + versionId?: string; rules: (AssignmentRule | InsertRule | PathRule)[]; constructor(public name: string) { @@ -17,6 +18,7 @@ export class Instance extends FshEntity { this.id = name; // init same as name this.rules = []; this.usage = 'Example'; // init to Example (default) + this.versionId = undefined; // init to undefined (default) } get constructorName() { diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts index 96c00d7e1..296f08d9c 100644 --- a/src/import/FSHImporter.ts +++ b/src/import/FSHImporter.ts @@ -448,15 +448,17 @@ export class FSHImporter extends FSHVisitor { const instance = new Instance(ctx.name().getText()) .withLocation(location) .withFile(this.currentFile); - if (this.docs.some(doc => doc.instances.has(instance.name))) { - logger.error(`Skipping Instance: an Instance named ${instance.name} already exists.`, { - file: this.currentFile, - location - }); - } else { + { try { this.parseInstance(instance, location, ctx.instanceMetadata(), ctx.instanceRule()); - this.currentDoc.instances.set(instance.name, instance); + if (this.docs.some(doc => (doc.instances.has(instance.name) && (Array.from(doc.instances.values()).some(entity => entity.name === instance.name && entity.versionId === instance.versionId))))) { + logger.error(`Skipping Instance: an Instance named ${instance.name} with versionId ${instance.versionId} already exists.`, { + file: this.currentFile, + location + }); + } else { + this.currentDoc.instances.set(instance.name, instance); + } } catch (e) { logger.error(e.message, instance.sourceInfo); if (e.stack) { @@ -518,6 +520,9 @@ export class FSHImporter extends FSHVisitor { const rule = this.visitInstanceRule(instanceRule); if (rule) { instance.rules.push(rule); + if (rule instanceof AssignmentRule && rule.path === `meta.versionId`) { + instance.versionId = rule.value.toString(); + } } }); } From dd7e62fdb0de2629e9ed21f6c4eec3421d312720 Mon Sep 17 00:00:00 2001 From: sebg-mio42 Date: Wed, 17 Jul 2024 12:11:49 +0200 Subject: [PATCH 2/3] fixed errors in usage of versionId --- src/export/InstanceExporter.ts | 11 ++++++++--- src/fhirtypes/InstanceDefinition.ts | 4 ++-- src/import/FSHImporter.ts | 24 ++++++++++++++++++------ src/utils/Processing.ts | 4 +++- 4 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/export/InstanceExporter.ts b/src/export/InstanceExporter.ts index 4020254f2..5c61aae41 100644 --- a/src/export/InstanceExporter.ts +++ b/src/export/InstanceExporter.ts @@ -746,7 +746,13 @@ export class InstanceExporter implements Fishable { } exportInstance(fshDefinition: Instance): InstanceDefinition { - if (this.pkg.instances.some(i => i._instanceMeta.name === fshDefinition.name)) { + if ( + this.pkg.instances.some( + i => + i._instanceMeta.name === fshDefinition.name && + i._instanceMeta.versionId === fshDefinition.versionId + ) + ) { return; } @@ -955,8 +961,7 @@ export class InstanceExporter implements Fishable { instanceDef.resourceType === instance.resourceType && (instanceDef.id ?? instanceDef._instanceMeta.name) === (instance.id ?? instance._instanceMeta.name) && - (instanceDef.versionId) === - (instance.versionId) && + instanceDef._instanceMeta.versionId === instance._instanceMeta.versionId && instanceDef !== instance ) ) { diff --git a/src/fhirtypes/InstanceDefinition.ts b/src/fhirtypes/InstanceDefinition.ts index 3dc124626..48ee97ba4 100644 --- a/src/fhirtypes/InstanceDefinition.ts +++ b/src/fhirtypes/InstanceDefinition.ts @@ -26,8 +26,8 @@ export class InstanceDefinition { getFileName(): string { // Logical instances should use Binary type. See: https://fshschool.org/docs/sushi/tips/#instances-of-logical-models const type = this._instanceMeta.sdKind === 'logical' ? 'Binary' : this.resourceType; - let versionString = (this._instanceMeta.versionId) ? `_v${this._instanceMeta.versionId}` : `` - let filename = `${type}-${this.id ?? this._instanceMeta.name}${versionString}.json`; + const versionString = this._instanceMeta.versionId ? `_v${this._instanceMeta.versionId}` : ''; + const filename = `${type}-${this.id ?? this._instanceMeta.name}${versionString}.json`; return sanitize(filename, { replacement: '-' }); diff --git a/src/import/FSHImporter.ts b/src/import/FSHImporter.ts index 296f08d9c..c0d904aaa 100644 --- a/src/import/FSHImporter.ts +++ b/src/import/FSHImporter.ts @@ -451,11 +451,23 @@ export class FSHImporter extends FSHVisitor { { try { this.parseInstance(instance, location, ctx.instanceMetadata(), ctx.instanceRule()); - if (this.docs.some(doc => (doc.instances.has(instance.name) && (Array.from(doc.instances.values()).some(entity => entity.name === instance.name && entity.versionId === instance.versionId))))) { - logger.error(`Skipping Instance: an Instance named ${instance.name} with versionId ${instance.versionId} already exists.`, { - file: this.currentFile, - location - }); + if ( + this.docs.some( + doc => + doc.instances.has(instance.name) && + Array.from(doc.instances.values()).some( + entity => entity.name === instance.name && entity.versionId === instance.versionId + ) + ) + ) { + const versionString = instance.versionId ? `with versionId ${instance.versionId} ` : ''; + logger.error( + `Skipping Instance: an Instance named ${instance.name} ${versionString}already exists.`, + { + file: this.currentFile, + location + } + ); } else { this.currentDoc.instances.set(instance.name, instance); } @@ -520,7 +532,7 @@ export class FSHImporter extends FSHVisitor { const rule = this.visitInstanceRule(instanceRule); if (rule) { instance.rules.push(rule); - if (rule instanceof AssignmentRule && rule.path === `meta.versionId`) { + if (rule instanceof AssignmentRule && rule.path === 'meta.versionId') { instance.versionId = rule.value.toString(); } } diff --git a/src/utils/Processing.ts b/src/utils/Processing.ts index 353dd38ab..450ed2dab 100644 --- a/src/utils/Processing.ts +++ b/src/utils/Processing.ts @@ -550,6 +550,7 @@ export function writeFHIRResources( toJSON: (snapshot: boolean) => any; url?: string; id?: string; + versionId?: string; resourceType?: string; }[] ) => { @@ -560,7 +561,8 @@ export function writeFHIRResources( predef => predef.url === resource.url && predef.resourceType === resource.resourceType && - predef.id === resource.id + predef.id === resource.id && + predef.versionId === resource.versionId ) ) { checkNullValuesOnArray(resource); From 6a5f689c367f01b2171afc09b38d025ee0503680 Mon Sep 17 00:00:00 2001 From: sebg-mio42 Date: Wed, 17 Jul 2024 12:12:05 +0200 Subject: [PATCH 3/3] test case --- test/import/FSHImporter.Instance.test.ts | 40 ++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/import/FSHImporter.Instance.test.ts b/test/import/FSHImporter.Instance.test.ts index d830bfed0..91e0c3f3b 100644 --- a/test/import/FSHImporter.Instance.test.ts +++ b/test/import/FSHImporter.Instance.test.ts @@ -410,6 +410,46 @@ describe('FSHImporter', () => { ); expect(loggerSpy.getLastMessage('error')).toMatch(/File: File2\.fsh.*Line: 2 - 4\D*/s); }); + + it('should not produce an error when encountering an instance with a name used by another instance in another file but has a unique versionId', () => { + const input1 = ` + Instance: SomeInstance + InstanceOf: Patient + Title: "Instance" + * meta + * meta.versionId = "1" + `; + + const input2 = ` + Instance: SomeInstance + InstanceOf: Patient + Title: "Instance" + * meta + * meta.versionId = "2" + `; + + const input3 = ` + Instance: SomeInstance + InstanceOf: Patient + Title: "Instance" + `; + + const result = importText([ + new RawFSH(input1, 'File1.fsh'), + new RawFSH(input2, 'File2.fsh'), + new RawFSH(input3, 'File2.fsh') + ]); + expect(result.reduce((sum, d2) => sum + d2.instances.size, 0)).toBe(3); + const instance1 = result[0].instances.get('SomeInstance'); + const instance2 = result[1].instances.get('SomeInstance'); + const instance3 = result[2].instances.get('SomeInstance'); + expect(instance1.title).toBe('Instance'); + expect(instance1.versionId).toBe('1'); + expect(instance1.title).toBe('Instance'); + expect(instance2.versionId).toBe('2'); + expect(instance3.title).toBe('Instance'); + expect(instance3.versionId).toBe(undefined); + }); }); }); });