Handle warnings and fail fast
A lenient parse returns a message and a list of warnings. Each warning has a stable code, a
position (the record, field and component it concerns) and a message that is a fixed sentence.
None of them carries a value from the stream. This page shows what to do with them.
Branch on a code
Compare warning.code with the registry constants. A code's name is part of the public contract, so
the branch will not drift:
import { parseAstmRecords, WARNING_CODES } from "@cosyte/astm";
const { warnings } = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6||||F\rL|1\r");
const needsReview = warnings.some((w) => w.code === WARNING_CODES.ASTM_RECORD_UNITS_ABSENT);
needsReview; // => true
warnings[0]?.position; // => { recordIndex: 1, recordType: "R", fieldIndex: 5 }
The record layer's codes are in WARNING_CODES; the frame decoder's in FRAME_WARNING_CODES; the
transport's in LTP_WARNING_CODES; and the LOINC mapping's in LIVD_WARNING_CODES. Warning and
error codes lists every one.
Two codes deserve a branch of their own on any real feed: ASTM_RECORD_UNKNOWN_TYPE, which can mean
two messages merged (Read a stream that carries several
messages),
and ASTM_RECORD_FIELDS_UNSEPARATED, which means a record lost its fields (How delimiters are read
and written).
Log the warning, not the record
A warning is safe to log as it is:
import { parseAstmRecords } from "@cosyte/astm";
const { warnings } = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6||||F\rL|1\r");
const lines = warnings.map((w) => `${w.code} ${w.message} ${JSON.stringify(w.position)}`);
lines[0]?.includes("28.6"); // => false
The record itself is not: a parsed message keeps every field as it arrived. What it does with patient data explains the difference.
Refuse every deviation with strict mode
At an integration boundary you may want a deviation to stop the message instead. Parse with
{ strict: true }, and every warning becomes a thrown AstmStrictError that carries them all:
import { AstmStrictError, parseAstmRecords } from "@cosyte/astm";
let refused: readonly string[] = [];
try {
parseAstmRecords("H|\\^&\rR|1|^^^687|28.6||||F\rL|1\r", { strict: true });
} catch (err) {
if (err instanceof AstmStrictError) refused = err.warnings.map((w) => w.code);
}
refused; // => ["ASTM_RECORD_UNITS_ABSENT"]
The frame decoder takes the same option and throws AstmFrameStrictError. A warning a vendor
profile expects is re-badged PROFILE_QUIRK_APPLIED and does not stop a strict parse, but no profile
can do that for a code that could cost you a value: Write a vendor
profile explains the safety gate. Ask
isSafetyCriticalCode(code) when you need the answer for one code.
Catch what always throws
Three conditions throw even in lenient mode, because there is nothing to parse into a message. Each
is an AstmParseError with a code from FATAL_CODES:
import { AstmParseError, parseAstmRecords } from "@cosyte/astm";
const codes: string[] = [];
for (const raw of ["", "P|1\r", "H\r"]) {
try {
parseAstmRecords(raw);
} catch (err) {
if (err instanceof AstmParseError) codes.push(err.code);
}
}
codes; // => ["EMPTY_INPUT", "ASTM_RECORD_NO_HEADER", "ASTM_RECORD_UNDECLARED_DELIMITERS"]
Read a thrown error's code, not its message, to tell these apart from an AstmStrictError and from
the accessors' AstmAmbiguousStreamError.