* Validate that every `+` (insert) op in `aChangeset` carries an * `author` attribute that resolves through `pool`. Callers that have * already rebased onto pad.pool pass the post-rebase changeset, so * we accept the pad's own pool here. * * Throws an Error if any insert op is missing
(aChangeset: string, pool: AttributePool)
| 121 | * path emits them at line boundaries. |
| 122 | */ |
| 123 | private static _assertInsertOpsCarryAuthor(aChangeset: string, pool: AttributePool) { |
| 124 | let unpacked; |
| 125 | try { |
| 126 | unpacked = unpack(aChangeset); |
| 127 | } catch (e: any) { |
| 128 | // unpack already throws a descriptive error; rethrow as-is so the |
| 129 | // caller's failure mode stays the same. |
| 130 | throw e; |
| 131 | } |
| 132 | for (const op of deserializeOps(unpacked.ops)) { |
| 133 | if (op.opcode !== '+') continue; |
| 134 | // Pure-newline inserts (e.g. `|1+1` for a single line break) are |
| 135 | // tolerated — the client's line assembler handles them regardless |
| 136 | // of attribs, and the API restoreRevision path emits them at |
| 137 | // line boundaries. |
| 138 | if (op.lines > 0 && op.chars === op.lines) continue; |
| 139 | if (!op.attribs) { |
| 140 | throw new Error( |
| 141 | 'insert op without an author attribute ' + |
| 142 | `(empty attribs): ${aChangeset}`); |
| 143 | } |
| 144 | let authorIdSeen: string | undefined; |
| 145 | try { |
| 146 | authorIdSeen = AttributeMap.fromString(op.attribs, pool).get('author'); |
| 147 | } catch (e: any) { |
| 148 | throw new Error( |
| 149 | 'insert op references an attribute number ' + |
| 150 | `not present in the pool: ${aChangeset} (${e && e.message || e})`); |
| 151 | } |
| 152 | if (!authorIdSeen) { |
| 153 | throw new Error( |
| 154 | 'insert op without an author attribute: ' + aChangeset); |
| 155 | } |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | private db: Database; |
| 160 | private atext: AText; |
no test coverage detected