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

Support fully defining resources using caret rules #1530

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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
Loading