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
- Identify existing validations inside
src/NLightning.Bolt11/Models/TaggedFieldList.cs (within Add(...)).
- Remove/relocate those checks to
src/NLightning.Bolt11/Services/InvoiceValidationService.cs inside ValidateInvoice(...) (or dedicated private helpers), operating on the fully constructed Invoice/TaggedFieldList.
- Ensure
TaggedFieldList.Add becomes a pure append/replace method without cross‑field business rules (only basic argument/null safety as needed).
- Update validation errors/messages to match current behavior so that external callers see consistent error reporting (same or clearer messages).
- 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
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.
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 inTaggedFieldList.Add. This improves separation of concerns, makes decoding more tolerant (collect first, validate after), and simplifies testing.Background / Motivation
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 callsInvoiceValidationService.ValidateInvoice(invoice)after constructing the invoice (seesrc/NLightning.Bolt11/Models/Invoice.cs).InvoiceValidationServiceallowsTaggedFieldListto remain a simple container and avoids partial state/exception paths during parsing.Scope
TaggedFieldList.Addand implement them inInvoiceValidationService.Proposed Changes
src/NLightning.Bolt11/Models/TaggedFieldList.cs(withinAdd(...)).src/NLightning.Bolt11/Services/InvoiceValidationService.csinsideValidateInvoice(...)(or dedicated private helpers), operating on the fully constructedInvoice/TaggedFieldList.TaggedFieldList.Addbecomes a pure append/replace method without cross‑field business rules (only basic argument/null safety as needed).InvoiceValidationService).InvoiceValidationService).InvoiceValidationServiceand reports errors rather than failing during parse/add.Acceptance Criteria
TaggedFieldList.Addcontains no business rules for uniqueness or mutual exclusivity.InvoiceValidationService.ValidateInvoicecontains the moved checks and returns appropriate errors.test/NLightning.Bolt11.Testsand integration tests pass, with adjustments only where they relied onTaggedFieldList.Addthrowing.InvoiceValidationService.InvoiceSerializationExceptionpopulated fromInvoiceValidationServiceerrors (as today), not low‑levelTaggedFieldListexceptions.Notes / Implementation Hints
src/NLightning.Bolt11/Models/TaggedFieldList.cssrc/NLightning.Bolt11/Services/InvoiceValidationService.cssrc/NLightning.Bolt11/Models/Invoice.cs(calls validation after constructing the invoice)test/NLightning.Bolt11.Tests/Models/TaggedFields/*test/NLightning.Integration.Tests/BOLT11/TaggedFields/*InvoiceValidationServicethe single source of truth for cross‑field rules. If any validations already live there, consolidate and avoid duplications.Potential Impacts
TaggedFieldList.Addwill now surface fromInvoiceValidationService; integration tests may need to expect validation errors at a slightly later stage.Testing Plan
InvoiceValidationServicethat build minimalInvoiceobjects with craftedTaggedFieldListinstances covering:PayeePubKeyTaggedFields).TaggedFieldList.Add).Invoice.Decode(...)that confirm invalid invoices are rejected with the correct messages viaInvoiceSerializationExceptionsourced from validation results.Definition of Done
net8.0,net9.0).