diff --git a/envelope.go b/envelope.go index 9bf720f..9e0d2f8 100644 --- a/envelope.go +++ b/envelope.go @@ -159,12 +159,23 @@ func (p Parser) ReadEnvelope(r io.Reader) (*Envelope, error) { if err != nil { return nil, errors.WithMessage(err, "Failed to ReadParts") } - return p.EnvelopeFromPart(root) + + e, err := p.EnvelopeFromPart(root) + if err != nil { + return nil, errors.WithMessage(err, "Failed to EnvelopeFromPart") + } + + err = e.GatherNestedErrors() + if err != nil { + return nil, errors.WithMessage(err, "Failed to GatherNestedErrors") + } + + return e, nil } // EnvelopeFromPart uses the provided Part tree to build an Envelope, downconverting HTML to plain // text if needed, and sorting the attachments, inlines and other parts into their respective -// slices. Errors are collected from all Parts and placed into the Envelopes Errors slice. +// slices. Gather errors from all parts with Envelope.GatherNestedErrors(). func EnvelopeFromPart(root *Part) (*Envelope, error) { return defaultParser.EnvelopeFromPart(root) } @@ -210,7 +221,12 @@ func (p Parser) EnvelopeFromPart(root *Part) (*Envelope, error) { } } - // Copy part errors into Envelope. + return e, nil +} + +// GatherNestedErrors gathers errors from the entire part tree (including the root) and adds them to the Envelope. +func (e *Envelope) GatherNestedErrors() error { + // Copy part errors from all nested/child/sibling parts into Envelope. if e.Root != nil { _ = e.Root.DepthMatchAll(func(part *Part) bool { // Using DepthMatchAll to traverse all parts, don't care about result. @@ -219,7 +235,7 @@ func (p Parser) EnvelopeFromPart(root *Part) (*Envelope, error) { }) } - return e, nil + return nil } // parseTextOnlyBody parses a plain text message in root that has MIME-like headers, but diff --git a/envelope_test.go b/envelope_test.go index a8eb9c2..b666250 100644 --- a/envelope_test.go +++ b/envelope_test.go @@ -61,6 +61,7 @@ func TestParseNonMime(t *testing.T) { func TestParseNonMimeHTML(t *testing.T) { msg := test.OpenTestData("mail", "non-mime-html.raw") + e, err := enmime.ReadEnvelope(msg) if err != nil { @@ -87,6 +88,57 @@ func TestParseNonMimeHTML(t *testing.T) { } } +// TestEnvelopeFromPartDoesNotGatherErrors verifies that EnvelopeFromPart does not collect errors, +// while GatherNestedErrors does. This enforces the separation of concerns: EnvelopeFromPart builds +// the envelope structure, and GatherNestedErrors is responsible for error collection. +func TestEnvelopeFromPartDoesNotGatherErrors(t *testing.T) { + // Use a message that generates errors (HTML without plain text) + msg := test.OpenTestData("mail", "non-mime-html.raw") + parser := enmime.NewParser() + root, err := parser.ReadParts(msg) + if err != nil { + t.Fatalf("Failed to read parts: %v", err) + } + + // Call EnvelopeFromPart directly (not ReadEnvelope) + e, err := parser.EnvelopeFromPart(root) + if err != nil { + t.Fatalf("Failed to create envelope from part: %v", err) + } + + // EnvelopeFromPart should NOT have collected errors + if len(e.Errors) != 0 { + t.Errorf("EnvelopeFromPart should not gather errors, but got %d errors:", len(e.Errors)) + for _, err := range e.Errors { + t.Logf(" - %v", err) + } + } + + // Verify that the envelope structure was built correctly + if e.HTML == "" { + t.Error("Expected HTML content from envelope structure building") + } + + // Now call GatherNestedErrors + err = e.GatherNestedErrors() + if err != nil { + t.Fatalf("GatherNestedErrors failed: %v", err) + } + + // GatherNestedErrors SHOULD have collected the error + if len(e.Errors) != 1 { + t.Errorf("GatherNestedErrors should collect exactly 1 error, got %d", len(e.Errors)) + } else if e.Errors[0].Name != enmime.ErrorPlainTextFromHTML { + t.Errorf("Expected error %q, got %q", enmime.ErrorPlainTextFromHTML, e.Errors[0].Name) + } + + // Verify text was downconverted from HTML + want := "This is *a* *test* mailing" + if !strings.Contains(e.Text, want) { + t.Errorf("Expected %q to contain %q", e.Text, want) + } +} + func TestParseMimeTree(t *testing.T) { msg := test.OpenTestData("mail", "attachment.raw") e, err := enmime.ReadEnvelope(msg)