Optics: Boundary Mapping Journey
- Multi-edit and sparse updates: several edits, one operation, all errors at once
ValidatedPrism: parse-don't-validate as an optic, with both round-trip laws@GenerateMapping: the whole domain ↔ DTO boundary derived from a spec interface- Located errors end to end: leaves, nesting, renames, and the sparse PATCH sibling
- The edge cases: a
null, a list index, a left-out field, a record's invariant, and a PATCH bean's default - An order desk, the Optics chapter's capstone: named paths, every bad price, a sparse amendment and a change of state
Tutorials: 5 (T24-T27 and the Capstone) | Exercises: 24
This journey is the hands-on lane for the Mapping at the Boundary chapter. Tutorial 24 builds the update-side machinery by hand (Edits.combine / Edits.accumulate), Tutorial 25 builds the leaf every fallible correspondence rests on (ValidatedPrism), and Tutorial 26 lets the processor derive the whole boundary and proves it lawful. Tutorial 27 takes it to the edge cases a real request brings. The Mapping chapter's capstone, One 422, Every Bad Field, then shows the same machinery at full scale. The optics track's Capstone closes the journey with the Optics chapter's Capstone: An Order Desk, built from named paths and Tutorial 24's edits.
Prerequisites: Optics: Lens & Prism Journey; the accumulating-assembly exercises in the Error Handling Journey help with Tutorials 25-27. The Capstone also assumes Tutorial 09 of the Fluent & Free DSL Journey and Tutorial 12 of the Focus DSL Journey.
Journey Overview
A service boundary has two directions and two failure styles: outbound rendering that cannot fail, and inbound parsing that should report every problem, located. This journey builds that boundary from its parts, then generates it:
T24 Edits.accumulate the hand-written fold
│
▼
T25 ValidatedPrism the fallible leaf
│
▼
T26 @GenerateMapping the derived boundary
│
▼
T27 edge cases nulls, list indexes, invariants, PATCH defaults
│
▼
Capstone order desk the Optics chapter's capstone, as exercises
Tutorial 24: Multi-Edit and Sparse Updates
File: Tutorial24_MultiEdit.java | Exercises: 5
Apply N independent edits at different paths in one reusable operation, including the sparse, all-errors-at-once REST PATCH shape.
What you'll learn:
- Folding pure edits into one reusable
Update<S>withEdits.combine - Sparse updates: the
…IfPresentfactories treatnullas "leave it alone" - The validated PATCH:
Edits.accumulatereports all located failures at once - Why a fallible edit cannot slip into
combine(compile-time purity)
Key insight: validation is source-independent and runs first; the writes run as one fold only if everything validated.
Tutorial 25: ValidatedPrism
File: Tutorial25_ValidatedPrism.java | Exercises: 3
The smart-constructor optic: a Prism whose match says why not, and all the reasons at once.
What you'll learn:
ValidatedPrism.of(parse, build): a fallible, accumulatingparseand a totalbuild- Lifting a plain prism with a reason via
fromPrism - Nesting short-circuits; sibling fields accumulate through
Validated.fields() - Verifying both round-trip laws with
ValidatedPrismLaws
Key insight: the section law forbids a parse that normalises; the prism's parse is exactly the leaf shape the mapper and the Edits builder consume.
Tutorial 26: Record Mapping
File: Tutorial26_RecordMapping.java | Exercises: 5
The boundary, generated: @GenerateMapping derives a total build and an accumulating, located parse from a spec interface (the specs live in org.higherkindedj.example.tutorials.mapping, main sources, where the processor runs).
What you'll learn:
- Calling the generated Impl, bound once in the calling class:
buildis total,parsereturnsValidated<NonEmptyList<FieldError>, Domain> - Reading located errors: stock codec messages, a nested spec's
guest.emailpath, declaration order - Law-checking a mapping with one
MappingLawscall - The sparse PATCH sibling:
UpdateSpec, null-as-absent, same leaf vocabulary
Key insight: everything Tutorials 24 and 25 built by hand is what the processor derives, and the laws prove the derivation honest.
Tutorial 27: Boundary Edge Cases
File: Tutorial27_BoundaryEdgeCases.java | Exercises: 6
A real request is rarely just a bad value. It leaves a field out, sends a list with one bad element, breaks a rule that spans two fields, or arrives as a PATCH bean that fills in a value nobody sent. Each exercise asks where that request lands. Its specs sit beside Tutorial 26's.
What you'll learn:
- A
nullfield on the wire is a located error, beside every other error - A list element is located by its index
@OptionalBridgedeclares, per component, that anullmeans absent- A record's constructor refusal becomes an error at the record's path
- A PATCH bean's default reads as sent, and how a law catches it
| Exercise | The request | The question |
|---|---|---|
| 1 | A booking with no id, whose guest has no email | Where is each null reported, and with what message? |
| 2 | A party whose first guest has no name, and whose second has a bad email | How does the path say which guest? |
| 3 | A room request that leaves its note out | What does @OptionalBridge make of the null? |
| 4 | A stay whose departure is not after its arrival | Where does the constructor's refusal land? |
| 5 | A PATCH that sends nothing | How do we check that it changes nothing? |
| Diagnostic | The same PATCH, on a bean whose schema said default: false | Why does the team's law pass, and which sample makes it fail? |
Key insight: parse and updateFrom return every edge case here as a value, never a thrown exception. The odd one out, a PATCH bean's default, comes back valid and wrong; only a law, run with a sample that differs from the default, catches it.
Capstone: The Order Desk
File: TutorialCapstone_OrderDesk.java | Exercises: 5
The Optics chapter's capstone as exercises, on its running example: an order, and the consignment that ships it. Each exercise asks for a piece of the Capstone page's order desk, on a request the page did not show.
What you'll learn:
- Reusing a named path in every operation that needs it
- Checking every price with
modifyAllValidated, and changing only a valid result withmap - Narrowing a traversal path with
filter, so a write leaves the other lines alone - Amending an order from a sparse request with
Edits.accumulate, every bad field located - Moving a sealed state to another variant only from the one you expect
Key insight: The paths do the navigating, so each operation is a few lines, and each failure is a value that names where it happened.
- Mapping at the Boundary: The reference chapter this journey practises
- Capstone: One 422, Every Bad Field: The same machinery at full scale
- Null has an address, not a stack trace: Tutorial 27's null rule
- Nesting a spec, and a list of them: How a list element is located
- Absent Fields and Record Invariants: Tutorial 27's
@OptionalBridgeand invariant rules in full - A PATCH getter must answer
nulluntil set: Why a PATCH bean must leave its fields uninitialised - Many Edits at Once: Tutorial 24's reference page
- Validated Prisms: Tutorial 25's reference page
- Capstone: An Order Desk: the Capstone's reference page
Previous: Batching & Coupled Updates Next: Expression