first commit
This commit is contained in:
108
docs/communication/emails/dmarc/src/parsers/aggregate.md
Normal file
108
docs/communication/emails/dmarc/src/parsers/aggregate.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 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 `Uint8Array` binary 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 `DmarcError` with stable codes
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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 failure
|
||||
- `DmarcError(DMARC.INVALID_REPORT)` — missing required elements or schema validation failure
|
||||
- `DmarcError(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.
|
||||
Reference in New Issue
Block a user