Skip to main content
Version: v0.1.6

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.

Reference​