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

Assign date or dateTime that contains only year #1511

Closed
wants to merge 2 commits into from
Closed
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
39 changes: 37 additions & 2 deletions src/export/CodeSystemExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { logger } from '../utils/FSHLogger';
import { MasterFisher, assembleFSHPath, resolveSoftIndexing } from '../utils';
import { InstanceExporter, Package } from '.';
import { CannotResolvePathError, MismatchedTypeError } from '../errors';
import { isEqual } from 'lodash';
import { cloneDeep, isEqual } from 'lodash';

export class CodeSystemExporter {
constructor(
Expand Down Expand Up @@ -157,11 +157,46 @@ export class CodeSystemExporter {
}
return replacedRule;
} catch (originalErr) {
// if the value is a number, it may have been a four-digit number
// that we tried to assign to a date or dateTime.
// a four-digit number could be a valid year, so see if it can be assigned.
if (
originalErr instanceof MismatchedTypeError &&
['date', 'dateTime'].includes(originalErr.elementType) &&
['number', 'bigint'].includes(typeof rule.value)
) {
try {
const retryRule = cloneDeep(rule);
const { pathParts } = codeSystemSD.validateValueAtPath(
path,
retryRule.rawValue,
this.fisher
);
if (pathParts.some(part => isExtension(part.base))) {
ruleMap.set(assembleFSHPath(pathParts).replace(/\[0+\]/g, ''), { pathParts });
}
retryRule.value = retryRule.rawValue;
return retryRule;
} catch (retryErr) {
if (retryErr instanceof MismatchedTypeError) {
logger.error(originalErr.message, rule.sourceInfo);
if (originalErr.stack) {
logger.debug(originalErr.stack);
}
} else {
logger.error(retryErr.message, rule.sourceInfo);
if (retryErr.stack) {
logger.debug(retryErr.stack);
}
}
return null;
}
}
// 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.
// try to fish up an Instance with the rule's raw value.
// if we find one, try assigning that instead.
if (
else if (
originalErr instanceof MismatchedTypeError &&
['number', 'bigint', 'boolean'].includes(typeof rule.value)
) {
Expand Down
27 changes: 26 additions & 1 deletion src/export/InstanceExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,36 @@ export class InstanceExporter implements Fishable {
}
doRuleValidation(rule instanceof AssignmentRule ? rule.value : null);
} catch (originalErr) {
// if the value is a number, it may have been a four-digit number
// that we tried to assign to a date or dateTime.
// a four-digit number could be a valid year, so see if it can be assigned.
if (
rule instanceof AssignmentRule &&
originalErr instanceof MismatchedTypeError &&
['date', 'dateTime'].includes(originalErr.elementType) &&
['number', 'bigint'].includes(typeof rule.value)
) {
try {
doRuleValidation(rule.rawValue);
} catch (retryErr) {
if (retryErr instanceof MismatchedTypeError) {
logger.error(originalErr.message, rule.sourceInfo);
if (originalErr.stack) {
logger.debug(originalErr.stack);
}
} else {
logger.error(retryErr.message, rule.sourceInfo);
if (retryErr.stack) {
logger.debug(retryErr.stack);
}
}
}
}
// if an Instance has an id that looks like a number, bigint, or boolean,
// we may have tried to validate with that value instead of an Instance.
// try to fish up an Instance with the rule's raw value.
// if we find one, try validating with that instead.
if (
else if (
rule instanceof AssignmentRule &&
originalErr instanceof MismatchedTypeError &&
['number', 'bigint', 'boolean'].includes(typeof rule.value)
Expand Down
60 changes: 58 additions & 2 deletions src/export/StructureDefinitionExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -801,7 +801,15 @@ export class StructureDefinitionExporter implements Fishable {
}
const replacedRule = replaceReferences(rule, this.tank, this);
try {
element.assignValue(replacedRule.value, replacedRule.exactly, this);
// since we have the element already, we can check for the "date parsed as number" special case now instead of after an exception
if (
['date', 'dateTime'].includes(element.type[0].code) &&
['number', 'bigint'].includes(typeof replacedRule.value)
) {
element.assignValue(replacedRule.rawValue, replacedRule.exactly, this);
} else {
element.assignValue(replacedRule.value, replacedRule.exactly, this);
}
} 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 Down Expand Up @@ -911,7 +919,34 @@ export class StructureDefinitionExporter implements Fishable {
} else if (rule instanceof CaretValueRule) {
const replacedRule = replaceReferences(rule, this.tank, this);
if (replacedRule.path !== '') {
element.setInstancePropertyByPath(replacedRule.caretPath, replacedRule.value, this);
try {
element.setInstancePropertyByPath(replacedRule.caretPath, replacedRule.value, this);
} catch (originalErr) {
// if the value is a number, it may have been a four-digit number
// that we tried to assign to a date or dateTime.
// a four-digit number could be a valid year, so see if it can be assigned.
if (
originalErr instanceof MismatchedTypeError &&
['date', 'dateTime'].includes(originalErr.elementType) &&
['number', 'bigint'].includes(typeof replacedRule.value)
) {
try {
element.setInstancePropertyByPath(
replacedRule.caretPath,
replacedRule.rawValue,
this
);
} catch (retryErr) {
if (retryErr instanceof MismatchedTypeError) {
throw originalErr;
} else {
throw retryErr;
}
}
} else {
throw originalErr;
}
}
} else {
if (replacedRule.isInstance) {
if (this.deferredCaretRules.has(structDef)) {
Expand All @@ -927,7 +962,28 @@ export class StructureDefinitionExporter implements Fishable {
this
);
} catch (originalErr) {
// if the value is a number, it may have been a four-digit number
// that we tried to assign to a date or dateTime.
// a four-digit number could be a valid year, so see if it can be assigned.
if (
originalErr instanceof MismatchedTypeError &&
['date', 'dateTime'].includes(originalErr.elementType) &&
['number', 'bigint'].includes(typeof replacedRule.value)
) {
try {
structDef.setInstancePropertyByPath(
replacedRule.caretPath,
replacedRule.rawValue,
this
);
} catch (retryErr) {
if (retryErr instanceof MismatchedTypeError) {
throw originalErr;
} else {
throw retryErr;
}
}
} else if (
originalErr instanceof MismatchedTypeError &&
['number', 'bigint', 'boolean'].includes(typeof rule.value)
) {
Expand Down
35 changes: 34 additions & 1 deletion src/export/ValueSetExporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
setImpliedPropertiesOnInstance
} from '../fhirtypes/common';
import { isUri } from 'valid-url';
import { flatMap, partition, xor } from 'lodash';
import { cloneDeep, flatMap, partition, xor } from 'lodash';

export class ValueSetExporter {
constructor(
Expand Down Expand Up @@ -250,6 +250,39 @@ export class ValueSetExporter {
ruleMap.set(assembleFSHPath(pathParts).replace(/\[0+\]/g, ''), { pathParts });
return rule;
} catch (originalErr) {
// if the value is a number, it may have been a four-digit number
// that we tried to assign to a date or dateTime.
// a four-digit number could be a valid year, so see if it can be assigned.
if (
originalErr instanceof MismatchedTypeError &&
['date', 'dateTime'].includes(originalErr.elementType) &&
['number', 'bigint'].includes(typeof rule.value)
) {
try {
const retryRule = cloneDeep(rule);
const { pathParts } = valueSetSD.validateValueAtPath(
rule.caretPath,
retryRule.rawValue,
this.fisher
);
ruleMap.set(assembleFSHPath(pathParts).replace(/\[0+\]/g, ''), { pathParts });
retryRule.value = retryRule.rawValue;
return retryRule;
} catch (retryErr) {
if (retryErr instanceof MismatchedTypeError) {
logger.error(originalErr.message, rule.sourceInfo);
if (originalErr.stack) {
logger.debug(originalErr.stack);
}
} else {
logger.error(retryErr.message, rule.sourceInfo);
if (retryErr.stack) {
logger.debug(retryErr.stack);
}
}
return null;
}
}
// 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.
// try to fish up an Instance with the rule's raw value.
Expand Down
86 changes: 86 additions & 0 deletions test/export/CodeSystemExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1231,6 +1231,92 @@ describe('CodeSystemExporter', () => {
);
});

it('should assign a date that was parsed as a number', () => {
// CodeSystem: CaretCodeSystem
// * #someCode "Some Code"
// * ^extension[0].url = "http://example.org/SomeExt"
// * ^extension[0].valueDate = 2023
const codeSystem = new FshCodeSystem('CaretCodeSystem');
const someCode = new ConceptRule('someCode', 'Some Code');
const extensionUrl = new CaretValueRule('');
extensionUrl.caretPath = 'extension[0].url';
extensionUrl.value = 'http://example.org/SomeExt';
const extensionValue = new CaretValueRule('');
extensionValue.caretPath = 'extension[0].valueDate';
extensionValue.value = BigInt(2023);
extensionValue.rawValue = '2023';
codeSystem.rules.push(someCode, extensionUrl, extensionValue);
doc.codeSystems.set(codeSystem.name, codeSystem);

const exported = exporter.export().codeSystems;
expect(exported.length).toBe(1);
expect(exported[0]).toEqual({
resourceType: 'CodeSystem',
id: 'CaretCodeSystem',
name: 'CaretCodeSystem',
content: 'complete',
url: 'http://hl7.org/fhir/us/minimal/CodeSystem/CaretCodeSystem',
count: 1,
status: 'draft',
extension: [
{
url: 'http://example.org/SomeExt',
valueDate: '2023'
}
],
concept: [
{
code: 'someCode',
display: 'Some Code'
}
]
});
});

it('should assign a dateTime that was parsed as a number', () => {
// CodeSystem: CaretCodeSystem
// * #someCode "Some Code"
// * #someCode ^property[0].code = #standard
// * #someCode ^property[0].valueDateTime = 0081
const codeSystem = new FshCodeSystem('CaretCodeSystem');
const someCode = new ConceptRule('someCode', 'Some Code');
const propertyCode = new CaretValueRule('');
propertyCode.pathArray = ['#someCode'];
propertyCode.caretPath = 'property[0].code';
propertyCode.value = new FshCode('standard');
const propertyValue = new CaretValueRule('');
propertyValue.pathArray = ['#someCode'];
propertyValue.caretPath = 'property[0].valueDateTime';
propertyValue.value = BigInt(81);
propertyValue.rawValue = '0081';
codeSystem.rules.push(someCode, propertyCode, propertyValue);
doc.codeSystems.set(codeSystem.name, codeSystem);

const exported = exporter.export().codeSystems;
expect(exported.length).toBe(1);
expect(exported[0]).toEqual({
resourceType: 'CodeSystem',
id: 'CaretCodeSystem',
name: 'CaretCodeSystem',
content: 'complete',
url: 'http://hl7.org/fhir/us/minimal/CodeSystem/CaretCodeSystem',
count: 1,
status: 'draft',
concept: [
{
code: 'someCode',
display: 'Some Code',
property: [
{
code: 'standard',
valueDateTime: '0081'
}
]
}
]
});
});

describe('#insertRules', () => {
let cs: FshCodeSystem;
let ruleSet: RuleSet;
Expand Down
31 changes: 31 additions & 0 deletions test/export/InstanceExporter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10117,6 +10117,37 @@ describe('InstanceExporter', () => {
});
});

it('should assign a date that was parsed as a number', () => {
// Instance: ExampleObs
// InstanceOf: Observation
// * extension[0].url = "http://example.org/SomeExt"
// * extension[0].valueDate = 2055
const observation = new Instance('ExampleObs');
observation.instanceOf = 'Observation';
const extensionUrl = new AssignmentRule('extension[0].url');
extensionUrl.value = 'http://example.org/SomeExt';
const extensionValue = new AssignmentRule('extension[0].valueDate');
extensionValue.value = BigInt(2055);
extensionValue.rawValue = '2055';
observation.rules.push(extensionUrl, extensionValue);
const exportedInstance = exportInstance(observation);
expect(exportedInstance.extension[0].valueDate).toEqual('2055');
});

it('should assign a dateTime that was parsed as a number', () => {
// Instance: ExampleObs
// InstanceOf: Observation
// * valueDateTime = 0895
const observation = new Instance('ExampleObs');
observation.instanceOf = 'Observation';
const assignmentRule = new AssignmentRule('valueDateTime');
assignmentRule.value = BigInt(895);
assignmentRule.rawValue = '0895';
observation.rules.push(assignmentRule);
const exportedInstance = exportInstance(observation);
expect(exportedInstance.valueDateTime).toEqual('0895');
});

describe('#TimeTravelingResources', () => {
it('should export a R5 ActorDefinition in a R4 IG', () => {
// Instance: AD1
Expand Down
Loading
Loading