Merge pull request #75 from larvit/receipt-field-separators

Separate a receipt's fields on any whitespace, not only a space
This commit is contained in:
2026-09-01 22:15:04 +02:00
committed by GitHub
3 changed files with 19 additions and 14 deletions
+7 -5
View File
@@ -56,21 +56,23 @@ export type Dlr = {
statusMsg: MessageState; statusMsg: MessageState;
}; };
const field = (name: string) => new RegExp(`\\b${name}:([^ ]*)`, 'i'); const field = (name: string) => new RegExp(`\\b${name}:(\\S*)`, 'i');
const patterns = { const patterns = {
dlvrd: field('dlvrd'), dlvrd: field('dlvrd'),
doneDate: /\bdone date:([^ ]*)/i, doneDate: field('done date'),
err: field('err'), err: field('err'),
id: field('id'), id: field('id'),
stat: field('stat'), stat: field('stat'),
sub: field('sub'), sub: field('sub'),
submitDate: /\bsubmit date:([^ ]*)/i, submitDate: field('submit date'),
text: /\btext:(.*)$/i, // The one field that may hold a space, carrying the message's own start, so it ends at its line.
text: /\btext:([^\r\n]*)/i,
}; };
function toNumber(value: string | undefined): number | undefined { function toNumber(value: string | undefined): number | undefined {
if (value === undefined) return undefined; // Number('') is 0, which would report a receipt that stated no count as one that stated none sent.
if (value === undefined || value === '') return undefined;
const parsed = Number(value); const parsed = Number(value);
+12 -4
View File
@@ -5,7 +5,7 @@ import { dlrFromPdu, parseReceipt, receiptCodes } from '../src/dlr.ts';
import { objToPdu, pduToObj } from '../src/pdu.ts'; import { objToPdu, pduToObj } from '../src/pdu.ts';
import type { PduObject, TlvInput } from '../src/pdu.ts'; import type { PduObject, TlvInput } from '../src/pdu.ts';
const receiptText = 'id:0195f0c7 sub:001 dlvrd:001 submit date:2508251430 done date:2508251431 stat:DELIVRD err:000 text:hello'; const receiptText = 'id:0195f0c7 sub:001 dlvrd:001 submit date:2508251430 done date:2508251431 stat:DELIVRD err:000 text:hello there';
function deliverSm( function deliverSm(
message: Buffer | string, message: Buffer | string,
@@ -44,7 +44,14 @@ describe('parseReceipt()', () => {
assert.equal(receipt.doneDate, '2508251431'); assert.equal(receipt.doneDate, '2508251431');
assert.equal(receipt.stat, 'DELIVRD'); assert.equal(receipt.stat, 'DELIVRD');
assert.equal(receipt.err, '000'); assert.equal(receipt.err, '000');
assert.equal(receipt.text, 'hello'); assert.equal(receipt.text, 'hello there');
});
test('takes any whitespace between the fields, not only a space', () => {
const wrapped = 'id:0195f0c7\r\nsub:001\ndlvrd:001\tsubmit date:2508251430\r\ndone date:2508251431'
+ '\r\nstat:DELIVRD\r\nerr:000\r\ntext:hello there\r\n';
assert.deepEqual(parseReceipt(wrapped), parseReceipt(receiptText));
}); });
test('does not confuse "done date" with "submit date"', () => { test('does not confuse "done date" with "submit date"', () => {
@@ -54,7 +61,7 @@ describe('parseReceipt()', () => {
}); });
test('leaves absent fields undefined rather than guessing', () => { test('leaves absent fields undefined rather than guessing', () => {
const receipt = parseReceipt('id:abc stat:UNDELIV'); const receipt = parseReceipt('id:abc sub: stat:UNDELIV');
assert.equal(receipt.id, 'abc'); assert.equal(receipt.id, 'abc');
assert.equal(receipt.stat, 'UNDELIV'); assert.equal(receipt.stat, 'UNDELIV');
@@ -87,6 +94,7 @@ describe('dlrFromPdu()', () => {
assert.equal(dlr.statusId, 2); assert.equal(dlr.statusId, 2);
assert.equal(dlr.errorCode, '000'); assert.equal(dlr.errorCode, '000');
assert.equal(dlr.doneDate?.toISOString(), '2025-08-25T14:31:00.000Z'); assert.equal(dlr.doneDate?.toISOString(), '2025-08-25T14:31:00.000Z');
assert.equal(dlrFromPdu(deliverSm('id:beef-1\r\nstat:DELIVRD'))?.smsId, 'beef-1');
}); });
test('maps every spec status code back to its message state and id', () => { test('maps every spec status code back to its message state and id', () => {
@@ -204,7 +212,7 @@ describe('dlrFromPdu()', () => {
assert.ok(dlr?.receipt); assert.ok(dlr?.receipt);
assert.equal(dlr.receipt.sub, 1); assert.equal(dlr.receipt.sub, 1);
assert.equal(dlr.receipt.text, 'hello'); assert.equal(dlr.receipt.text, 'hello there');
}); });
test('reads the id in the notation the peer writes receipts in', () => { test('reads the id in the notation the peer writes receipts in', () => {
-5
View File
@@ -170,11 +170,6 @@ session message is a change to every call site.
- [ ] **Coverage reporting.** `node --test --experimental-test-coverage` works today; nothing - [ ] **Coverage reporting.** `node --test --experimental-test-coverage` works today; nothing
publishes the numbers. publishes the numbers.
- [ ] **A receipt whose fields are separated by anything but a space reads as one field.**
`parseReceipt()` takes `id:` as everything up to the next space, so a peer writing CRLF
between fields yields an id of `1a2b\r\nstat:DELIVRD` — one nothing correlates and no
notation can read. Every field pattern has the same shape. Raised by review, 2026-08-30.
- [ ] **An `onReceipt` hook.** Receipt text is only loosely specified and operators disagree on it, - [ ] **An `onReceipt` hook.** Receipt text is only loosely specified and operators disagree on it,
but `dlrFromPdu()` is wired into `IncomingRequests` with no seam of its own: an application but `dlrFromPdu()` is wired into `IncomingRequests` with no seam of its own: an application
facing a format we do not parse has to take the whole PDU on `onRequest` and reimplement the facing a format we do not parse has to take the whole PDU on `onRequest` and reimplement the