Skip to content

Commit

Permalink
Fish With Version (#1275)
Browse files Browse the repository at this point in the history
* Support fishing with a version

- Add support to FSHTank to fish with a version
- Add helper function to determine if a version is set on a FSH definition,
  which can be used by the FSHTank fish() function
- Add helper functions for fishing for best version - a version that matches
  or any version if match is not found
- Replace most places where fishing was done without any version that was specified

Note: this will need an updated FPL in order to have FHIRDefinitions fish correctly with a version

* Add tests and minor tweeks around fishing with versions in FSH Tank and for FishingUtils

* Only append version if not already provided in URL and add test to check fishing for VS URL with version throws mismatch error

* Fish with version in Package

* Add back version if fished up a value set

* Use FPL v0.3.0

* Simplify check for matching version on instances

* Simplify places where version needs to be appended to a fished up url

* Invariants, Mappings, RuleSets won't have a version so don't need to check on rules

* Simplify warning message when incorrect version is fished up and simplify conditional statements

* Assigning a code with an invalid code system should only warn when assigning to a primitive code/string/uri

* Log source info for mismatched version when fishing for best version

Also updates system lookup code for FSHCodes to avoid double-warning on mismatched version.

* Remove unnecessary import

---------

Co-authored-by: Chris Moesel <[email protected]>
  • Loading branch information
jafeltra and cmoesel authored May 19, 2023
1 parent 2b9020c commit 2c2e4c4
Show file tree
Hide file tree
Showing 18 changed files with 1,243 additions and 153 deletions.
14 changes: 7 additions & 7 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@
"chalk": "^3.0.0",
"commander": "^8.2.0",
"fhir": "^4.9.0",
"fhir-package-loader": "^0.2.0",
"fhir-package-loader": "^0.3.0",
"fs-extra": "^8.1.0",
"html-minifier-terser": "5.1.1",
"https-proxy-agent": "^5.0.0",
Expand Down
64 changes: 51 additions & 13 deletions src/export/Package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,85 +30,123 @@ export class Package implements Fishable {
];
}

// version needs to be checked separately from the base
const [base, ...versionParts] = item?.split('|') ?? ['', ''];
const version = versionParts.join('|') || null;

