Skip to content

Commit

Permalink
Support fishing all resource types from dependency packages and input…
Browse files Browse the repository at this point in the history
…/* (#1548)

* Treat Instance type as wildcard when fishing FHIR dependencies

If the FHIRDefinitions fish functions are called specifying Type.Instance as a requested type, treat it like no types were passed in at all (i.e. wildcard type search) in order to search all types.

* Add fishForMetadatas function to support smarter resolution in Canonical 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 authored Dec 30, 2024
1 parent 97e5efd commit 09b2585
Show file tree
Hide file tree
Showing 16 changed files with 3,072 additions and 295 deletions.
5 changes: 5 additions & 0 deletions src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,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 @@ -1459,6 +1459,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
40 changes: 36 additions & 4 deletions src/fhirdefs/FHIRDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,24 +158,33 @@ export class FHIRDefinitions extends BasePackageLoader implements Fishable {

fishForPredefinedResource(item: string, ...types: Type[]): any | undefined {
return this.findResourceJSON(item, {
type: types,
type: normalizeTypes(types),
scope: PREDEFINED_PACKAGE_NAME,
sort: DEFAULT_SORT
});
}

fishForPredefinedResourceMetadata(item: string, ...types: Type[]): Metadata | undefined {
const info = this.findResourceInfo(item, {
type: types,
type: normalizeTypes(types),
scope: PREDEFINED_PACKAGE_NAME,
sort: DEFAULT_SORT
});
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: types,
type: normalizeTypes(types),
sort: DEFAULT_SORT
});
if (def) {
Expand All @@ -189,7 +198,7 @@ export class FHIRDefinitions extends BasePackageLoader implements Fishable {

fishForMetadata(item: string, ...types: Type[]): Metadata | undefined {
const info = this.findResourceInfo(item, {
type: types,
type: normalizeTypes(types),
sort: DEFAULT_SORT
});
if (info) {
Expand All @@ -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 All @@ -223,6 +250,11 @@ export async function createFHIRDefinitions(
return fhirDefinitions;
}

function normalizeTypes(types?: Type[]): undefined | string[] {
// Instance is like a wildcard, allowing anything -- so treat it like no types are passed in at all
return types?.some(t => t === Type.Instance) ? undefined : types;
}

function convertInfoToMetadata(info: ResourceInfo): Metadata {
if (info) {
// Note: explicitly return undefined instead of null to keep tests happy
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 09b2585

Please sign in to comment.