Skip to content

Commit

Permalink
Add fishForMetadatas function to support smarter resolution in Canoni…
Browse files Browse the repository at this point in the history
…cal assignments

Now that SUSHI loads additional resource types from dependencies, there is a greater chance of naming collisions when resolving names. In one case, this caused a canoonical assignment that used to work to stop working (i.e. it used to resolve to a PlanDefinition from the tank, which was good; but then it started resolving to a Library from predefined resources, which didn't match the needed Canonical type).

Now we can search for all metadata results from a given search term and, if necessary, choose the one that is a best match during canonical assignment. This may be relevant to other parts of the code as well, but for now we only do it for Canonical assignment in instances.
  • Loading branch information
cmoesel committed Dec 21, 2024
1 parent b5cff59 commit 185f785
Show file tree
Hide file tree
Showing 16 changed files with 2,935 additions and 261 deletions.
5 changes: 5 additions & 0 deletions src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,11 @@ export class InstanceExporter implements Fishable {
return this.fisher.fishForMetadata(item, Type.Instance);
}

fishForMetadatas(item: string): Metadata[] {
// If it's in the tank, it can get the metadata from there (no need to export like in fishForFHIR)
return this.fisher.fishForMetadatas(item, Type.Instance);
}

applyInsertRules(): void {
const instances = this.tank.getAllInstances();
for (const instance of instances) {
Expand Down
78 changes: 62 additions & 16 deletions src/export/Package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ export class Package implements Fishable {
item: string,
...types: Type[]
): StructureDefinition | ValueSet | CodeSystem | InstanceDefinition | undefined {
return this.internalFish(item, types, true)[0];
}

fishAll(
item: string,
...types: Type[]
): (StructureDefinition | ValueSet | CodeSystem | InstanceDefinition)[] {
return this.internalFish(item, types, false);
}

private internalFish(
item: string,
types: Type[],
stopOnFirstMatch: boolean
): (StructureDefinition | ValueSet | CodeSystem | InstanceDefinition)[] {
const results: (StructureDefinition | ValueSet | CodeSystem | InstanceDefinition)[] = [];

// No types passed in means to search ALL supported types
if (types.length === 0) {
types = [
Expand Down Expand Up @@ -161,9 +178,13 @@ export class Package implements Fishable {
break;
}
if (def) {
return def;
if (stopOnFirstMatch) {
return [def];
}
results.push(def);
}
}
return results;
}

fishForFHIR(item: string, ...types: Type[]): any | undefined {
Expand All @@ -172,6 +193,33 @@ export class Package implements Fishable {

fishForMetadata(item: string, ...types: Type[]): Metadata | undefined {
const result = this.fish(item, ...types);
if (result) {
return this.extractMetadata(result);
} else if (
// If nothing is returned, perhaps the Package itself is being referenced
item != null &&
(item === this.config.packageId || item === this.config.name || item === this.config.id)
) {
return this.extractImplementationGuideMetadataFromConfig();
}
}

fishForMetadatas(item: string, ...types: Type[]): Metadata[] {
const results = this.fishAll(item, ...types);
const metadatas = results.map(result => this.extractMetadata(result));
// Also handle cases where the Package itself is being referenced
if (
item != null &&
(item === this.config.packageId || item === this.config.name || item === this.config.id)
) {
metadatas.push(this.extractImplementationGuideMetadataFromConfig());
}
return metadatas;
}

private extractMetadata(
result: StructureDefinition | ValueSet | CodeSystem | InstanceDefinition
): Metadata {
if (result) {
const metadata: Metadata = {
id: result.id,
Expand Down Expand Up @@ -217,24 +265,22 @@ export class Package implements Fishable {
}
}
return metadata;
} else if (
// If nothing is returned, perhaps the Package itself is being referenced
item != null &&
(item === this.config.packageId || item === this.config.name || item === this.config.id)
) {
const metadata: Metadata = {
id: this.config.packageId || this.config.id,
name: this.config.name,
url:
this.config.url ||
`${this.config.canonical}/ImplementationGuide/${this.config.packageId || this.config.id}`,
version: this.config.version,
resourceType: 'ImplementationGuide'
};
return metadata;
}
}

private extractImplementationGuideMetadataFromConfig(): Metadata {
const metadata: Metadata = {
id: this.config.packageId || this.config.id,
name: this.config.name,
url:
this.config.url ||
`${this.config.canonical}/ImplementationGuide/${this.config.packageId || this.config.id}`,
version: this.config.version,
resourceType: 'ImplementationGuide'
};
return metadata;
}

// Resets all the definition arrays to be zero-length. This is useful for re-using a package during testing.
clearAllDefinitions() {
this.profiles.length = 0;
Expand Down
5 changes: 5 additions & 0 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1458,6 +1458,11 @@ export class StructureDefinitionExporter implements Fishable {
return this.fisher.fishForMetadata(item, ...types);
}

fishForMetadatas(item: string, ...types: Type[]): Metadata[] {
// If it's in the tank, it can get the metadata from there (no need to export like in fishForFHIR)
return this.fisher.fishForMetadatas(item, ...types);
}

applyInsertRules(): void {
const invariants = this.tank.getAllInvariants();
for (const inv of invariants) {
Expand Down
27 changes: 27 additions & 0 deletions src/fhirdefs/FHIRDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,15 @@ export class FHIRDefinitions extends BasePackageLoader implements Fishable {
return convertInfoToMetadata(info);
}

fishForPredefinedResourceMetadatas(item: string, ...types: Type[]): Metadata[] {
const infos = this.findResourceInfos(item, {
type: normalizeTypes(types),
scope: PREDEFINED_PACKAGE_NAME,
sort: DEFAULT_SORT
});
return infos.map(info => convertInfoToMetadata(info));
}

fishForFHIR(item: string, ...types: Type[]): any | undefined {
const def = this.findResourceJSON(item, {
type: normalizeTypes(types),
Expand Down Expand Up @@ -200,6 +209,24 @@ export class FHIRDefinitions extends BasePackageLoader implements Fishable {
return materializeImpliedExtensionMetadata(item, this);
}
}

fishForMetadatas(item: string, ...types: Type[]): Metadata[] {
const infos = this.findResourceInfos(item, {
type: normalizeTypes(types),
sort: DEFAULT_SORT
});
if (infos.length) {
return infos.map(info => convertInfoToMetadata(info));
}
// If it's an "implied extension", try to materialize it. See:http://hl7.org/fhir/versions.html#extensions
if (IMPLIED_EXTENSION_REGEX.test(item) && types.some(t => t === Type.Extension)) {
const info = materializeImpliedExtensionMetadata(item, this);
if (info) {
return [info];
}
}
return [];
}
}

export async function createFHIRDefinitions(
Expand Down
27 changes: 25 additions & 2 deletions src/fhirtypes/ElementDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1944,7 +1944,7 @@ export class ElementDefinition {
case 'Canonical':
value = value as FshCanonical;
// Get the canonical url of the entity
const canonicalDefinition = fisher.fishForMetadata(
let canonicalDefinition = fisher.fishForMetadata(
value.entityName,
Type.Resource,
Type.Logical,
Expand All @@ -1963,7 +1963,30 @@ export class ElementDefinition {
fisher
)
) {
throw new InvalidTypeError(`Canonical(${canonicalDefinition.resourceType})`, this.type);
// The first fishing result didn't work. Now check all results in case the fishing matches multiple results.
const allMatchingMetadatas = fisher.fishForMetadatas(
value.entityName,
Type.Resource,
Type.Logical,
Type.Type,
Type.Profile,
Type.Extension,
Type.ValueSet,
Type.CodeSystem,
Type.Instance
);
const otherCanonicalDefinition = allMatchingMetadatas.find(md =>
this.typeSatisfiesTargetProfile(
md?.resourceType,
(value as FshCanonical).sourceInfo,
fisher
)
);
if (otherCanonicalDefinition) {
canonicalDefinition = otherCanonicalDefinition;
} else {
throw new InvalidTypeError(`Canonical(${canonicalDefinition.resourceType})`, this.type);
}
}
if (canonicalDefinition?.url) {
canonicalUrl = canonicalDefinition.url;
Expand Down
Loading

0 comments on commit 185f785

Please sign in to comment.