for (const type of types) {
let def;
switch (type) {
case Type.Profile:
def = this.profiles.find(p => p.id === item || p.name === item || p.url === item);
def = this.profiles.find(
p =>
(p.id === base || p.name === base || p.url === base) &&
(version == null || p.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'StructureDefinition' &&
i.derivation === 'constraint' &&
i.type !== 'Extension' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.Extension:
def = this.extensions.find(e => e.id === item || e.name === item || e.url === item);
def = this.extensions.find(
e =>
(e.id === base || e.name === base || e.url === base) &&
(version == null || e.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'StructureDefinition' &&
i.derivation === 'constraint' &&
i.type === 'Extension' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.Logical:
def = this.logicals.find(e => e.id === item || e.name === item || e.url === item);
def = this.logicals.find(
l =>
(l.id === base || l.name === base || l.url === base) &&
(version == null || l.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'StructureDefinition' &&
i.derivation === 'specialization' &&
i.kind === 'logical' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.Resource:
def = this.resources.find(e => e.id === item || e.name === item || e.url === item);
def = this.resources.find(
r =>
(r.id === base || r.name === base || r.url === base) &&
(version == null || r.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'StructureDefinition' &&
i.derivation === 'specialization' &&
i.kind === 'resource' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.ValueSet:
def = this.valueSets.find(vs => vs.id === item || vs.name === item || vs.url === item);
def = this.valueSets.find(
vs =>
(vs.id === base || vs.name === base || vs.url === base) &&
(version == null || vs.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'ValueSet' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.CodeSystem:
def = this.codeSystems.find(cs => cs.id === item || cs.name === item || cs.url === item);
def = this.codeSystems.find(
cs =>
(cs.id === base || cs.name === base || cs.url === base) &&
(version == null || cs.version === version)
);
if (!def) {
def = this.instances.find(
i =>
i._instanceMeta.usage === 'Definition' &&
i.resourceType === 'CodeSystem' &&
(i.id === item || i._instanceMeta.name === item || i.url === item)
(i.id === base || i._instanceMeta.name === base || i.url === base) &&
(version == null || i.version === version)
);
}
break;
case Type.Instance:
def = this.instances.find(i => i.id === item || i._instanceMeta.name === item);
def = this.instances.find(
i =>
(i.id === base || i._instanceMeta.name === base) &&
(version == null || i.version === version)
);
break;
case Type.Type: // Package doesn't currently support types
default:
Expand Down
30 changes: 15 additions & 15 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,15 @@ import {
ObeysRule,
OnlyRule
} from '../fshtypes/rules';
import { logger, Type, Fishable, Metadata, MasterFisher, resolveSoftIndexing } from '../utils';
import {
logger,
Type,
Fishable,
fishForFHIRBestVersion,
Metadata,
MasterFisher,
resolveSoftIndexing
} from '../utils';
import {
applyInsertRules,
cleanResource,
Expand Down Expand Up @@ -603,7 +611,8 @@ export class StructureDefinitionExporter implements Fishable {
const target = structDef.getReferenceOrCanonicalName(rule.path, element);
element.constrainType(rule, this, target);
} else if (rule instanceof BindingRule) {
const vsURI = this.fishForMetadata(rule.valueSet, Type.ValueSet)?.url ?? rule.valueSet;
const vsMetadata = this.fishForMetadata(rule.valueSet, Type.ValueSet);
const vsURI = rule.valueSet.replace(/^([^|]+)/, vsMetadata?.url ?? '$1');
const csURI = this.fishForMetadata(rule.valueSet, Type.CodeSystem)?.url;
if (csURI && !isUri(vsURI)) {
throw new MismatchedBindingTypeError(rule.valueSet, rule.path, 'ValueSet');
Expand Down Expand Up @@ -780,27 +789,18 @@ export class StructureDefinitionExporter implements Fishable {
}
rule.items.forEach(item => {
if (item.type) {
// there might be a |version appended to the type, so don't include it while fishing
const [typeWithoutVersion, version] = item.type.split('|', 2);
const extension = this.fishForFHIR(typeWithoutVersion, Type.Extension);
// there might be a |version appended to the type, so try to use the version but fall back
// to any version if necessary
const extension = fishForFHIRBestVersion(this, item.type, rule.sourceInfo, Type.Extension);
if (extension == null) {
logger.error(
`Cannot create ${item.name} extension; unable to locate extension definition for: ${item.type}.`,
rule.sourceInfo
);
return;
}
if (version != null && extension.version != null && version != extension.version) {
logger.warn(
`The ${typeWithoutVersion} extension was specified with version ${version}, but SUSHI found version ${extension.version}`,
rule.sourceInfo
);
}
try {
let profileUrl = extension.url;
if (version) {
profileUrl += `|${version}`;
}
const profileUrl = item.type.replace(/^[^|]+/, extension.url);
const slice = element.addSlice(item.name);
if (!slice.type[0].profile) {
slice.type[0].profile = [];
Expand Down
8 changes: 4 additions & 4 deletions src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ export class ValueSetExporter {
const composeElement: ValueSetComposeIncludeOrExclude = {};
if (component.from.system) {
const systemParts = component.from.system.split('|');
const foundSystem = (
this.fisher.fishForMetadata(systemParts[0], Type.CodeSystem)?.url ??
component.from.system
).split('|');
const csMetadata = this.fisher.fishForMetadata(component.from.system, Type.CodeSystem);
const foundSystem = component.from.system
.replace(/^([^|]+)/, csMetadata?.url ?? '$1')
.split('|');
composeElement.system = foundSystem[0];
// if the rule specified a version, use that version.
composeElement.version = systemParts.slice(1).join('|') || undefined;
Expand Down
Loading

0 comments on commit 2c2e4c4

Please sign in to comment.