* 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)
| 285 | * Order is the CDDL choice order (the spec's priority), made explicit here. |
| 286 | */ |
| 287 | function unionSelector(name, types) { |
| 288 | const variants = types[name].variants |
| 289 | const constKeys = new Set() |
| 290 | for (const leaf of variants.flatMap((v) => unionLeaves(v, types))) |
| 291 | for (const f of types[leaf].fields) |
| 292 | if (discriminatorValue(types[leaf], f.name)?.value !== undefined) constKeys.add(f.name) |
| 293 | |
| 294 | for (const key of constKeys) { |
| 295 | const contributions = variants.map((v) => tagContribution(v, key, types)) |
| 296 | if (contributions.some((c) => c === null)) continue // some member can't be placed on this key |
| 297 | const tagged = contributions.flatMap((c) => c.tagged ?? []) |
| 298 | const defaults = contributions.filter((c) => c.default).map((c) => c.default) |
| 299 | if (defaults.length > 1 || tagged.length === 0) continue // ambiguous catch-all, or nothing to tag |
| 300 | const values = tagged.map((e) => JSON.stringify(e.value)) |
| 301 | if (new Set(values).size !== values.length) continue // values collide — not a clean tag |
| 302 | const selector = { by: key, variants: tagged } |
| 303 | if (defaults.length === 1) selector.default = defaults[0] |
| 304 | return selector |
| 305 | } |
| 306 | |
| 307 | // No shared discriminator: dispatch by required-field presence, in spec order. |
| 308 | // Resolve each variant through aliases/sub-unions to its leaf records (as the |
| 309 | // discriminator path does) and require the fields required in every leaf, so an |
| 310 | // alias-to-record variant is not left with an empty (always-matching) predicate. |
| 311 | const requiresOf = (ref) => { |
| 312 | const leaves = unionLeaves(ref, types).map( |
| 313 | (l) => new Set(types[l].fields.filter((f) => f.required).map((f) => f.name)), |
| 314 | ) |
| 315 | return leaves.length ? [...leaves[0]].filter((k) => leaves.every((s) => s.has(k))) : [] |
| 316 | } |
| 317 | return { ordered: variants.map((ref) => ({ ref, requires: requiresOf(ref) })) } |
| 318 | } |
| 319 | |
| 320 | // A structural selector can dispatch a payload only when every arm has a required |
| 321 | // field to test AND no arm's `requires` is a subset of a later arm's (which would |
no test coverage detected