Skip to content

Commit

Permalink
Support fully defining resources using caret rules
Browse files Browse the repository at this point in the history
A contained resource can be defined within another resource one rule at
a time using caret rules. By setting the resourceType of the contained
resource, SUSHI will be able to correctly validate other rules that
build up the contained resource. Most of the logic for this already
existed, but some additional code was needed in order to collect
information about inline resource types. Additionally, if an instance is
assigned with a caret rule, further caret rules may be used to modify
the contained resource. This gets a little complicated when exporting a
StructureDefinition.

A ValueSet may refer to a CodeSystem that is defined with caret rules on
the ValueSet. The resulting compose element will look the same as if the
inline CodeSystem were defined separately.

Log a warning when an example instance is assigned within a conformance
resource.

Update object definitions for inline instance tests so that the
Instances are, in fact, inline.
  • Loading branch information
mint-thompson committed Oct 25, 2024
1 parent faaf076 commit 1d43112
Show file tree
Hide file tree
Showing 12 changed files with 1,782 additions and 88 deletions.
67 changes: 59 additions & 8 deletions src/export/CodeSystemExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
setImpliedPropertiesOnInstance,
validateInstanceFromRawValue,
isExtension,
replaceReferences
replaceReferences,
splitOnPathPeriods
} from '../fhirtypes/common';
import { FshCodeSystem } from '../fshtypes';
import { CaretValueRule, ConceptRule } from '../fshtypes/rules';
Expand Down Expand Up @@ -130,6 +131,18 @@ export class CodeSystemExporter {
// so, we only need to track rules that involve an extension.
const ruleMap: Map<string, { pathParts: PathPart[] }> = new Map();
const codeSystemSD = codeSystem.getOwnStructureDefinition(this.fisher);
const inlineResourcePaths: { path: string; caretPath: string; instanceOf: string }[] = [];
// first, collect the information we can from rules that set a resourceType
// if instances are directly assigned, we'll get information from them upon validation
successfulRules.forEach((r: CaretValueRule) => {
if (r.caretPath.endsWith('.resourceType') && typeof r.value === 'string' && !r.isInstance) {
inlineResourcePaths.push({
path: r.path,
caretPath: splitOnPathPeriods(r.caretPath).slice(0, -1).join('.'),
instanceOf: r.value
});
}
});
const successfulRulesWithInstances = successfulRules
.map(rule => {
if (rule.isInstance) {
Expand All @@ -142,20 +155,43 @@ export class CodeSystemExporter {
);
return null;
}
if (instance._instanceMeta.usage === 'Example') {
logger.warn(
`Contained instance "${rule.value}" is an example and probably should not be included in a conformance resource.`,
rule.sourceInfo
);
}
rule.value = instance;
inlineResourcePaths.push({
path: rule.path,
caretPath: rule.caretPath,
instanceOf: instance.resourceType
});
}
const matchingInlineResourcePaths = inlineResourcePaths.filter(i => {
return (
rule.path == i.path &&
rule.caretPath.startsWith(`${i.caretPath}.`) &&
rule.caretPath !== `${i.caretPath}.resourceType`
);
});
const inlineResourceTypes: string[] = [];
matchingInlineResourcePaths.forEach(match => {
inlineResourceTypes[splitOnPathPeriods(match.caretPath).length - 1] = match.instanceOf;
});
const path = rule.path.length > 1 ? `${rule.path}.${rule.caretPath}` : rule.caretPath;
try {
const replacedRule = replaceReferences(rule, this.tank, this.fisher);
const { pathParts } = codeSystemSD.validateValueAtPath(
path,
replacedRule.value,
this.fisher
this.fisher,
inlineResourceTypes
);
if (pathParts.some(part => isExtension(part.base))) {
ruleMap.set(assembleFSHPath(pathParts).replace(/\[0+\]/g, ''), { pathParts });
}
return replacedRule;
return { rule: replacedRule, inlineResourceTypes };
} catch (originalErr) {
// if an Instance has an id that looks like a number, bigint, or boolean,
// we may have tried to assign that value instead of an Instance.
Expand All @@ -171,13 +207,27 @@ export class CodeSystemExporter {
rule,
instanceExporter,
this.fisher,
originalErr
originalErr,
inlineResourceTypes
);
if (instance?._instanceMeta.usage === 'Example') {
logger.warn(
`Contained instance "${rule.rawValue}" is an example and probably should not be included in a conformance resource.`,
rule.sourceInfo
);
}
rule.value = instance;
if (instance != null) {
inlineResourcePaths.push({
path: rule.path,
caretPath: rule.caretPath,
instanceOf: instance.resourceType
});
}
if (pathParts.some(part => isExtension(part.base))) {
ruleMap.set(assembleFSHPath(pathParts).replace(/\[0+\]/g, ''), { pathParts });
}
return rule;
return { rule, inlineResourceTypes };
} else {
logger.error(originalErr.message, rule.sourceInfo);
if (originalErr.stack) {
Expand All @@ -193,18 +243,19 @@ export class CodeSystemExporter {
codeSystem,
codeSystemSD,
[...ruleMap.keys()],
[],
inlineResourcePaths.map(i => i.caretPath),
this.fisher,
knownSlices
);

for (const rule of successfulRulesWithInstances) {
for (const { rule, inlineResourceTypes } of successfulRulesWithInstances) {
try {
setPropertyOnDefinitionInstance(
codeSystem,
rule.path.length > 1 ? `${rule.path}.${rule.caretPath}` : rule.caretPath,
rule.value,
this.fisher
this.fisher,
inlineResourceTypes
);
} catch (err) {
logger.error(err.message, rule.sourceInfo);
Expand Down
18 changes: 18 additions & 0 deletions src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,15 @@ export class InstanceExporter implements Fishable {
if (r instanceof AssignmentRule && r.isInstance) {
const instance: InstanceDefinition = this.fishForFHIR(r.value as string);
if (instance != null) {
if (
instance._instanceMeta.usage === 'Example' &&
instanceDef._instanceMeta.usage === 'Definition'
) {
logger.warn(
`Contained instance "${r.value}" is an example and probably should not be included in a conformance resource.`,
r.sourceInfo
);
}
r.value = instance;
return true;
} else {
Expand Down Expand Up @@ -254,6 +263,15 @@ export class InstanceExporter implements Fishable {
}
} else {
try {
if (
instanceToAssign._instanceMeta.usage === 'Example' &&
instanceDef._instanceMeta.usage === 'Definition'
) {
logger.warn(
`Contained instance "${rule.rawValue}" is an example and probably should not be included in a conformance resource.`,
rule.sourceInfo
);
}
doRuleValidation(instanceToAssign);
} catch (instanceErr) {
if (instanceErr instanceof MismatchedTypeError) {
Expand Down
Loading

0 comments on commit 1d43112

Please sign in to comment.