Skip to content

Move uniqueness and mutual‑exclusivity checks from TaggedFieldList.Add to InvoiceValidationService #69

@nGoline

Description

@nGoline

Summary

Refactor BOLT11 invoice validation so that all logical validation (uniqueness and mutual exclusivity of tagged fields) is centralized in InvoiceValidationService, rather than being enforced during list mutation in TaggedFieldList.Add. This improves separation of concerns, makes decoding more tolerant (collect first, validate after), and simplifies testing.

Background / Motivation

  • Current behavior enforces certain validations inside TaggedFieldList.Add (e.g., preventing multiple instances of single‑instance fields, or forbidding mutually exclusive fields). This couples data collection with policy/validation.
  • Invoice.Decode(...) already calls InvoiceValidationService.ValidateInvoice(invoice) after constructing the invoice (see src/NLightning.Bolt11/Models/Invoice.cs).
  • Centralizing policy in InvoiceValidationService allows TaggedFieldList to remain a simple container and avoids partial state/exception paths during parsing.

Scope

  • Move all checks for:
    • Uniqueness constraints for single‑instance tagged fields.
    • Mutual‑exclusivity constraints between specific tagged fields.
  • Remove these checks from TaggedFieldList.Add and implement them in InvoiceValidationService.

Proposed Changes

  1. Identify existing validations inside src/NLightning.Bolt11/Models/TaggedFieldList.cs (within Add(...)).
  2. Remove/relocate those checks to src/NLightning.Bolt11/Services/InvoiceValidationService.cs inside ValidateInvoice(...) (or dedicated private helpers), operating on the fully constructed Invoice/TaggedFieldList.
  3. Ensure TaggedFieldList.Add becomes a pure append/replace method without cross‑field business rules (only basic argument/null safety as needed).
  4. Update validation errors/messages to match current behavior so that external callers see consistent error reporting (same or clearer messages).
  5. Adjust or add unit tests to cover:
    • Duplicate single‑instance tags (should fail in InvoiceValidationService).
    • Mutually exclusive combinations (should fail in InvoiceValidationService).
    • Decoding path still reaches InvoiceValidationService and reports errors rather than failing during parse/add.

Acceptance Criteria

  • TaggedFieldList.Add contains no business rules for uniqueness or mutual exclusivity.
  • InvoiceValidationService.ValidateInvoice contains the moved checks and returns appropriate errors.
  • All existing tests in test/NLightning.Bolt11.Tests and integration tests pass, with adjustments only where they relied on TaggedFieldList.Add throwing.
  • New/updated tests assert that duplicates and mutually exclusive fields are caught by InvoiceValidationService.
  • Decoding invalid invoices yields InvoiceSerializationException populated from InvoiceValidationService errors (as today), not low‑level TaggedFieldList exceptions.

Notes / Implementation Hints

  • Relevant files:
    • src/NLightning.Bolt11/Models/TaggedFieldList.cs
    • src/NLightning.Bolt11/Services/InvoiceValidationService.cs
    • src/NLightning.Bolt11/Models/Invoice.cs (calls validation after constructing the invoice)
    • Tests likely touching this behavior:
      • test/NLightning.Bolt11.Tests/Models/TaggedFields/*
      • test/NLightning.Integration.Tests/BOLT11/TaggedFields/*
  • Keep InvoiceValidationService the single source of truth for cross‑field rules. If any validations already live there, consolidate and avoid duplications.
  • Preserve error messages where possible to avoid breaking consumers that assert on error text; if improvements are needed, update tests accordingly.

Potential Impacts

  • Behavior timing changes: exceptions that previously occurred during TaggedFieldList.Add will now surface from InvoiceValidationService; integration tests may need to expect validation errors at a slightly later stage.
  • Decoding pipeline becomes more linear: parse first, validate after — which is desirable.

Testing Plan

  • Unit tests for InvoiceValidationService that build minimal Invoice objects with crafted TaggedFieldList instances covering:
    • Duplicate single‑instance fields (e.g., two PayeePubKeyTaggedFields).
    • Mutually exclusive pairs (per current rules enforced in TaggedFieldList.Add).
  • Integration tests for Invoice.Decode(...) that confirm invalid invoices are rejected with the correct messages via InvoiceSerializationException sourced from validation results.

Definition of Done

  • Code changes merged with tests updated/added.
  • All CI builds/tests green on supported target frameworks (net8.0, net9.0).
  • Changelog entry (if you maintain one) noting validation logic relocation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions