infrastructure capa

infrastructure-mail-extract

Per-sender-family structured field extraction (regex-based body parsing) over foundation-mail-message::Email

Per-sender-family structured field extraction (regex-based body parsing) over foundation-mail-message::Email

Tierinfrastructure
Roleunclassified (baselined)
Pathcrates/infrastructure/mail-extract
Edition2021
Targetsinfrastructure_mail_extract
Public items12 across 6 modules
Tests19

What it is for

# infrastructure-mail-extract

Per-sender-family structured body field extraction over foundation_mail_message::Email — "this message is a PayPal transaction receipt, and here's the amount/fee/transaction id it contains," not generic MIME parsing (that's Email/MessagePart themselves, crate 1) and not header-based classification (that's infrastructure-mail-headers, crate 6).

Refactored, not ported, from rust-gmail-manager/src/parsing/{service, families/paypal}.rs (Gate 4.5 provenance — see CHANGELOG.md). The source's EmailParser trait took a decomposed (sender, subject, body, date) bag of &str/Option<DateTime> params read from Postgres columns; this crate's FamilyParser trait reads an Email directly — same refactor direction as crate 6, applied to body extraction instead of header classification.

Scope: PayPal only, for now

mm's parsing/families/ has three parsers (paypal.rs, uber.rs, cointracker.rs). The sprint doc's crate 7 source line names only families/paypal.rs, so only families::paypal::PayPalParser is lifted here — see families/mod.rs and CHANGELOG.md for the explicit non-goal note on the other two.

Layer note: no cargo dependency on infrastructure-mail-headers

The sprint doc's crate table lists crate 7's "Depends on" column as mail-headers (crate 6). This crate does not actually Cargo.toml-depend on infrastructure-mail-headers: both crates are infrastructure layer, and tools-xtask check-deps's allowed_deps only permits a non-tools library crate to depend on foundation — crate 6 itself only depends on foundation-mail-message for the same reason. Functionally there is no need for the dependency either: PayPalParser::matches_sender reads email.from directly (a substring check, same as the source), never infrastructure_mail_headers::classify's MailClass or from_address::ParsedFrom output. See docs/architecture/layers.toml's entry for this crate for the same note.

Gate 1.5.6 — input limits

Email bodies originate from arbitrary inbound mail (attacker-reachable content). limits::capped_body truncates to limits::MAX_BODY_BYTES before any family parser's regex runs — see that module's doc comment.

Capabilities

Extraction

Run a set of family parsers against one Email and collect the matches.

Item
pub struct Extraction
fn extract(email : & Email, parsers : & Box <dyn FamilyParser>) -> Vec <Extraction>

ExtractionOutcome

Run a set of family parsers against one Email and collect the matches.

Item
pub enum ExtractionOutcome

PayPalParser

PayPal transaction-email parser.

Item
pub struct PayPalParser
PayPalParser :: fn family_key(& self) -> & 'static str
PayPalParser :: fn matches_sender(& self, email : & Email) -> bool
PayPalParser :: fn parse(& self, email : & Email) -> Option <ParseResult>

limits (other)

Gate 1.5.6 input-limit compliance: every family parser in this crate reads

Item
pub const MAX_BODY_BYTES: usize
fn capped_body(email : & Email) -> & str

registry (other)

Default family-parser registry.

Item
fn default_parsers() -> Vec <Box <dyn FamilyParser>>

FamilyParser

Extraction result type and the per-sender-family parser trait.

Item
pub trait FamilyParser

ParseResult

Extraction result type and the per-sender-family parser trait.

Item
pub struct ParseResult

How to use it

No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.

Module structure

infrastructure_mail_extract

flowchart TD
  n_infrastructure_mail_extract["infrastructure_mail_extract"]
  n_infrastructure_mail_extract --> n_extract["extract"]
  n_infrastructure_mail_extract --> n_families["families"]
  n_families --> n_families__paypal["paypal"]
  n_infrastructure_mail_extract --> n_limits["limits"]
  n_infrastructure_mail_extract --> n_registry["registry"]
  n_infrastructure_mail_extract --> n_types["types"]

Public surface

`extract`

