(schema)
| 430 | * @returns {string[]} One message per problem; empty when valid. |
| 431 | */ |
| 432 | export function checkSchema(schema) { |
| 433 | const errors = [] |
| 434 | const has = (name) => Object.hasOwn(schema.types, name) |
| 435 | const refsIn = (node) => |
| 436 | !node |
| 437 | ? [] |
| 438 | : node.ref |
| 439 | ? [node.ref] |
| 440 | : node.list |
| 441 | ? refsIn(node.list) |
| 442 | : node.map |
| 443 | ? refsIn(node.map) |
| 444 | : node.union |
| 445 | ? node.union.flatMap(refsIn) |
| 446 | : node.record |
| 447 | ? node.record.flatMap((f) => refsIn(f.type)) |
| 448 | : [] |
| 449 | const hasUnknown = (node) => |
| 450 | !node |
| 451 | ? false |
| 452 | : node.primitive === 'unknown' |
| 453 | ? true |
| 454 | : node.list |
| 455 | ? hasUnknown(node.list) |
| 456 | : node.map |
| 457 | ? hasUnknown(node.map) |
| 458 | : node.union |
| 459 | ? node.union.some(hasUnknown) |
| 460 | : node.record |
| 461 | ? node.record.some((f) => hasUnknown(f.type)) |
| 462 | : false |
| 463 | const hasEmptyInlineRecord = (node) => |
| 464 | !node |
| 465 | ? false |
| 466 | : Array.isArray(node.record) |
| 467 | ? node.record.length === 0 || node.record.some((f) => hasEmptyInlineRecord(f.type)) |
| 468 | : node.list |
| 469 | ? hasEmptyInlineRecord(node.list) |
| 470 | : node.map |
| 471 | ? hasEmptyInlineRecord(node.map) |
| 472 | : node.union |
| 473 | ? node.union.some(hasEmptyInlineRecord) |
| 474 | : false |
| 475 | const report = (where, node) => { |
| 476 | for (const r of refsIn(node)) if (!has(r)) errors.push(`${where}: unresolved type ${r}`) |
| 477 | if (hasUnknown(node)) errors.push(`${where}: projected to an unknown primitive (unhandled CDDL type)`) |
| 478 | if (hasEmptyInlineRecord(node)) errors.push(`${where}: projected an empty inline record (dropped type reference)`) |
| 479 | } |
| 480 | |
| 481 | for (const c of [...schema.commands, ...schema.events]) { |
| 482 | report(c.method, c.params) |
| 483 | report(c.method, c.result ?? null) |
| 484 | } |
| 485 | // A command/event whose envelope record carries real params must surface them — |
| 486 | // guards the model builder's gap where an inline `params: {...}` (vs a named ref) |
| 487 | // was dropped, leaving it parameterless while its type still required them. |
| 488 | const envelopeParams = commandEnvelopeParams(schema.types) |
| 489 | for (const c of [...schema.commands, ...schema.events]) { |
no test coverage detected