Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find entities by id, even when their id is set by a rule #1198

Merged
merged 6 commits into from
Jan 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/export/CodeSystemExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
);
Expand Down
6 changes: 6 additions & 0 deletions src/export/FHIRExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
13 changes: 9 additions & 4 deletions src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
jafeltra marked this conversation as resolved.
Show resolved Hide resolved
if (fshDefinition.title == '') {
logger.warn(`Instance ${fshDefinition.name} has a title field that should not be empty.`);
}
Expand Down
9 changes: 7 additions & 2 deletions src/export/MappingExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand Down
10 changes: 7 additions & 3 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 7 additions & 2 deletions src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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[]
Expand Down
7 changes: 1 addition & 6 deletions src/fhirtypes/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,9 @@ export function replaceReferences<T extends AssignmentRule | CaretValueRule>(
);
// 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;
Comment on lines -432 to -436
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh... right! We don't even need this anymore. How exciting!

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) {
Expand Down
13 changes: 8 additions & 5 deletions src/fhirtypes/mixins.ts
Original file line number Diff line number Diff line change
@@ -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}$/;

Expand Down Expand Up @@ -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);
Comment on lines +66 to +67
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

} 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)) {
Expand Down
16 changes: 14 additions & 2 deletions src/fshtypes/FshCodeSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ 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';

/**
* For more information about a CodeSystem in FHIR,
* @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)[];
Expand All @@ -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);
Expand Down
16 changes: 14 additions & 2 deletions src/fshtypes/FshStructure.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}`);
Expand Down
16 changes: 14 additions & 2 deletions src/fshtypes/FshValueSet.ts
Original file line number Diff line number Diff line change
@@ -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)[];
Expand All @@ -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}`);
Expand Down
16 changes: 14 additions & 2 deletions src/fshtypes/Instance.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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}`);
Expand Down
21 changes: 20 additions & 1 deletion src/fshtypes/common.ts
Original file line number Diff line number Diff line change
@@ -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[] = [];
Expand Down Expand Up @@ -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;
}
4 changes: 1 addition & 3 deletions src/import/FSHTank.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
Expand Down
Loading