All() is fail-fast; no all-errors-at-once aggregation #5

Closed
opened 2026-09-07 12:22:50 +00:00 by claude · 0 comments
Member

All(fs...) returns on the first failing validator. That's the right default for a lot of cases, but form and API validation usually wants every problem reported at once ("email is invalid and password is too short"), which is also what ozzo-validation and validator/v10 do.

Today the caller has to run each validator separately and collect the errors themselves.

Sketch of a fix (not implemented — flagged for a decision):

// Collect runs every validator and joins all failures with errors.Join,
// instead of stopping at the first like All. errors.Is still matches any
// individual error in the result.
func Collect[T any](fs ...func(T) error) func(T) error {
    return func(value T) error {
        var errs []error
        for _, f := range fs {
            if err := f(value); err != nil {
                errs = append(errs, err)
            }
        }
        return errors.Join(errs...)
    }
}

Open questions: Collect as a sibling of All, or a variadic option on All? How should the joined error render (newline-joined via errors.Join, or a custom multi-error type with a []error accessor for callers that want to format per-field)? Interaction with #4.

`All(fs...)` returns on the first failing validator. That's the right default for a lot of cases, but form and API validation usually wants *every* problem reported at once ("email is invalid **and** password is too short"), which is also what `ozzo-validation` and `validator/v10` do. Today the caller has to run each validator separately and collect the errors themselves. Sketch of a fix (not implemented — flagged for a decision): ```go // Collect runs every validator and joins all failures with errors.Join, // instead of stopping at the first like All. errors.Is still matches any // individual error in the result. func Collect[T any](fs ...func(T) error) func(T) error { return func(value T) error { var errs []error for _, f := range fs { if err := f(value); err != nil { errs = append(errs, err) } } return errors.Join(errs...) } } ``` Open questions: `Collect` as a sibling of `All`, or a variadic option on `All`? How should the joined error render (newline-joined via `errors.Join`, or a custom multi-error type with a `[]error` accessor for callers that want to format per-field)? Interaction with #4.
Sign in to join this conversation.