babelfhir-ts 1.5.21 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/out/src/generator/core/constants.js +2 -1
- package/out/src/generator/core/extensionSdReader.js +4 -1
- package/out/src/generator/core/utils.js +91 -9
- package/out/src/generator/emitters/class/classGenerator.js +14 -8
- package/out/src/generator/emitters/class/classGeneratorHelpers.js +22 -9
- package/out/src/generator/emitters/class/classHeuristics.js +4 -2
- package/out/src/generator/emitters/class/fieldPatternExtractor.js +17 -13
- package/out/src/generator/emitters/class/sliceElementDefaults.js +21 -6
- package/out/src/generator/emitters/class/sliceElementGenerator.js +3 -3
- package/out/src/generator/emitters/interface/backboneSliceTyping.js +21 -13
- package/out/src/generator/emitters/interface/importManager.js +1 -1
- package/out/src/generator/emitters/interface/interfaceFieldProcessor.js +15 -12
- package/out/src/generator/emitters/interface/interfaceFieldUtils.js +6 -3
- package/out/src/generator/emitters/interface/interfaceGenerator.js +14 -9
- package/out/src/generator/emitters/interface/postProcessExtensions.js +32 -16
- package/out/src/generator/emitters/interface/processFlatField.js +9 -7
- package/out/src/generator/emitters/interface/processNestedField.js +9 -10
- package/out/src/generator/emitters/namingsystem/namingSystemGenerator.js +136 -0
- package/out/src/generator/emitters/prefab/prefabEmitter.js +2 -15
- package/out/src/generator/emitters/prefab/prefabRenderer.js +2 -19
- package/out/src/generator/emitters/prefab/prefabTypeMapper.js +2 -0
- package/out/src/generator/emitters/validator/closedSlicingValidation.js +5 -2
- package/out/src/generator/emitters/validator/fhirpathStubInstaller.js +7 -1
- package/out/src/generator/emitters/validator/sliceBackboneValidation.js +27 -12
- package/out/src/generator/emitters/validator/sliceDelegation.js +8 -6
- package/out/src/generator/emitters/validator/sliceExtensionValidation.js +3 -3
- package/out/src/generator/emitters/validator/sliceValidatorGenerator.js +41 -41
- package/out/src/generator/emitters/validator/sliceValidatorUtils.js +8 -3
- package/out/src/generator/emitters/validator/subExtensionScope.js +10 -2
- package/out/src/generator/emitters/validator/validatorBindingBuilder.js +4 -3
- package/out/src/generator/emitters/validator/validatorBindingLeafEmitters.js +5 -5
- package/out/src/generator/emitters/validator/validatorConstraintBuilders.js +8 -6
- package/out/src/generator/emitters/validator/validatorExpressions.js +44 -0
- package/out/src/generator/emitters/validator/validatorFieldBuilders.js +34 -17
- package/out/src/generator/emitters/validator/validatorGenerator.js +43 -47
- package/out/src/generator/emitters/validator/validatorRuntime.js +13 -2
- package/out/src/generator/emitters/validator/validatorTemplates.js +4 -1
- package/out/src/generator/emitters/valueset/valueSetGenerator.js +6 -10
- package/out/src/generator/emitters/zod/zodRefinementBuilder.js +48 -8
- package/out/src/generator/emitters/zod/zodSchemaGenerator.js +51 -24
- package/out/src/generator/fhir/corePackageResolver.js +2 -3
- package/out/src/generator/fhir/corePackageResolver.ts +2 -3
- package/out/src/generator/generationHelpers.js +59 -7
- package/out/src/generator/index.js +94 -49
- package/out/src/generator/parser/packageManager.js +4 -4
- package/out/src/generator/parser/packageParser.js +116 -64
- package/out/src/generator/parser/sdFetcher.js +5 -3
- package/out/src/generator/parser/sdParser.js +1 -1
- package/out/src/generator/parser/sliceAggregator.js +13 -10
- package/out/src/generator/parser/sliceAggregatorComposition.js +11 -2
- package/out/src/generator/parser/vsParser.js +6 -3
- package/out/src/generator/sdProcessor.js +30 -21
- package/out/src/generator/sdProcessorRequiredFields.js +3 -2
- package/out/src/main.js +5 -0
- package/package.json +13 -10
- package/parity-matrix.json +6 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* BackboneElement and binding-codes slice validation strategies.
|
|
3
3
|
*/
|
|
4
|
+
import { firstSegment } from '../../core/utils.js';
|
|
4
5
|
import { safeAccessor, safeList, sliceElseBranch, detectNestedArray, FHIR_PRIMITIVE_STRING_TYPES, collectWithinSliceMaxChildren, generateWithinSliceMaxChecks } from './sliceValidatorUtils.js';
|
|
5
6
|
/** Collect required direct children of a BackboneElement slice for per-element validation. */
|
|
6
7
|
export function collectRequiredSliceChildren(sliceElementId, fields, discriminatorProp, profileName, relPath) {
|
|
@@ -45,6 +46,11 @@ export function collectNestedRequiredSliceChildren(sliceElementId, fields, discr
|
|
|
45
46
|
const parts = relativePath.split('.');
|
|
46
47
|
if (parts.length !== 2)
|
|
47
48
|
continue;
|
|
49
|
+
// Destructured so the two segments are values: the length check establishes
|
|
50
|
+
// there are exactly two, but indexing alone does not tell the compiler.
|
|
51
|
+
const [parentSegment, childSegment] = parts;
|
|
52
|
+
if (!parentSegment || !childSegment)
|
|
53
|
+
continue;
|
|
48
54
|
if (relativePath.includes(':'))
|
|
49
55
|
continue;
|
|
50
56
|
if (f.max === 0)
|
|
@@ -52,20 +58,21 @@ export function collectNestedRequiredSliceChildren(sliceElementId, fields, discr
|
|
|
52
58
|
const childMin = typeof f.min === 'number' ? f.min : (f.isOptional ? 0 : 1);
|
|
53
59
|
if (childMin < 1)
|
|
54
60
|
continue;
|
|
55
|
-
const parentProp =
|
|
61
|
+
const parentProp = parentSegment.replace(/\[x\]$/, '');
|
|
56
62
|
if (parentProp === discriminatorProp)
|
|
57
63
|
continue;
|
|
58
|
-
const isChoiceType =
|
|
59
|
-
const prop =
|
|
64
|
+
const isChoiceType = childSegment.endsWith('[x]');
|
|
65
|
+
const prop = childSegment.replace(/\[x\]$/, '');
|
|
60
66
|
const key = `${parentProp}.${prop}`;
|
|
61
67
|
if (seen.has(key))
|
|
62
68
|
continue;
|
|
63
69
|
seen.add(key);
|
|
64
|
-
const parentFieldId = `${sliceElementId}.${
|
|
70
|
+
const parentFieldId = `${sliceElementId}.${parentSegment}`;
|
|
65
71
|
const parentField = fields.find(pf => (pf.elementId || pf.name) === parentFieldId);
|
|
66
72
|
const isParentArray = parentField?.isArray ?? false;
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
// relPath is non-empty, so split() yields a last segment; falling back to the
|
|
74
|
+
// whole path keeps the error path readable if it ever does not.
|
|
75
|
+
const lastPart = relPath.split('.').at(-1) ?? relPath;
|
|
69
76
|
result.push({ parentProp, isParentArray, prop, errorPath: `${profileName}.${lastPart}.${parentProp}.${prop}`, isChoiceType });
|
|
70
77
|
}
|
|
71
78
|
return result;
|
|
@@ -87,7 +94,11 @@ export function collectFixedValueSliceChildren(sliceElementId, fields, discrimin
|
|
|
87
94
|
// Handle choice type slicing: value[x]:valueString → prop "valueString"
|
|
88
95
|
let prop = relativePath;
|
|
89
96
|
if (prop.includes('[x]:')) {
|
|
90
|
-
|
|
97
|
+
// The typed variant after the colon: "valueString", "valueCode", etc.
|
|
98
|
+
const typedVariant = prop.split(':')[1];
|
|
99
|
+
if (!typedVariant)
|
|
100
|
+
continue;
|
|
101
|
+
prop = typedVariant;
|
|
91
102
|
}
|
|
92
103
|
else if (prop.includes(':')) {
|
|
93
104
|
continue; // Skip other slice references
|
|
@@ -117,13 +128,13 @@ export function generateBackboneElementValidation(slice, relPath, errorPath, sli
|
|
|
117
128
|
const sliceId = (slice.elementId || slice.name).replace(/^[^.]+\./, '');
|
|
118
129
|
let discriminatorProp = '';
|
|
119
130
|
if (childFieldId.startsWith(sliceId + '.')) {
|
|
120
|
-
discriminatorProp = childFieldId.substring(sliceId.length + 1)
|
|
131
|
+
discriminatorProp = firstSegment(childFieldId.substring(sliceId.length + 1));
|
|
121
132
|
}
|
|
122
133
|
else {
|
|
123
134
|
const childFieldName = discriminatorChild.name.replace(/^[^.]+\./, '');
|
|
124
135
|
const sliceName = slice.name.replace(/^[^.]+\./, '');
|
|
125
136
|
if (childFieldName.startsWith(sliceName + '.')) {
|
|
126
|
-
discriminatorProp = childFieldName.substring(sliceName.length + 1)
|
|
137
|
+
discriminatorProp = firstSegment(childFieldName.substring(sliceName.length + 1));
|
|
127
138
|
}
|
|
128
139
|
}
|
|
129
140
|
if (discriminatorProp.includes('[x]')) {
|
|
@@ -167,7 +178,7 @@ export function generateBackboneElementValidation(slice, relPath, errorPath, sli
|
|
|
167
178
|
}
|
|
168
179
|
if (!checks)
|
|
169
180
|
return;
|
|
170
|
-
const resourceType = profileName || sliceElementId
|
|
181
|
+
const resourceType = profileName || firstSegment(sliceElementId);
|
|
171
182
|
const requiredChildren = collectRequiredSliceChildren(sliceElementId, fields, discriminatorProp, resourceType, relPath);
|
|
172
183
|
const nestedChildren = collectNestedRequiredSliceChildren(sliceElementId, fields, discriminatorProp, resourceType, relPath);
|
|
173
184
|
const directChecksInner = requiredChildren.map(c => c.isChoiceType
|
|
@@ -257,12 +268,16 @@ export function generateBackboneElementValidation(slice, relPath, errorPath, sli
|
|
|
257
268
|
*/
|
|
258
269
|
export function generateFixedValueSliceValidation(slice, relPath, errorPath, sliceLabel, varName, min, fields, childFieldsWithFixedValue, out, profileName) {
|
|
259
270
|
const sliceElementId = slice.elementId || slice.name;
|
|
271
|
+
// The caller only reaches here with a fixed-value child, but an empty list would
|
|
272
|
+
// otherwise read its discriminator off undefined.
|
|
260
273
|
const discriminatorChild = childFieldsWithFixedValue[0];
|
|
274
|
+
if (!discriminatorChild)
|
|
275
|
+
return;
|
|
261
276
|
const childFieldId = (discriminatorChild.elementId || discriminatorChild.name).replace(/^[^.]+\./, '');
|
|
262
277
|
const sliceId = (slice.elementId || slice.name).replace(/^[^.]+\./, '');
|
|
263
278
|
let discriminatorProp = '';
|
|
264
279
|
if (childFieldId.startsWith(sliceId + '.')) {
|
|
265
|
-
discriminatorProp = childFieldId.substring(sliceId.length + 1)
|
|
280
|
+
discriminatorProp = firstSegment(childFieldId.substring(sliceId.length + 1));
|
|
266
281
|
}
|
|
267
282
|
if (!discriminatorProp)
|
|
268
283
|
return;
|
|
@@ -270,7 +285,7 @@ export function generateFixedValueSliceValidation(slice, relPath, errorPath, sli
|
|
|
270
285
|
? `"${discriminatorChild.fixedValue}"`
|
|
271
286
|
: String(discriminatorChild.fixedValue);
|
|
272
287
|
const checks = `at(item, '${discriminatorProp}') === ${fixedVal}`;
|
|
273
|
-
const resourceType = profileName || sliceElementId
|
|
288
|
+
const resourceType = profileName || firstSegment(sliceElementId);
|
|
274
289
|
const requiredChildren = collectRequiredSliceChildren(sliceElementId, fields, discriminatorProp, resourceType, relPath);
|
|
275
290
|
const nestedChildren = collectNestedRequiredSliceChildren(sliceElementId, fields, discriminatorProp, resourceType, relPath);
|
|
276
291
|
const fixedChildren = collectFixedValueSliceChildren(sliceElementId, fields, discriminatorProp);
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* validator, which is what keeps findings inside a nested resource from being
|
|
7
7
|
* lost. Split out of sliceValidatorGenerator, which had grown past the size limit.
|
|
8
8
|
*/
|
|
9
|
-
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
|
|
9
|
+
import { stripVersionFromCanonicalUrl, firstSegment } from '../../core/utils.js';
|
|
10
10
|
import { safeAccessor, safeList, collectWithinSliceMaxChildren, generateWithinSliceMaxChecks } from './sliceValidatorUtils.js';
|
|
11
11
|
export function generateProfiledChildDelegation(slice, relPath, childFieldsWithFixedValue, out, ctx) {
|
|
12
12
|
if (!ctx.profileUrlToName || !ctx.validatorImports)
|
|
@@ -26,10 +26,12 @@ export function generateProfiledChildDelegation(slice, relPath, childFieldsWithF
|
|
|
26
26
|
return;
|
|
27
27
|
// Get discriminator info to build the filter expression
|
|
28
28
|
const discriminatorChild = childFieldsWithFixedValue[0];
|
|
29
|
+
if (!discriminatorChild)
|
|
30
|
+
return;
|
|
29
31
|
const childFieldId = (discriminatorChild.elementId || discriminatorChild.name).replace(/^[^.]+\./, '');
|
|
30
32
|
let discriminatorProp = '';
|
|
31
33
|
if (childFieldId.startsWith(sliceElemId + '.')) {
|
|
32
|
-
discriminatorProp = childFieldId.substring(sliceElemId.length + 1)
|
|
34
|
+
discriminatorProp = firstSegment(childFieldId.substring(sliceElemId.length + 1));
|
|
33
35
|
}
|
|
34
36
|
if (!discriminatorProp)
|
|
35
37
|
return;
|
|
@@ -148,9 +150,9 @@ export function detectReferenceProfileDiscriminator(slice, profileUrlToType) {
|
|
|
148
150
|
if (!slice.typeOptions?.length)
|
|
149
151
|
return undefined;
|
|
150
152
|
const refType = slice.typeOptions.find(t => t.code === 'Reference');
|
|
151
|
-
|
|
153
|
+
const targetUrl = refType?.targetProfileUrls?.[0];
|
|
154
|
+
if (!targetUrl)
|
|
152
155
|
return undefined;
|
|
153
|
-
const targetUrl = refType.targetProfileUrls[0];
|
|
154
156
|
// 1. Authoritative: resolve from profileUrlToType map
|
|
155
157
|
if (profileUrlToType) {
|
|
156
158
|
const resolved = profileUrlToType.get(targetUrl);
|
|
@@ -190,7 +192,7 @@ export function detectReferenceProfileDiscriminator(slice, profileUrlToType) {
|
|
|
190
192
|
export function generateReferenceProfileDiscriminatorValidation(slice, relPath, errorPath, sliceLabel, varName, min, targetResourceType, out, fields, baseResourceType) {
|
|
191
193
|
// Build within-slice child max constraint checks (e.g., max=0 forbidden fields)
|
|
192
194
|
const maxChildren = fields ? collectWithinSliceMaxChildren(slice, fields) : [];
|
|
193
|
-
const resType = baseResourceType || (slice.elementId || slice.name)
|
|
195
|
+
const resType = baseResourceType || firstSegment(slice.elementId || slice.name);
|
|
194
196
|
const childMaxChecks = generateWithinSliceMaxChecks(maxChildren, `_${varName}Refs`, resType, relPath, sliceLabel);
|
|
195
197
|
out.push(`
|
|
196
198
|
// Required Reference-profile slice validation for ${relPath}:${sliceLabel} (target → ${targetResourceType})
|
|
@@ -333,7 +335,7 @@ export function detectProfileDiscriminator(slice, fields, profileUrlToType) {
|
|
|
333
335
|
}
|
|
334
336
|
// 2. Try to extract from profile URL last segment (core profiles)
|
|
335
337
|
const profileUrl = profileUrls[0];
|
|
336
|
-
const lastSegment = profileUrl
|
|
338
|
+
const lastSegment = profileUrl?.split('/').pop() || '';
|
|
337
339
|
if (/^[A-Z][a-zA-Z]+$/.test(lastSegment)) {
|
|
338
340
|
return { resourceType: lastSegment, profileUrls };
|
|
339
341
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Extension and ValueSet slice validation strategies.
|
|
3
3
|
*/
|
|
4
4
|
import { safeAccessor, safeList, sliceElseBranch, detectNestedArray, findArrayAncestor, isArrayPath, FHIR_PRIMITIVE_STRING_TYPES } from './sliceValidatorUtils.js';
|
|
5
|
-
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
|
|
5
|
+
import { stripVersionFromCanonicalUrl, stripSliceSuffix } from '../../core/utils.js';
|
|
6
6
|
import { buildSubExtensionScope, renderScopeHops } from './subExtensionScope.js';
|
|
7
7
|
/**
|
|
8
8
|
* Read pre-resolved Extension SD metadata for a canonical.
|
|
@@ -267,7 +267,7 @@ export function generateExistsExtensionValidation(slice, relPath, errorPath, sli
|
|
|
267
267
|
* profile URL that can serve as the extension URL discriminator.
|
|
268
268
|
*/
|
|
269
269
|
export function detectExtensionUrlSlice(slice, relPath, fields) {
|
|
270
|
-
const basePath = relPath
|
|
270
|
+
const basePath = stripSliceSuffix(relPath);
|
|
271
271
|
if (!basePath.endsWith('extension') && !basePath.endsWith('modifierExtension'))
|
|
272
272
|
return undefined;
|
|
273
273
|
if (slice.profileUrls && slice.profileUrls.length > 0) {
|
|
@@ -335,7 +335,7 @@ export function generateExtensionUrlSliceValidation(slice, relPath, errorPath, s
|
|
|
335
335
|
const scopedElemId = (slice.elementId || '').replace(/^[^.]+\./, '');
|
|
336
336
|
const scopedErrorPath = scopedElemId || `${errorPath}:${sliceLabel}`;
|
|
337
337
|
// The slot the slice lives in: its own last segment, minus the slice qualifier.
|
|
338
|
-
const leafProp = scopedElemId.split('.').pop()
|
|
338
|
+
const leafProp = stripSliceSuffix(scopedElemId.split('.').pop() ?? '');
|
|
339
339
|
const body = renderScopeHops(subScope, (container, indent) => {
|
|
340
340
|
const matches = `${varName}Matches`;
|
|
341
341
|
let inner = `\n${indent}const ${matches} = elements(at(${container}, '${leafProp}')).filter((item) => item.url === "${extensionUrl}");`;
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { addDiagnostic } from '../../core/sdDiagnostics.js';
|
|
7
7
|
import { INLINE_RESOURCE_SLOTS } from './validatorFieldBuilders.js';
|
|
8
|
-
import {
|
|
8
|
+
import { firstSegment, stripSliceSuffix } from '../../core/utils.js';
|
|
9
|
+
import { safeAccessor, safeList, sliceElseBranch, rewritePrimitiveExtensionPath, isArrayPath, resolveNestedPatternChild, collectWithinSliceMaxChildren, generateWithinSliceMaxChecks } from './sliceValidatorUtils.js';
|
|
9
10
|
import { generateProfiledChildDelegation, detectReferenceProfileDiscriminator, generateReferenceProfileDiscriminatorValidation, generateOptionalProfiledSliceDelegation, detectProfileDiscriminator, generateProfileDiscriminatorValidation, detectSelfTypeDiscriminator, generateSelfTypeSliceValidation, generateDatatypeProfileSliceValidation, } from './sliceDelegation.js';
|
|
10
11
|
import { generateBackboneElementValidation, generateBindingCodesValidation, generateFixedValueSliceValidation } from './sliceBackboneValidation.js';
|
|
11
12
|
import { generateValueSetValidation, detectExistsExtensionDiscriminator, generateExistsExtensionValidation, detectExtensionUrlSlice, generateExtensionUrlSliceValidation, } from './sliceExtensionValidation.js';
|
|
@@ -144,7 +145,7 @@ export function generateSliceValidations(ctx) {
|
|
|
144
145
|
return subMin > 0;
|
|
145
146
|
}).map(f => {
|
|
146
147
|
const fId = (f.elementId || f.name).replace(/^[^.]+\./, '');
|
|
147
|
-
return fId.substring(sliceElemId.length + 1)
|
|
148
|
+
return firstSegment(fId.substring(sliceElemId.length + 1));
|
|
148
149
|
});
|
|
149
150
|
generatePatternCodingValidation(slice, relPath, errorPath, sliceLabel, varName, min, fields, sliceValidations, requiredSubFields, ctx.baseResourceType, ctx.profileUrl);
|
|
150
151
|
}
|
|
@@ -152,17 +153,21 @@ export function generateSliceValidations(ctx) {
|
|
|
152
153
|
// For extension slices where the only fixedValue child is 'url', prefer the
|
|
153
154
|
// extension URL validation path — it includes value[x] type checking from
|
|
154
155
|
// resolved ExtensionMetadata, which generic fixedValue validation doesn't.
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
156
|
+
// Read the sole child once rather than indexing it five times.
|
|
157
|
+
const onlyFixedChild = childFieldsWithFixedValue.length === 1
|
|
158
|
+
? childFieldsWithFixedValue[0]
|
|
159
|
+
: undefined;
|
|
160
|
+
const extUrl = typeof onlyFixedChild?.fixedValue === 'string'
|
|
161
|
+
? onlyFixedChild.fixedValue
|
|
162
|
+
: undefined;
|
|
163
|
+
const isExtensionUrlOnly = !!onlyFixedChild && extUrl !== undefined &&
|
|
158
164
|
(() => {
|
|
159
|
-
const fId = (
|
|
165
|
+
const fId = (onlyFixedChild.elementId || onlyFixedChild.name).replace(/^[^.]+\./, '');
|
|
160
166
|
const sliceEId = (slice.elementId || slice.name).replace(/^[^.]+\./, '');
|
|
161
167
|
const relChild = fId.startsWith(sliceEId + '.') ? fId.substring(sliceEId.length + 1) : '';
|
|
162
168
|
return relChild === 'url';
|
|
163
169
|
})();
|
|
164
|
-
if (isExtensionUrlOnly) {
|
|
165
|
-
const extUrl = childFieldsWithFixedValue[0].fixedValue;
|
|
170
|
+
if (isExtensionUrlOnly && extUrl !== undefined) {
|
|
166
171
|
generateExtensionUrlSliceValidation(slice, relPath, errorPath, sliceLabel, varName, min, extUrl, fieldPathMap, arrayFieldPaths, sliceValidations, ctx);
|
|
167
172
|
}
|
|
168
173
|
else {
|
|
@@ -194,6 +199,10 @@ export function generateSliceValidations(ctx) {
|
|
|
194
199
|
generateProfileDiscriminatorValidation(slice, relPath, errorPath, sliceLabel, varName, min, profileMatch, sliceValidations, ctx);
|
|
195
200
|
}
|
|
196
201
|
else {
|
|
202
|
+
const sliceProfileUrl = slice.profileUrls?.[0];
|
|
203
|
+
const datatypePin = sliceProfileUrl
|
|
204
|
+
? ctx.datatypeProfilePins?.get(sliceProfileUrl)
|
|
205
|
+
: undefined;
|
|
197
206
|
const refResourceType = detectReferenceProfileDiscriminator(slice, ctx.profileUrlToType);
|
|
198
207
|
if (refResourceType) {
|
|
199
208
|
generateReferenceProfileDiscriminatorValidation(slice, relPath, errorPath, sliceLabel, varName, min, refResourceType, sliceValidations, fields, ctx.baseResourceType);
|
|
@@ -201,10 +210,11 @@ export function generateSliceValidations(ctx) {
|
|
|
201
210
|
else if (slice.fixedValue !== undefined && !isChoiceTypeSlice) {
|
|
202
211
|
generateSelfFixedValueSliceValidation(slice, relPath, errorPath, sliceLabel, varName, min, sliceValidations);
|
|
203
212
|
}
|
|
204
|
-
else if (
|
|
213
|
+
else if (datatypePin) {
|
|
205
214
|
// A {profile, $this} slice against a datatype profile: the profile's
|
|
206
|
-
// own pinned element is what distinguishes its members.
|
|
207
|
-
|
|
215
|
+
// own pinned element is what distinguishes its members. Resolved once
|
|
216
|
+
// above, which also drops the non-null assertion on the second lookup.
|
|
217
|
+
generateDatatypeProfileSliceValidation(relPath, errorPath, sliceLabel, varName, min, datatypePin, sliceValidations);
|
|
208
218
|
}
|
|
209
219
|
else if (INLINE_RESOURCE_SLOTS.has(relPath) && detectSelfTypeDiscriminator(slice)) {
|
|
210
220
|
// An inline-resource slot sliced by {type, $this}: the member is the
|
|
@@ -310,28 +320,12 @@ function generatePatternCodeableConceptValidation(slice, relPath, errorPath, sli
|
|
|
310
320
|
const sliceFullId = slice.sliceName && !sliceElemId.includes(':')
|
|
311
321
|
? `${sliceElemId}:${slice.sliceName}`
|
|
312
322
|
: sliceElemId;
|
|
313
|
-
const resType = baseResourceType || sliceElemId
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
if (relChild.includes('.'))
|
|
320
|
-
return false;
|
|
321
|
-
return typeof f.max === 'number' && f.max < Infinity;
|
|
322
|
-
});
|
|
323
|
-
for (const child of childMaxFields) {
|
|
324
|
-
const childFullId = (child.elementId || child.name).replace(/^[^.]+\./, '');
|
|
325
|
-
const childProp = childFullId.substring(sliceFullId.length + 1);
|
|
326
|
-
const maxVal = child.max;
|
|
327
|
-
childMaxChecks += `
|
|
328
|
-
for (const _wsElem of ${varName}Elements) {
|
|
329
|
-
const _wsArr = at(_wsElem, '${childProp}');
|
|
330
|
-
if (Array.isArray(_wsArr) && _wsArr.length > ${maxVal}) {
|
|
331
|
-
errors.push("${resType}.${relPath}:${sliceLabel}.${childProp}: max allowed = ${maxVal}, but found " + _wsArr.length);
|
|
332
|
-
}
|
|
333
|
-
}`;
|
|
334
|
-
}
|
|
323
|
+
const resType = baseResourceType || firstSegment(sliceElemId);
|
|
324
|
+
// Shared with the backbone and delegation slice validators. The inline copy
|
|
325
|
+
// this replaces was array-only, so `max=0` on a singleton child could never
|
|
326
|
+
// fire, and it rejected any dotted path, so a grandchild like
|
|
327
|
+
// `entity:patient.what.reference` was never reached.
|
|
328
|
+
childMaxChecks += generateWithinSliceMaxChecks(collectWithinSliceMaxChildren(slice, fields), `${varName}Elements`, resType, relPath, sliceLabel);
|
|
335
329
|
// Build within-slice fixedValue checks on nested descendants.
|
|
336
330
|
// E.g. category:labCategory.coding.system fixed to a specific URI.
|
|
337
331
|
const fixedValueDescendants = fields.filter(f => {
|
|
@@ -371,11 +365,11 @@ function generatePatternCodeableConceptValidation(slice, relPath, errorPath, sli
|
|
|
371
365
|
// pattern coding values as effectively fixed on each coding element
|
|
372
366
|
// within matched slice members. The HL7 validator treats pattern values
|
|
373
367
|
// as fixed when combined with max=1 cardinality.
|
|
374
|
-
const codingChild =
|
|
368
|
+
const codingChild = fields.find(f => {
|
|
375
369
|
const fId = (f.elementId || f.name).replace(/^[^.]+\./, '');
|
|
376
|
-
return fId.substring(sliceFullId.length + 1) === 'coding';
|
|
370
|
+
return fId.startsWith(sliceFullId + '.') && fId.substring(sliceFullId.length + 1) === 'coding';
|
|
377
371
|
});
|
|
378
|
-
if (codingChild
|
|
372
|
+
if (codingChild?.max === 1) {
|
|
379
373
|
const pRef = profileUrl ? `${profileUrl}#${resType}.${relPath}:${sliceLabel}` : `${resType}.${relPath}:${sliceLabel}`;
|
|
380
374
|
for (const pc of patternCodings) {
|
|
381
375
|
const pSys = pc.system ? String(pc.system) : undefined;
|
|
@@ -462,7 +456,7 @@ function generatePatternCodingValidation(slice, relPath, errorPath, sliceLabel,
|
|
|
462
456
|
// nothing where HL7 reports the mismatch. The CodeableConcept sibling has done
|
|
463
457
|
// this for its descendants all along — the gap was only here.
|
|
464
458
|
const sliceIdRel = (slice.elementId || slice.name).replace(/^[^.]+\./, '');
|
|
465
|
-
const resType = baseResourceType || sliceIdRel
|
|
459
|
+
const resType = baseResourceType || firstSegment(sliceIdRel);
|
|
466
460
|
const fixedChildChecks = fields
|
|
467
461
|
.filter(f => {
|
|
468
462
|
if (typeof f.fixedValue !== 'string' && typeof f.fixedValue !== 'number' && f.fixedValue !== true)
|
|
@@ -508,10 +502,12 @@ function generatePatternCodingValidation(slice, relPath, errorPath, sliceLabel,
|
|
|
508
502
|
const sliceIdParts = sliceElemId.split('.');
|
|
509
503
|
for (let i = 0; i < sliceIdParts.length - 1; i++) {
|
|
510
504
|
const part = sliceIdParts[i];
|
|
511
|
-
if (part
|
|
505
|
+
if (part?.includes(':')) {
|
|
512
506
|
const [basePath, sliceNameInId] = part.split(':');
|
|
507
|
+
if (!basePath)
|
|
508
|
+
continue;
|
|
513
509
|
const parentSlice = fields.find(f => f.sliceName === sliceNameInId &&
|
|
514
|
-
f.name.replace(/^[^.]+\./, '')
|
|
510
|
+
firstSegment(f.name.replace(/^[^.]+\./, '')) === stripSliceSuffix(basePath));
|
|
515
511
|
if (parentSlice) {
|
|
516
512
|
const parentMin = typeof parentSlice.min === 'number' ? parentSlice.min : (parentSlice.isOptional ? 0 : 1);
|
|
517
513
|
if (parentMin === 0) {
|
|
@@ -522,9 +518,13 @@ function generatePatternCodingValidation(slice, relPath, errorPath, sliceLabel,
|
|
|
522
518
|
}
|
|
523
519
|
}
|
|
524
520
|
const matchedChecks = subFieldChecks + fixedChildChecks;
|
|
525
|
-
|
|
521
|
+
// Within-slice max cardinality on this slice's own children, through the same
|
|
522
|
+
// helper the backbone and delegation validators use. This validator never ran
|
|
523
|
+
// them, so a `max=0` child of a Coding slice went unreported.
|
|
524
|
+
const maxChecks = generateWithinSliceMaxChecks(collectWithinSliceMaxChildren(slice, fields), `${varName}Elements`, resType, relPath, sliceLabel);
|
|
525
|
+
const subFieldBlock = (matchedChecks ? `
|
|
526
526
|
for (const matched of ${varName}Elements) {${matchedChecks}
|
|
527
|
-
}` : '';
|
|
527
|
+
}` : '') + maxChecks;
|
|
528
528
|
if (isNestedInArray && arrayParentPath) {
|
|
529
529
|
const remainingPath = relPath.substring(arrayParentPath.length + 1);
|
|
530
530
|
const optionalPath = remainingPath.split('.').join('?.');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared utility functions for slice validation code generation.
|
|
3
3
|
*/
|
|
4
|
+
import { stripSliceSuffix } from '../../core/utils.js';
|
|
4
5
|
/** FHIR primitive type codes whose JSON extensions use _fieldName prefix. */
|
|
5
6
|
export const FHIR_PRIMITIVES = new Set([
|
|
6
7
|
'boolean', 'integer', 'string', 'decimal', 'uri', 'url', 'canonical',
|
|
@@ -56,7 +57,7 @@ export function detectNestedArray(relPath, fields) {
|
|
|
56
57
|
const partialPath = pathParts.slice(0, i + 1).join('.');
|
|
57
58
|
const parentField = fields.find(f => {
|
|
58
59
|
const fieldPath = f.name.replace(/^[^.]+\./, '');
|
|
59
|
-
return fieldPath === partialPath || fieldPath
|
|
60
|
+
return fieldPath === partialPath || stripSliceSuffix(fieldPath) === partialPath;
|
|
60
61
|
});
|
|
61
62
|
if (parentField && parentField.isArray) {
|
|
62
63
|
return {
|
|
@@ -101,7 +102,7 @@ export function rewritePrimitiveExtensionPath(relPath, fields) {
|
|
|
101
102
|
const sdSuffix = parts.slice(0, extIdx).join('.');
|
|
102
103
|
const parentField = fields.find(f => {
|
|
103
104
|
const fPath = f.name.replace(/^[^.]+\./, '');
|
|
104
|
-
return fPath === sdSuffix || fPath
|
|
105
|
+
return fPath === sdSuffix || stripSliceSuffix(fPath) === sdSuffix;
|
|
105
106
|
});
|
|
106
107
|
if (parentField && FHIR_PRIMITIVES.has(parentField.type || '')) {
|
|
107
108
|
parts[extIdx - 1] = `_${parentSegment}`;
|
|
@@ -209,7 +210,11 @@ export function collectWithinSliceMaxChildren(slice, fields) {
|
|
|
209
210
|
.every((_, i) => isTraversableIntermediate(segments.slice(0, i + 1).join('.')));
|
|
210
211
|
if (!intermediatesOk)
|
|
211
212
|
continue;
|
|
212
|
-
const leaf = segments
|
|
213
|
+
const leaf = segments.at(-1);
|
|
214
|
+
// relChild is non-empty here, so split() always produced a leaf; the guard is
|
|
215
|
+
// what tells the compiler, and skipping is right if a path ever arrives empty.
|
|
216
|
+
if (!leaf)
|
|
217
|
+
continue;
|
|
213
218
|
const isChoiceType = leaf.endsWith('[x]');
|
|
214
219
|
const propPath = [...segments.slice(0, -1), isChoiceType ? leaf.replace(/\[x\]$/, '') : leaf];
|
|
215
220
|
result.push({ propPath, fhirPath: relChild, maxVal: f.max, isArray: !!f.isArray, isChoiceType });
|
|
@@ -25,7 +25,8 @@ export function instanceUrlForSlice(slice, fields) {
|
|
|
25
25
|
if (childId === sliceFullId + '.url' && typeof f.fixedValue === 'string')
|
|
26
26
|
return f.fixedValue;
|
|
27
27
|
}
|
|
28
|
-
|
|
28
|
+
const firstProfile = slice.profileUrls?.[0];
|
|
29
|
+
return firstProfile ? stripVersionFromCanonicalUrl(firstProfile) : undefined;
|
|
29
30
|
}
|
|
30
31
|
/**
|
|
31
32
|
* Build the walk down to the elements that *contain* a sub-extension slice.
|
|
@@ -54,7 +55,14 @@ export function buildSubExtensionScope(slice, fields) {
|
|
|
54
55
|
return null;
|
|
55
56
|
const hops = [];
|
|
56
57
|
for (let i = 0; i < containerSegments.length; i++) {
|
|
57
|
-
const
|
|
58
|
+
const segment = containerSegments[i];
|
|
59
|
+
if (segment === undefined)
|
|
60
|
+
return null;
|
|
61
|
+
const [prop, sliceName] = segment.split(':');
|
|
62
|
+
// split() on a non-empty segment always yields a first part; bailing keeps the
|
|
63
|
+
// "no unscoped walk" guarantee this function exists for.
|
|
64
|
+
if (!prop)
|
|
65
|
+
return null;
|
|
58
66
|
let url;
|
|
59
67
|
if (sliceName) {
|
|
60
68
|
const ancestorId = containerSegments.slice(0, i + 1).join('.');
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripVersionFromCanonicalUrl } from '../../core/utils.js';
|
|
1
2
|
import { buildNestedLoopCode, emitSystemPresenceLeaf, emitSystemOnlyLeaf, emitResolvedLeaf, } from './validatorBindingLeafEmitters.js';
|
|
2
3
|
/**
|
|
3
4
|
* Emit a typed read of `rel` off the resource.
|
|
@@ -106,7 +107,7 @@ function emitSystemPresenceFallback(output, field, rel, isNested, arrayFieldPath
|
|
|
106
107
|
const type = field.type || field.baseTypeCode;
|
|
107
108
|
if (type !== 'CodeableConcept' && type !== 'Coding' && type !== 'Quantity')
|
|
108
109
|
return;
|
|
109
|
-
const vsUri = (field.binding.uri || '')
|
|
110
|
+
const vsUri = stripVersionFromCanonicalUrl(field.binding.uri || '');
|
|
110
111
|
const vsDisplayName = vsUri.split('/').pop() || vsUri;
|
|
111
112
|
if (isNested) {
|
|
112
113
|
const code = buildNestedLoopCode(field, rel, arrayFieldPaths, (leafAcc, indent, fType) => emitSystemPresenceLeaf(leafAcc, indent, fType, vsDisplayName, vsUri, field.isArray), `Required binding system-presence fallback for ${rel} (nested, ValueSet unresolved)`, type);
|
|
@@ -139,7 +140,7 @@ function emitSystemOnlyBinding(output, field, rel, isNested, vsInfo, arrayFieldP
|
|
|
139
140
|
const type = field.type || field.baseTypeCode;
|
|
140
141
|
if (type !== 'CodeableConcept' && type !== 'Coding' && type !== 'Quantity')
|
|
141
142
|
return;
|
|
142
|
-
const sysVsUri = (field.binding.uri || '')
|
|
143
|
+
const sysVsUri = stripVersionFromCanonicalUrl(field.binding.uri || '');
|
|
143
144
|
const systemsLiteral = JSON.stringify(vsInfo.systems);
|
|
144
145
|
const vsDisplayName = vsInfo.displayName;
|
|
145
146
|
if (isNested) {
|
|
@@ -186,7 +187,7 @@ function emitResolvedBinding(output, field, rel, isNested, vsInfo, arrayFieldPat
|
|
|
186
187
|
const type = field.type || field.baseTypeCode;
|
|
187
188
|
const vsName = vsInfo.sanitizedName;
|
|
188
189
|
const vsDisplayName = vsInfo.displayName;
|
|
189
|
-
const vsUri = (field.binding.uri || '')
|
|
190
|
+
const vsUri = stripVersionFromCanonicalUrl(field.binding.uri || '');
|
|
190
191
|
if (isNested) {
|
|
191
192
|
const code = buildNestedLoopCode(field, rel, arrayFieldPaths, (leafAcc, indent, fType) => emitResolvedLeaf(leafAcc, indent, fType, vsName, vsDisplayName, vsUri, field.isArray), `Required ValueSet binding validation for ${rel} (nested)`, type);
|
|
192
193
|
if (code)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripSliceSuffix } from '../../core/utils.js';
|
|
1
2
|
/**
|
|
2
3
|
* Build nested loop code for iterating through array parents to reach a leaf field.
|
|
3
4
|
* Shared by all three binding modes (system-presence, system-only, resolved).
|
|
@@ -6,10 +7,10 @@ export function buildNestedLoopCode(field, rel, arrayFieldPaths, emitLeaf, comme
|
|
|
6
7
|
const parts = rel.split('.');
|
|
7
8
|
const leafField = parts[parts.length - 1];
|
|
8
9
|
const loopInfos = [];
|
|
9
|
-
for (
|
|
10
|
+
for (const [i, segment] of parts.slice(0, -1).entries()) {
|
|
10
11
|
const partialPath = parts.slice(0, i + 1).join('.');
|
|
11
|
-
const isArr = arrayFieldPaths.has(partialPath) || arrayFieldPaths.has(partialPath
|
|
12
|
-
loopInfos.push({ segment
|
|
12
|
+
const isArr = arrayFieldPaths.has(partialPath) || arrayFieldPaths.has(stripSliceSuffix(partialPath));
|
|
13
|
+
loopInfos.push({ segment, isArray: isArr });
|
|
13
14
|
}
|
|
14
15
|
// A nested path with no array ancestor is still reachable: the loop below already
|
|
15
16
|
// descends plain levels with an `if (… !== undefined)`. Requiring an array meant
|
|
@@ -21,8 +22,7 @@ export function buildNestedLoopCode(field, rel, arrayFieldPaths, emitLeaf, comme
|
|
|
21
22
|
let code = `\n // ${comment}`;
|
|
22
23
|
let indent = ' ';
|
|
23
24
|
let currentVar = 'resource';
|
|
24
|
-
for (
|
|
25
|
-
const li = loopInfos[i];
|
|
25
|
+
for (const [i, li] of loopInfos.entries()) {
|
|
26
26
|
// Every level goes through `at()`, including the first. Reading `resource.x`
|
|
27
27
|
// directly assumed the emitted interface declares `x`, which a logical model
|
|
28
28
|
// does not: SDCQuestionLibrary derives from Resource, so `resource.address`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stripSliceSuffix } from '../../core/utils.js';
|
|
1
2
|
/**
|
|
2
3
|
* Build required-field existence constraints.
|
|
3
4
|
* Emits FHIRPath-style constraints for fields with min >= 1.
|
|
@@ -158,7 +159,8 @@ export function buildForbiddenFieldConstraintEntries(fields) {
|
|
|
158
159
|
const eidRel = eid.replace(/^[^.]+\./, '');
|
|
159
160
|
const concreteParts = eidRel.split('.').map(p => {
|
|
160
161
|
if (p.includes(':')) {
|
|
161
|
-
const
|
|
162
|
+
const basePart = stripSliceSuffix(p);
|
|
163
|
+
const sliceName = p.substring(p.indexOf(':') + 1);
|
|
162
164
|
if (basePart.includes('[x]') && sliceName)
|
|
163
165
|
return sliceName;
|
|
164
166
|
return basePart;
|
|
@@ -216,7 +218,7 @@ export function buildPatternValidations(fields, arrayFieldPaths, fieldPathMap) {
|
|
|
216
218
|
if (!isNestedInArray) {
|
|
217
219
|
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
218
220
|
const parentPath = pathParts.slice(0, i + 1).join('.');
|
|
219
|
-
if (arrayFieldPaths.has(parentPath) || arrayFieldPaths.has(parentPath
|
|
221
|
+
if (arrayFieldPaths.has(parentPath) || arrayFieldPaths.has(stripSliceSuffix(parentPath))) {
|
|
220
222
|
isNestedInArray = true;
|
|
221
223
|
break;
|
|
222
224
|
}
|
|
@@ -224,7 +226,7 @@ export function buildPatternValidations(fields, arrayFieldPaths, fieldPathMap) {
|
|
|
224
226
|
}
|
|
225
227
|
const pattern = field.patternConstraint;
|
|
226
228
|
const parentFieldName = pathParts.length === 2 ? pathParts[0] : '';
|
|
227
|
-
const parentIsArray = parentFieldName && (arrayFieldPaths.has(parentFieldName) || arrayFieldPaths.has(parentFieldName
|
|
229
|
+
const parentIsArray = parentFieldName && (arrayFieldPaths.has(parentFieldName) || arrayFieldPaths.has(stripSliceSuffix(parentFieldName)));
|
|
228
230
|
const isNestedCodingPattern = pathParts.length === 2 && rel.endsWith('.coding')
|
|
229
231
|
&& parentIsArray
|
|
230
232
|
&& pattern && typeof pattern === 'object' && 'code' in pattern && !('coding' in pattern);
|
|
@@ -268,10 +270,10 @@ export function buildPatternValidations(fields, arrayFieldPaths, fieldPathMap) {
|
|
|
268
270
|
return patternValidations;
|
|
269
271
|
}
|
|
270
272
|
function emitNestedCodingPattern(output, fields, pathParts, parentFieldName, pattern, humanRel) {
|
|
271
|
-
const parentPath = pathParts[0];
|
|
272
|
-
const parentBasePath = parentPath
|
|
273
|
+
const parentPath = pathParts[0] ?? '';
|
|
274
|
+
const parentBasePath = stripSliceSuffix(parentPath);
|
|
273
275
|
const parentIsSliced = fields.some(f => f.sliceName &&
|
|
274
|
-
(f.name || '').replace(/^[^.]+\./, '')
|
|
276
|
+
stripSliceSuffix((f.name || '').replace(/^[^.]+\./, '')) === parentBasePath);
|
|
275
277
|
if (parentIsSliced)
|
|
276
278
|
return;
|
|
277
279
|
// Same split as the CodeableConcept case: identity here, pinned details below.
|
|
@@ -48,6 +48,50 @@ export function normalizeCollectionTypeFilters(expression) {
|
|
|
48
48
|
* They are dropped for nested field paths, and kept at a single level for
|
|
49
49
|
* recursive elements, rather than being walked through the whole recursion.
|
|
50
50
|
*/
|
|
51
|
+
/**
|
|
52
|
+
* Escape a constraint expression for embedding in a double-quoted emitted string,
|
|
53
|
+
* and scope it to its element.
|
|
54
|
+
*
|
|
55
|
+
* Shared by the validator and the Zod emitter. Both evaluate the same FHIRPath
|
|
56
|
+
* against the same resource, so a second copy of this scoping would make the two
|
|
57
|
+
* disagree on the same profile — which parity would report as a real mismatch.
|
|
58
|
+
*
|
|
59
|
+
* If the expression does not already navigate from `fieldPath`, it is wrapped in
|
|
60
|
+
* `<path>.all(...)`. Only the START of the expression is checked: built
|
|
61
|
+
* constraints (e.g. "action.exists()") begin with the fieldPath, while
|
|
62
|
+
* field-level constraints are relative to the element and start with child
|
|
63
|
+
* names. Matching the fieldPath anywhere would false-positive on recursive
|
|
64
|
+
* elements, where "resource.exists() != action.exists()" on fieldPath "action"
|
|
65
|
+
* mentions the name only as a child reference.
|
|
66
|
+
*
|
|
67
|
+
* Resource-root constraints (fieldPath === baseResourceType) need the wrapping
|
|
68
|
+
* too: FHIRPath `implies` yields empty when the antecedent is missing, and
|
|
69
|
+
* `.all()` converts that to false, which is what HL7 and Firely report.
|
|
70
|
+
*/
|
|
71
|
+
export function scopeConstraintExpression(expression, fieldPath, opts = {}) {
|
|
72
|
+
let escaped = normalizeCollectionTypeFilters(expression || '')
|
|
73
|
+
.replace(/\s*\n\s*/g, ' ')
|
|
74
|
+
.replace(/\\/g, '\\\\')
|
|
75
|
+
.replace(/"/g, '\\"');
|
|
76
|
+
if (!fieldPath)
|
|
77
|
+
return escaped;
|
|
78
|
+
const escapedPath = fieldPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
79
|
+
const navigationPattern = new RegExp(`^${escapedPath}\\s*\\.`);
|
|
80
|
+
const standalonePattern = new RegExp(`^${escapedPath}(?:[^.\\w]|$)`);
|
|
81
|
+
if (navigationPattern.test(escaped) || standalonePattern.test(escaped))
|
|
82
|
+
return escaped;
|
|
83
|
+
const resourceTypePattern = /^[A-Z][a-zA-Z]+\./;
|
|
84
|
+
if (resourceTypePattern.test(escaped))
|
|
85
|
+
return escaped;
|
|
86
|
+
const walkRecursion = !!opts.isRecursiveElement?.(fieldPath)
|
|
87
|
+
&& !isBaseElementInvariant(expression || '');
|
|
88
|
+
const navigation = opts.sliceFilter
|
|
89
|
+
? `${quoteFhirPathPath(fieldPath)}.where(${opts.sliceFilter})`
|
|
90
|
+
: quoteFhirPathPath(fieldPath);
|
|
91
|
+
const scope = walkRecursion ? `repeat(${navigation})` : navigation;
|
|
92
|
+
escaped = `${scope}.all(${escaped})`;
|
|
93
|
+
return escaped;
|
|
94
|
+
}
|
|
51
95
|
export function isBaseElementInvariant(expression) {
|
|
52
96
|
return /^hasValue\(\)\s+or\s+\(children\(\)\.count\(\)\s*>\s*id\.count\(\)\)$/.test(expression)
|
|
53
97
|
|| /^extension\.exists\(\)\s*!=\s*value\.exists\(\)$/.test(expression);
|