4.0 KiB
aggregate
Purpose
Implements parseDmarcAggregate, the primary entry point for converting raw DMARC aggregate (RUA) report data into validated, strongly-typed DmarcAggregateReport objects. It handles the full pipeline: gzip auto-detection and decompression, XML parsing, snake_case-to-camelCase field mapping, single-vs-multiple element normalisation, and Zod schema validation with structured error reporting.
Features
- Auto-detects gzip input via magic-byte probe — no caller configuration needed for compressed reports
- Accepts both raw XML strings and
Uint8Arraybinary blobs in a single overload - Full snake_case → camelCase mapping for all RFC 7489 field names
- Handles variable-arity elements:
<record>,<dkim>,<spf>can be single or multiple - Validates the mapped structure with
DmarcAggregateReportSchema(Zod) - Emits OTel counter and histogram via
recordParse()on both success and error paths - Wraps all failures in typed
DmarcErrorwith stable codes
Usage: Basic Example
import { parseDmarcAggregate } from "@suaveplan/dmarc/parsers/aggregate";
const report = parseDmarcAggregate(`
<feedback>
<version>1.0</version>
<report_metadata>
<org_name>Example Corp</org_name>
<email>dmarc@example.com</email>
<report_id>12345</report_id>
<date_range><begin>1609459200</begin><end>1609545600</end></date_range>
</report_metadata>
<policy_published>
<domain>example.com</domain>
<adkim>r</adkim><aspf>r</aspf><p>none</p><pct>100</pct>
</policy_published>
<record>
<row>
<source_ip>192.0.2.1</source_ip><count>5</count>
<policy_evaluated><disposition>none</disposition><dkim>pass</dkim><spf>pass</spf></policy_evaluated>
</row>
<identifiers><header_from>example.com</header_from></identifiers>
<auth_results>
<dkim><domain>example.com</domain><result>pass</result></dkim>
<spf><domain>example.com</domain><result>pass</result></spf>
</auth_results>
</record>
</feedback>
`);
console.log(report.reportMetadata.orgName); // "Example Corp"
console.log(report.record[0].row.count); // 5
Usage: Advanced Example
import { parseDmarcAggregate } from "@suaveplan/dmarc/parsers/aggregate";
import { DmarcError, DMARC_CODES } from "@suaveplan/dmarc/errors";
import { gzipSync, strToU8 } from "fflate";
// Parse from gzip-compressed bytes
const xmlBytes = strToU8(xmlString);
const compressed = gzipSync(xmlBytes);
const report = parseDmarcAggregate(compressed);
// Error discrimination
try {
parseDmarcAggregate("not xml");
} catch (err) {
if (err instanceof DmarcError) {
switch (err.code) {
case DMARC_CODES.PARSE_ERROR:
// XML could not be parsed
break;
case DMARC_CODES.INVALID_REPORT:
// Parsed but failed schema validation
break;
case DMARC_CODES.UNSUPPORTED_ENCODING:
// Gzip decompression failed
break;
}
}
}
API Reference
parseDmarcAggregate(input, options?): DmarcAggregateReport
Parameters:
input: string | Uint8Array— raw XML string or binary data (plain or gzip-compressed)options.decompress?: boolean— force gzip decompression even without magic bytes
Returns: DmarcAggregateReport — fully validated aggregate report
Throws:
DmarcError(DMARC.PARSE_ERROR)— XML parse failureDmarcError(DMARC.INVALID_REPORT)— missing required elements or schema validation failureDmarcError(DMARC.UNSUPPORTED_ENCODING)— gzip decompression failure
ParseDmarcAggregateOptions
{ decompress?: boolean }
Implementation Notes
The parser uses a two-phase approach: structural mapping first (XML → plain object), validation second (Zod). This separation means that when validation fails the error message includes Zod's structured issue list rather than a raw type error, making debugging significantly easier. The performance.now() timing wraps the entire pipeline so OTel histograms reflect the true end-to-end cost including decompression and schema validation.