babelfhir-ts 1.5.19 → 1.5.21
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/out/src/cli/updateCommand.js +19 -12
- package/out/src/generator/core/constants.js +19 -0
- package/out/src/generator/core/extensionSdReader.js +99 -0
- package/out/src/generator/core/utils.js +97 -3
- package/out/src/generator/emitters/class/classTemplate.js +8 -2
- package/out/src/generator/emitters/class/sliceElementDefaults.js +5 -3
- package/out/src/generator/emitters/client/clientGenerator.js +22 -6
- package/out/src/generator/emitters/client/smartAuthGenerator.js +4 -1
- package/out/src/generator/emitters/interface/backboneSliceTyping.js +15 -4
- package/out/src/generator/emitters/interface/importManager.js +28 -2
- package/out/src/generator/emitters/interface/interfaceFieldProcessor.js +21 -16
- package/out/src/generator/emitters/interface/interfaceFieldUtils.js +16 -0
- package/out/src/generator/emitters/interface/postProcessExtensions.js +1 -0
- package/out/src/generator/emitters/validator/closedSlicingValidation.js +146 -0
- package/out/src/generator/emitters/validator/sliceDelegation.js +450 -0
- package/out/src/generator/emitters/validator/sliceExtensionValidation.js +200 -35
- package/out/src/generator/emitters/validator/sliceValidatorGenerator.js +130 -299
- package/out/src/generator/emitters/validator/sliceValidatorUtils.js +89 -16
- package/out/src/generator/emitters/validator/subExtensionScope.js +100 -0
- package/out/src/generator/emitters/validator/validatorBindingBuilder.js +113 -237
- package/out/src/generator/emitters/validator/validatorBindingLeafEmitters.js +13 -4
- package/out/src/generator/emitters/validator/validatorConstraintBuilders.js +104 -63
- package/out/src/generator/emitters/validator/validatorExpressions.js +54 -0
- package/out/src/generator/emitters/validator/validatorFieldBuilders.js +492 -46
- package/out/src/generator/emitters/validator/validatorGenerator.js +170 -169
- package/out/src/generator/emitters/validator/validatorRuntime.js +279 -0
- package/out/src/generator/emitters/validator/validatorTemplates.js +186 -54
- package/out/src/generator/emitters/zod/zodRefinementBuilder.js +29 -19
- package/out/src/generator/emitters/zod/zodSchemaGenerator.js +16 -1
- package/out/src/generator/index.js +21 -11
- package/out/src/generator/parser/packageParser.js +1 -8
- package/out/src/generator/parser/sdParser.js +1 -0
- package/out/src/generator/sdProcessor.js +303 -30
- package/out/src/generator/sdProcessorHelpers.js +1 -1
- package/out/src/generator/sdProcessorRequiredFields.js +120 -0
- package/out/src/main.js +37 -11
- package/package.json +3 -1
- package/parity-matrix.json +18 -18
|
@@ -2,6 +2,20 @@
|
|
|
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';
|
|
6
|
+
import { buildSubExtensionScope, renderScopeHops } from './subExtensionScope.js';
|
|
7
|
+
/**
|
|
8
|
+
* Read pre-resolved Extension SD metadata for a canonical.
|
|
9
|
+
*
|
|
10
|
+
* The map is keyed by the version-stripped canonical because a slice names its
|
|
11
|
+
* extension in two spellings — `type.profile` with a `|x.y.z` suffix, the pinned
|
|
12
|
+
* `url` child without one — and either may reach this lookup. Reading with the
|
|
13
|
+
* raw url missed every versioned entry, and a miss is indistinguishable from an
|
|
14
|
+
* unreadable SD, so the emitter silently skipped the slice.
|
|
15
|
+
*/
|
|
16
|
+
export function lookupExtensionMetadata(metadata, extensionUrl) {
|
|
17
|
+
return metadata?.get(stripVersionFromCanonicalUrl(extensionUrl));
|
|
18
|
+
}
|
|
5
19
|
export function generateValueSetValidation(slice, baseName, relPath, errorPath, sliceLabel, varName, min, minText, getValueSetForBinding, valueSetImports, fields, out) {
|
|
6
20
|
const vsInfo = getValueSetForBinding(slice.binding.uri);
|
|
7
21
|
const vsInfoFull = vsInfo && !('systemOnly' in vsInfo && vsInfo.systemOnly) ? vsInfo : undefined;
|
|
@@ -216,17 +230,12 @@ export function generateExistsExtensionValidation(slice, relPath, errorPath, sli
|
|
|
216
230
|
const remainingAccessor = remaining.includes('.') ? remaining.split('.').join('?.') : remaining;
|
|
217
231
|
out.push(`
|
|
218
232
|
// Required slice validation for ${relPath}:${sliceLabel} (exists extension discriminator, nested in array)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
);
|
|
226
|
-
if (${varName}Matched.length < ${min}) {
|
|
227
|
-
errors.push("No elements matched required slice: '${errorPath}:${sliceLabel}'");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
233
|
+
for (const _el of arr(${safeAccessor(arrayParent)})) {
|
|
234
|
+
const ${varName}Matched = elements(at(_el, ${remainingAccessor.split('?.').map(s => `'${s}'`).join(', ')})).filter((item) =>
|
|
235
|
+
elements(at(item, 'extension')).some((ext) => ext.url === "${extensionUrl}")
|
|
236
|
+
);
|
|
237
|
+
if (${varName}Matched.length < ${min}) {
|
|
238
|
+
errors.push("No elements matched required slice: '${errorPath}:${sliceLabel}'");
|
|
230
239
|
}
|
|
231
240
|
}`);
|
|
232
241
|
return;
|
|
@@ -278,12 +287,17 @@ export function detectExtensionUrlSlice(slice, relPath, fields) {
|
|
|
278
287
|
}
|
|
279
288
|
export function generateExtensionUrlSliceValidation(slice, relPath, errorPath, sliceLabel, varName, min, extensionUrl, fieldPathMap, arrayFieldPaths, out, ctx) {
|
|
280
289
|
const baseFieldIsArray = isArrayPath(relPath, fieldPathMap, arrayFieldPaths);
|
|
281
|
-
const extMeta = ctx?.extensionMetadata
|
|
290
|
+
const extMeta = lookupExtensionMetadata(ctx?.extensionMetadata, extensionUrl);
|
|
282
291
|
// Narrow extension metadata value types with profile-level constraints.
|
|
283
292
|
// The profile differential may restrict value[x] types more strictly than
|
|
284
293
|
// the extension SD (e.g., extension allows [boolean, url] but profile allows only [url]).
|
|
294
|
+
//
|
|
295
|
+
// An empty type list is not a list to narrow: it means the extension is complex and
|
|
296
|
+
// forbids a value outright. The profile still carries the inherited value[x] element
|
|
297
|
+
// with its full base type list, so narrowing would read that as "any of ~50 types
|
|
298
|
+
// allowed" and cancel the prohibition.
|
|
285
299
|
let effectiveMeta = extMeta;
|
|
286
|
-
if (extMeta && ctx?.fields && slice.elementId) {
|
|
300
|
+
if (extMeta && extMeta.valueTypes.length > 0 && ctx?.fields && slice.elementId) {
|
|
287
301
|
const sliceElemId = slice.elementId.replace(/^[^.]+\./, '');
|
|
288
302
|
const valueChild = ctx.fields.find(f => {
|
|
289
303
|
if (!f.elementId || !f.typeOptions?.length)
|
|
@@ -296,22 +310,55 @@ export function generateExtensionUrlSliceValidation(slice, relPath, errorPath, s
|
|
|
296
310
|
effectiveMeta = { ...extMeta, valueTypes: profileTypes };
|
|
297
311
|
}
|
|
298
312
|
}
|
|
313
|
+
// An optional slice asserts no count, but a present instance must still conform.
|
|
314
|
+
// Omitting the assertion rather than emitting `length < 0` keeps a branch that can
|
|
315
|
+
// never fire out of the generated output.
|
|
316
|
+
const countAssertion = min > 0
|
|
317
|
+
? `
|
|
318
|
+
if (${varName}Matches.length < ${min}) {
|
|
319
|
+
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
320
|
+
}`
|
|
321
|
+
: '';
|
|
322
|
+
const headline = min > 0 ? 'Required' : 'Optional';
|
|
323
|
+
// With no count to assert and no resolved metadata there is nothing to check, and
|
|
324
|
+
// the block would filter a list and discard it. That happens whenever the
|
|
325
|
+
// extension SD could not be read, so emit only when the slice earns it.
|
|
326
|
+
if (min === 0 && !generateExtensionValueChecks('_probe', extensionUrl, effectiveMeta, ctx, '')) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// A sub-extension of another extension slice needs the parent walked and filtered
|
|
330
|
+
// first; the generic paths below address it from the resource root and cannot.
|
|
331
|
+
const subScope = ctx?.fields
|
|
332
|
+
? buildSubExtensionScope(slice, ctx.fields)
|
|
333
|
+
: null;
|
|
334
|
+
if (subScope) {
|
|
335
|
+
const scopedElemId = (slice.elementId || '').replace(/^[^.]+\./, '');
|
|
336
|
+
const scopedErrorPath = scopedElemId || `${errorPath}:${sliceLabel}`;
|
|
337
|
+
// The slot the slice lives in: its own last segment, minus the slice qualifier.
|
|
338
|
+
const leafProp = scopedElemId.split('.').pop().split(':')[0];
|
|
339
|
+
const body = renderScopeHops(subScope, (container, indent) => {
|
|
340
|
+
const matches = `${varName}Matches`;
|
|
341
|
+
let inner = `\n${indent}const ${matches} = elements(at(${container}, '${leafProp}')).filter((item) => item.url === "${extensionUrl}");`;
|
|
342
|
+
if (min > 0) {
|
|
343
|
+
inner += `\n${indent}if (${matches}.length < ${min}) {`;
|
|
344
|
+
inner += `\n${indent} errors.push("Slice '${scopedErrorPath}': a matching slice is required, but not found");`;
|
|
345
|
+
inner += `\n${indent}}`;
|
|
346
|
+
}
|
|
347
|
+
inner += generateExtensionValueChecks(matches, extensionUrl, effectiveMeta, ctx, indent);
|
|
348
|
+
return inner;
|
|
349
|
+
});
|
|
350
|
+
out.push(`
|
|
351
|
+
// ${headline} extension slice validation for ${scopedErrorPath} (sub-extension, scoped to its parent slice)${body}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
299
354
|
const nested = findArrayAncestor(relPath, fieldPathMap, arrayFieldPaths);
|
|
300
355
|
if (nested) {
|
|
301
356
|
const { arrayParent, remaining } = nested;
|
|
302
357
|
const remainingAccessor = remaining.includes('.') ? remaining.split('.').join('?.') : remaining;
|
|
303
358
|
out.push(`
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const _extArr = elements(at(_el, ${remainingAccessor.split('?.').map(s => `'${s}'`).join(', ')}));
|
|
308
|
-
if (Array.isArray(_extArr)) {
|
|
309
|
-
const ${varName}Matches = _extArr.filter((item) => item.url === "${extensionUrl}");
|
|
310
|
-
if (${varName}Matches.length < ${min}) {
|
|
311
|
-
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
312
|
-
}${generateExtensionValueChecks(varName + 'Matches', extensionUrl, effectiveMeta, ctx, ' ')}
|
|
313
|
-
}
|
|
314
|
-
}
|
|
359
|
+
// ${headline} extension slice validation for ${relPath}:${sliceLabel} (url discriminator, nested in array)
|
|
360
|
+
for (const _el of arr(${safeAccessor(arrayParent)})) {
|
|
361
|
+
const ${varName}Matches = elements(at(_el, ${remainingAccessor.split('?.').map(s => `'${s}'`).join(', ')})).filter((item) => item.url === "${extensionUrl}");${countAssertion}${generateExtensionValueChecks(varName + 'Matches', extensionUrl, effectiveMeta, ctx, ' ')}
|
|
315
362
|
}`);
|
|
316
363
|
return;
|
|
317
364
|
}
|
|
@@ -319,41 +366,159 @@ export function generateExtensionUrlSliceValidation(slice, relPath, errorPath, s
|
|
|
319
366
|
const pathParts = relPath.split('.');
|
|
320
367
|
const parentPath = pathParts.length > 1 ? pathParts.slice(0, -1).join('.') : null;
|
|
321
368
|
const parentGuard = parentPath ? `${safeAccessor(parentPath)} && ` : '';
|
|
322
|
-
|
|
369
|
+
// The absent branch asserts the slice is missing. For min=0 that is simply false,
|
|
370
|
+
// and on an empty resource the element is never an array, so it fired every time:
|
|
371
|
+
// this is what cost empty parity 613/613 -> 599/613 across mcode, ndh, pas and
|
|
372
|
+
// physical-activity. Two earlier attempts patched only the nested path above and
|
|
373
|
+
// never reached this one.
|
|
374
|
+
const absentBranch = min > 0
|
|
375
|
+
? ` ${parentPath ? `else if (${safeAccessor(parentPath)})` : 'else'} {
|
|
376
|
+
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
377
|
+
}`
|
|
378
|
+
: '';
|
|
379
|
+
const arrayCountAssertion = min > 0
|
|
380
|
+
? `
|
|
381
|
+
if (${varName}Elements.length < ${min}) {
|
|
382
|
+
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
383
|
+
}`
|
|
384
|
+
: '';
|
|
323
385
|
out.push(`
|
|
324
|
-
//
|
|
386
|
+
// ${headline} extension slice validation for ${relPath}:${sliceLabel} (url discriminator)
|
|
325
387
|
if (${parentGuard}Array.isArray(${safeAccessor(relPath)})) {
|
|
326
388
|
const ${varName}Elements = ${safeList(relPath)}.filter((item) =>
|
|
327
389
|
item.url === "${extensionUrl}"
|
|
328
|
-
)
|
|
329
|
-
|
|
330
|
-
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
331
|
-
}${generateExtensionValueChecks(varName + 'Elements', extensionUrl, effectiveMeta, ctx, ' ')}
|
|
332
|
-
} ${elseGuard} {
|
|
333
|
-
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
334
|
-
}`);
|
|
390
|
+
);${arrayCountAssertion}${generateExtensionValueChecks(varName + 'Elements', extensionUrl, effectiveMeta, ctx, ' ')}
|
|
391
|
+
}${absentBranch}`);
|
|
335
392
|
}
|
|
336
|
-
else {
|
|
393
|
+
else if (min > 0) {
|
|
337
394
|
out.push(`
|
|
338
395
|
// Required extension slice validation for ${relPath}:${sliceLabel} (url discriminator)
|
|
339
396
|
if (!${safeAccessor(relPath)} || (${safeAccessor(relPath)} as { url?: string }).url !== "${extensionUrl}") {
|
|
340
397
|
errors.push("Slice '${errorPath}:${sliceLabel}': a matching slice is required, but not found");
|
|
341
398
|
}`);
|
|
342
399
|
}
|
|
400
|
+
// A non-array optional slice has nothing to assert: its only check was that the
|
|
401
|
+
// slice is present, which min=0 does not require.
|
|
343
402
|
}
|
|
344
403
|
// ── Extension value type + binding validation helper ─────────────────────
|
|
345
404
|
/** Map FHIR type code → JSON property name suffix (e.g., 'code' → 'Code'). */
|
|
346
405
|
function valuePropertySuffix(typeCode) {
|
|
347
406
|
return typeCode.charAt(0).toUpperCase() + typeCode.slice(1);
|
|
348
407
|
}
|
|
408
|
+
/** A ValueSet resolved to an emitted validator function, or nothing to check against. */
|
|
409
|
+
function resolveBindingValueSet(binding, ctx) {
|
|
410
|
+
if (!binding || !ctx)
|
|
411
|
+
return undefined;
|
|
412
|
+
if (binding.strength !== 'required' && binding.strength !== 'extensible')
|
|
413
|
+
return undefined;
|
|
414
|
+
const vsInfo = ctx.getValueSetForBinding(binding.uri);
|
|
415
|
+
// A system-only ValueSet cannot decide membership of a code, only of its system.
|
|
416
|
+
if (!vsInfo || ('systemOnly' in vsInfo && vsInfo.systemOnly))
|
|
417
|
+
return undefined;
|
|
418
|
+
const full = vsInfo;
|
|
419
|
+
ctx.valueSetImports.set(full.sanitizedName, `./valuesets/ValueSet-${full.sanitizedName}.js`);
|
|
420
|
+
return full;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Check a sub-extension's value against its bound ValueSet.
|
|
424
|
+
*
|
|
425
|
+
* `elemVar` is `unknown`, so the value is reached through `at()` and only the shape
|
|
426
|
+
* actually read is asserted. Codings are the case KBV needs — the Payor extension
|
|
427
|
+
* binds `Extension.extension:Kostentraegertyp.value[x]:valueCoding` required — and
|
|
428
|
+
* primitives and CodeableConcepts are handled alongside so one helper covers the
|
|
429
|
+
* shapes an extension value can take.
|
|
430
|
+
*/
|
|
431
|
+
function subExtensionBindingCheck(sub, vs, elemVar, indent) {
|
|
432
|
+
const lines = [];
|
|
433
|
+
const notFound = (valueExpr) => `errors.push("The value provided ('" + ${valueExpr} + "') was not found in the value set '${vs.displayName}' (${sub.binding.uri})");`;
|
|
434
|
+
if (sub.valueTypes.includes('Coding')) {
|
|
435
|
+
lines.push(`${indent}const _seCode = at(${elemVar}, 'valueCoding', 'code');`);
|
|
436
|
+
lines.push(`${indent}if (typeof _seCode === 'string' && !${vs.validatorFn}(_seCode)) {`);
|
|
437
|
+
lines.push(`${indent} ${notFound('_seCode')}`);
|
|
438
|
+
lines.push(`${indent}}`);
|
|
439
|
+
}
|
|
440
|
+
else if (sub.valueTypes.includes('CodeableConcept')) {
|
|
441
|
+
lines.push(`${indent}const _seCodings = codings(at(${elemVar}, 'valueCodeableConcept'));`);
|
|
442
|
+
lines.push(`${indent}if (_seCodings.length > 0 && !_seCodings.some((c) => !!c.code && ${vs.validatorFn}(c.code))) {`);
|
|
443
|
+
lines.push(`${indent} errors.push("None of the codings in the extension value are from the value set '${vs.displayName}' (${sub.binding.uri})");`);
|
|
444
|
+
lines.push(`${indent}}`);
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
// Primitive: whichever value* property is present carries the code.
|
|
448
|
+
lines.push(`${indent}const _seEntry = Object.entries(node(${elemVar})).find(([k]) => k.startsWith("value"));`);
|
|
449
|
+
lines.push(`${indent}if (_seEntry && typeof _seEntry[1] === 'string' && !${vs.validatorFn}(_seEntry[1])) {`);
|
|
450
|
+
lines.push(`${indent} ${notFound('_seEntry[1]')}`);
|
|
451
|
+
lines.push(`${indent}}`);
|
|
452
|
+
}
|
|
453
|
+
return lines.join('\n');
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Checks on the sub-extensions of a matched complex extension.
|
|
457
|
+
*
|
|
458
|
+
* `_ext` is the matched member; its own `extension` array holds the sub-extensions.
|
|
459
|
+
* Emits the cardinality on `Extension.extension`, the presence of each required
|
|
460
|
+
* slice, and the bound ValueSet of each slice that declares one — all in HL7's
|
|
461
|
+
* wording so parity attributes them to the same fields.
|
|
462
|
+
*/
|
|
463
|
+
function generateSubExtensionChecks(extMeta, ctx, indent) {
|
|
464
|
+
const subs = extMeta.subExtensions ?? [];
|
|
465
|
+
const min = extMeta.subExtensionMin ?? 0;
|
|
466
|
+
const lines = [];
|
|
467
|
+
if (min > 0) {
|
|
468
|
+
lines.push(`${indent}const _seAll = elements(at(_ext, 'extension'));`);
|
|
469
|
+
lines.push(`${indent}if (_seAll.length < ${min}) {`);
|
|
470
|
+
lines.push(`${indent} errors.push("Extension.extension: minimum required = ${min}, but only found " + _seAll.length);`);
|
|
471
|
+
lines.push(`${indent}}`);
|
|
472
|
+
}
|
|
473
|
+
for (const sub of subs) {
|
|
474
|
+
const needsPresence = sub.min > 0;
|
|
475
|
+
const vs = resolveBindingValueSet(sub.binding, ctx);
|
|
476
|
+
if (!needsPresence && !vs)
|
|
477
|
+
continue;
|
|
478
|
+
const varName = `_se_${sub.sliceName.replace(/[^a-zA-Z0-9_]/g, '_')}`;
|
|
479
|
+
lines.push(`${indent}const ${varName} = elements(at(_ext, 'extension')).filter((e) => e.url === "${sub.url}");`);
|
|
480
|
+
if (needsPresence) {
|
|
481
|
+
lines.push(`${indent}if (${varName}.length < ${sub.min}) {`);
|
|
482
|
+
lines.push(`${indent} errors.push("Slice 'Extension.extension:${sub.sliceName}': a matching slice is required, but not found");`);
|
|
483
|
+
lines.push(`${indent}}`);
|
|
484
|
+
}
|
|
485
|
+
if (vs) {
|
|
486
|
+
lines.push(`${indent}for (const _seElem of ${varName}) {`);
|
|
487
|
+
lines.push(subExtensionBindingCheck(sub, vs, '_seElem', indent + ' '));
|
|
488
|
+
lines.push(`${indent}}`);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return lines.length ? '\n' + lines.join('\n') : '';
|
|
492
|
+
}
|
|
349
493
|
/**
|
|
350
494
|
* Generate inline JS code that validates extension value type and binding
|
|
351
495
|
* for each matched extension in the given array variable.
|
|
352
496
|
* Returns an empty string when no metadata is available.
|
|
353
497
|
*/
|
|
354
498
|
function generateExtensionValueChecks(matchedArrayVar, extensionUrl, extMeta, ctx, indent) {
|
|
355
|
-
|
|
499
|
+
// No entry means the extension SD could not be read, so nothing can be concluded.
|
|
500
|
+
if (!extMeta)
|
|
356
501
|
return '';
|
|
502
|
+
// An entry with no permitted types means the opposite of unknown: the extension
|
|
503
|
+
// is complex, pins value[x] to max=0, and carries sub-extensions instead. A value
|
|
504
|
+
// of any type is forbidden, and since no single property can be named — every
|
|
505
|
+
// `value*` variant is disallowed — the check is on the presence of any of them.
|
|
506
|
+
if (extMeta.valueTypes.length === 0) {
|
|
507
|
+
const forbidUrl = ctx?.profileUrl || extensionUrl;
|
|
508
|
+
// HL7 reports one defect under two headings — the cardinality breach and the
|
|
509
|
+
// empty allowed-type list — and parity classifies them into different fields
|
|
510
|
+
// ('value' and 'constraint'), so emitting only the first left `constraint`
|
|
511
|
+
// showing as HL7-only on a finding we had already detected.
|
|
512
|
+
return `
|
|
513
|
+
${indent}// Complex extension: value[x] is max=0, so no value* property is allowed
|
|
514
|
+
${indent}for (const _ext of ${matchedArrayVar}) {
|
|
515
|
+
${indent} const _forbidden = Object.keys(node(_ext)).find((k) => k.startsWith("value"));
|
|
516
|
+
${indent} if (_forbidden) {
|
|
517
|
+
${indent} errors.push("Extension.value[x]: max allowed = 0, but found 1 (${forbidUrl})");
|
|
518
|
+
${indent} errors.push("The Extension '${extensionUrl}' definition allows for the types [] but found type " + _forbidden.slice(5));
|
|
519
|
+
${indent} }${generateSubExtensionChecks(extMeta, ctx, indent + ' ')}
|
|
520
|
+
${indent}}`;
|
|
521
|
+
}
|
|
357
522
|
const lines = [];
|
|
358
523
|
const allowedProps = extMeta.valueTypes.map(t => `"value${valuePropertySuffix(t)}"`);
|
|
359
524
|
const allowedSet = `[${allowedProps.join(', ')}]`;
|