ItemWhat it is
pub struct ExtractionOne family parser's match against one Email: which family matched, and what it extracted (or didn't — see ExtractionOutcome).
pub enum ExtractionOutcomeMirrors the source's parsed-vs-no_match distinction (report.parsed vs report.no_match in mm's ParseRunReport): a family whose sender pattern matched but whose body didn't parse is a reportable event, not silence — a caller building its own run report (as mm did) needs both outcomes, not just the successes.
fn extract(email : & Email, parsers : & Box <dyn FamilyParser>) -> Vec <Extraction>Run every parser in parsers whose FamilyParser::matches_sender accepts email, returning one Extraction per matching family in registry order

`families::paypal`

ItemWhat it is
pub struct PayPalParser
PayPalParser :: fn family_key(& self) -> & 'static str
PayPalParser :: fn matches_sender(& self, email : & Email) -> bool
PayPalParser :: fn parse(& self, email : & Email) -> Option <ParseResult>

`limits`

ItemWhat it is
pub const MAX_BODY_BYTES: usizeCeiling on how many bytes of an Email's plain-text body a family parser will scan
fn capped_body(email : & Email) -> & strThe Email's first text/plain part, truncated to MAX_BODY_BYTES at a char boundary (never split a multi-byte UTF-8 sequence)

`registry`

ItemWhat it is
fn default_parsers() -> Vec <Box <dyn FamilyParser>>Build the default family-parser registry: currently PayPal only.

`types`

ItemWhat it is
pub struct ParseResultA single family parser's structured extraction from one Email
pub trait FamilyParserOne sender-family's body field extractor (PayPal, Uber, CoinTracker, ..

Re-exports. Exported here, defined elsewhere.

ExportDefined in
default_parsersregistry::default_parsers
{FamilyParser,ParseResult}types::{FamilyParser,ParseResult}
{extract,Extraction,ExtractionOutcome}extract::{extract,Extraction,ExtractionOutcome}

Boundary

Reaches into foundation.

Shares tier infrastructure with 82 other crates: infrastructure-acquire, infrastructure-adapters-google-calendar, infrastructure-adapters-google-gmail, infrastructure-adapters-google-places, infrastructure-adapters-google-trends, infrastructure-adapters-shodan, infrastructure-adapters-yelp, infrastructure-agent, … (82 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)infrastructure
Architectural role (taxonomy)unclassified (baselined)
Locationcrates/infrastructure/mail-extract
Vocabulary in force (lexicon)current

Tier flow. Which tiers this crate's own edges cross.

flowchart LR
  n_infrastructure["infrastructure"] --> n_foundation["foundation"]

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`foundation-mail-message`foundationnoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
chrono^0.4serdenoalways
regex^1noalways
serde^1derivenoalways
serde_json^1noalways

Development. None.

Build. None.

Depended on by. Nothing in this workspace.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  SELF["infrastructure-mail-extract"]
  SELF -->|runtime| n_foundation_mail_message["foundation-mail-message"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.

Targets

KindNameSource
libinfrastructure_mail_extract`src/lib.rs`

Error model

No public error type was detected: no public item declares a type named *Error, and no public signature returns one.

Operational characteristics

PropertyEvidence
async public surfacenone detected
async runtimenone detected
database accessnone detected
network I/Onone detected
unsafe codenone detected
environment variablesnone detected

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.

No workspace crate depends on this one.

Verification

KindCount
Unit tests19
Integration tests0
Examples0
Doctests0

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
extract300
families::paypal100
limits200
registry100
types200

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc812
Public modules with a //! block66
pie showData
    title Public items with rustdoc
    "Documented" : 8
    "No rustdoc detected" : 4

Metrics

MetricValue
Rust source files7
Source lines631
Code lines390
Public API items12
Public modules6
Tests19
Examples0
Cargo features0
Direct runtime dependencies5
Workspace reverse dependencies0
pie showData
    title Public API by kind
    "constant" : 1
    "enum" : 1
    "function" : 3
    "method" : 3
    "struct" : 3
    "trait" : 1
pie showData
    title Rust source composition
    "Code" : 390
    "Blank or comment" : 241

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

Todas las infrastructure · Manual