* Derive how a wire payload selects one variant of a union, so every binding runs * the same dispatch instead of re-deriving it (and silently depending on emit * order). Two shapes: * { by, variants: [{ value, ref }], default? } — a discriminated union: look up * payload[by] among `variant
(name, types)
| 402 | * Order is the CDDL choice order (the spec's priority), made explicit here. |
| 403 | */ |
| 404 | function unionSelector(name, types) { |
| 405 | const variants = types[name].variants |
| 406 | const constKeys = new Set() |
| 407 | for (const leaf of variants.flatMap((v) => unionLeaves(v, types))) |
| 408 | for (const f of types[leaf].fields) |
| 409 | if (discriminatorValue(types[leaf], f.name)?.value !== undefined) constKeys.add(f.name) |
| 410 | |
| 411 | for (const key of constKeys) { |
| 412 | const contributions = variants.map((v) => tagContribution(v, key, types)) |
| 413 | if (contributions.some((c) => c === null)) continue // some member can't be placed on this key |
| 414 | const tagged = contributions.flatMap((c) => c.tagged ?? []) |
| 415 | const defaults = contributions.filter((c) => c.default).map((c) => c.default) |
| 416 | if (defaults.length > 1 || tagged.length === 0) continue // ambiguous catch-all, or nothing to tag |
| 417 | const values = tagged.map((e) => JSON.stringify(e.value)) |
| 418 | if (new Set(values).size !== values.length) continue // values collide — not a clean tag |
| 419 | const selector = { by: key, variants: tagged } |
| 420 | if (defaults.length === 1) selector.default = defaults[0] |
| 421 | return selector |
| 422 | } |
| 423 | |
| 424 | // No shared discriminator: dispatch by required-field presence, in spec order. |
| 425 | // Resolve each variant through aliases/sub-unions to its leaf records (as the |
| 426 | // discriminator path does) and require the fields required in every leaf, so an |
| 427 | // alias-to-record variant is not left with an empty (always-matching) predicate. |
| 428 | const requiresOf = (ref) => { |
| 429 | const leaves = unionLeaves(ref, types).map( |
| 430 | (l) => new Set(types[l].fields.filter((f) => f.required).map((f) => f.name)), |
| 431 | ) |
| 432 | return leaves.length ? [...leaves[0]].filter((k) => leaves.every((s) => s.has(k))) : [] |
| 433 | } |
| 434 | return { ordered: variants.map((ref) => ({ ref, requires: requiresOf(ref) })) } |
| 435 | } |
| 436 | |
| 437 | // A structural selector can dispatch a payload only when every arm has a required |
| 438 | // field to test AND no arm's `requires` is a subset of a later arm's (which would |
no test coverage detected