@@ -135,13 +135,15 @@ repositories {
135135| ` getErrors() ` | Get errors (empty if Ok) |
136136| ` withPrefix(String) ` | Namespace errors for nested objects |
137137
138- ### ResultCollector
138+ ### ResultCollectors
139139
140- | Method | Description |
141- | -----------------------------------------------------------| ----------------------------------------------------------------------|
142- | ` toResultList(String) ` / ` toResultList(String, int) ` | Returns ` Result<List<T>> ` (` Ok ` if all valid, ` Err ` with all errors) |
143- | ` toList(String) ` / ` toList(String, int) ` | Returns ` List<T> ` or throws with all accumulated errors |
144- | ` toPartitioned(String) ` / ` toPartitioned(String, int) ` | Returns valid items + errors (partial success) |
140+ | Method | Description |
141+ | ---------------------------------------------| ----------------------------------------------------------------------|
142+ | ` toResultList() ` / ` toResultList(int) ` | Returns ` Result<List<T>> ` (` Ok ` if all valid, ` Err ` with all errors) |
143+ | ` toList() ` / ` toList(int) ` | Returns ` List<T> ` or throws with all accumulated errors |
144+ | ` toPartitioned() ` / ` toPartitioned(int) ` | Returns valid items + errors (partial success) |
145+ | ` indexed(Collector<...>) ` | Wraps a collector to add ` [0] ` , ` [1] ` , etc. prefixes to errors |
146+ | ` indexed(Collector<...>, String) ` | Wraps a collector to add ` prefix[0] ` , ` prefix[1] ` , etc. to errors |
145147
146148> ** Note:** The optional ` int ` parameter provides an ` initialCapacity ` hint to avoid ArrayList resizing when the
147149> collection size is known upfront, improving performance for large streams.
@@ -370,9 +372,9 @@ for (int i = 0; i < items.size(); i++) {
370372Result<List<Item > > result = validation. asResult(items);
371373```
372374
373- #### Stream-Based Approach with ResultCollector
375+ #### Stream-Based Approach with ResultCollectors
374376
375- The ` ResultCollector ` class provides three specialized collectors for validating streams. ** All three collectors
377+ The ` ResultCollectors ` class provides three specialized collectors for validating streams. ** All three collectors
376378accumulate all validation errors** before returning/throwing - they do not fail fast, so you can get comprehensive
377379feedback on all items in the collection.
378380
@@ -381,21 +383,20 @@ feedback on all items in the collection.
381383Returns ` Result<List<T>> ` with all validation errors accumulated:
382384
383385``` java
384- import io.github.raniagus.javalidation.ResultCollector ;
386+ import io.github.raniagus.javalidation.ResultCollectors ;
385387
386388// Validate all items, collect ALL errors
387389Result<List<User > > result = items. stream()
388- .map(this :: validateUser)
389- .collect(ResultCollector . toResultList());
390+ .map(this :: validateUser)
391+ .collect(ResultCollectors . toResultList());
390392
391393// Handle result
392394switch (result) {
393395 case Result . Ok(List<User > users) - >
394396 processUsers(users);
395397 case Result . Err(ValidationErrors errors) - > {
396- // errors contain all validation failures with indexes:
397- // "[0].email": ["Invalid format"]
398- // "[2].age": ["Must be 18 or older"]
398+ // Errors contain all validation failures (without indexes by default)
399+ // Use indexed() wrapper to add automatic indexing
399400 logErrors(errors);
400401 }
401402}
@@ -408,13 +409,14 @@ Returns `List<T>` directly, throwing `JavalidationException` with all accumulate
408409``` java
409410try {
410411 List<User > users = items. stream()
411- .map(this :: validateUser)
412- .collect(ResultCollector . toList());
412+ .map(this :: validateUser)
413+ .collect(ResultCollectors . toList());
413414
414415 // All items valid
415416 processUsers(users);
416417} catch (JavalidationException e) {
417- // Contains ALL indexed errors: "[0].email", "[2].age", etc.
418+ // Contains ALL errors (without indexes by default)
419+ // Use indexed() wrapper to add automatic indexing
418420 logErrors(e. getErrors());
419421}
420422```
@@ -425,8 +427,8 @@ Returns `PartitionedResult<List<T>>` with both valid items and all errors:
425427
426428``` java
427429var partitioned = items. stream()
428- .map(this :: validateUser)
429- .collect(ResultCollector . toPartitioned());
430+ .map(this :: validateUser)
431+ .collect(ResultCollectors . toPartitioned());
430432
431433// Process valid items even if some failed
432434List<User > validUsers = partitioned. value();
@@ -441,70 +443,92 @@ if (errors.isNotEmpty()) {
441443processUsers(validUsers);
442444```
443445
444- ** Error Indexing:**
446+ ** Automatic Error Indexing with indexed() :**
445447
446- All three collectors automatically index errors by their position in the stream:
448+ By default, the collectors do not add index prefixes to errors. Use the ` indexed() ` wrapper to automatically prefix
449+ errors with ` [0] ` , ` [1] ` , etc. based on the item's position in the stream:
447450
448451``` java
449- // Input stream with 3 items (indices 0, 1, 2)
450- // Items at index 0 and 2 have validation errors
451-
452- Result<List<Item > > result = stream. collect(ResultCollector . toResultList());
452+ // Without indexing (default)
453+ Result<List<Item > > result = stream. collect(ResultCollectors . toResultList());
454+ // Errors: "field": ["Error message"]
453455
456+ // With automatic indexing
457+ Result<List<Item > > result = stream. collect(
458+ ResultCollectors . indexed(ResultCollectors . toResultList())
459+ );
454460// Errors are prefixed with "[index]":
455- // "[0].field1": ["Error message"]
456- // "[0].field2": ["Another error"]
461+ // "[0].field": ["Error message"]
457462// "[2].price": ["Must be positive"]
458463```
459464
460465** Custom Prefix for Nested Collections:**
461466
462- Each collector has an overloaded variant accepting a ` prefix ` parameter to namespace errors for nested structures:
467+ The ` indexed() ` wrapper accepts an optional ` prefix ` parameter to namespace errors for nested structures:
463468
464469``` java
465- // Validate items in an order
470+ // Validate items in an order with custom prefix
466471Result<List<Item > > items = order. getItems(). stream()
467- .map(this :: validateItem)
468- .collect(ResultCollector . toResultList(" order.items" ));
472+ .map(this :: validateItem)
473+ .collect(ResultCollectors . indexed(
474+ ResultCollectors . toResultList(),
475+ " order.items"
476+ ));
469477
470478// Errors appear as: "order.items[0].price", "order.items[1].name", etc.
471479
472480// Process valid items with prefix
473481var partitioned = order. getLineItems(). stream()
474- .map(this :: validateLineItem)
475- .collect(ResultCollector . toPartitioned(" lineItems" ));
482+ .map(this :: validateLineItem)
483+ .collect(ResultCollectors . indexed(
484+ ResultCollectors . toPartitioned(),
485+ " lineItems"
486+ ));
476487
477488// Errors: "lineItems[0].quantity", "lineItems[2].discount", etc.
478489
479490// Throw on error with custom prefix
480491try {
481492 List<Address > addresses = user. getAddresses(). stream()
482- .map(this :: validateAddress)
483- .collect(ResultCollector . toList(" addresses" ));
493+ .map(this :: validateAddress)
494+ .collect(ResultCollectors . indexed(
495+ ResultCollectors . toList(),
496+ " addresses"
497+ ));
484498} catch (JavalidationException e) {
485499 // Errors: "addresses[0].street", "addresses[1].zipCode", etc.
486500}
487501```
488502
489503** Performance Optimization with Size Hints:**
490504
491- Each collector also accepts an optional ` initialCapacity ` parameter to avoid ArrayList resizing when the collection size is known upfront:
505+ All three collectors accept an optional ` initialCapacity ` parameter to avoid ArrayList resizing when the collection
506+ size is known upfront:
492507
493508``` java
494509// When you know the collection size, provide a capacity hint
495510List<User > users = items. stream()
496- .map(this :: validateUser)
497- .collect(ResultCollector . toList(" users" , items. size()));
511+ .map(this :: validateUser)
512+ .collect(ResultCollectors . indexed(
513+ ResultCollectors . toList(items. size()),
514+ " users"
515+ ));
498516
499517// For large streams, this avoids multiple ArrayList resizing operations
500518Result<List<Product > > products = productStream
501- .map(this :: validateProduct)
502- .collect(ResultCollector . toResultList(" products" , expectedSize));
519+ .map(this :: validateProduct)
520+ .collect(ResultCollectors . indexed(
521+ ResultCollectors . toResultList(expectedSize),
522+ " products"
523+ ));
503524
504525// Works with all three collectors
505526var partitioned = orders. stream()
506- .map(this :: validateOrder)
507- .collect(ResultCollector . toPartitioned(" orders" , orders. size()));
527+ .map(this :: validateOrder)
528+ .collect(ResultCollectors . indexed(
529+ ResultCollectors . toPartitioned(orders. size()),
530+ " orders"
531+ ));
508532```
509533
510534** Choosing the Right Collector:**
0 commit comments