Part 2 of #54. Depends on the Go 1.27 bump (part 1, branch feat/54-go127-toolchain).
Summary
validation.WithTyped[T] exists only because Go could not put a fresh type parameter on a method,
which forced the type argument to the front of the chain:
err := validation.WithTyped[TestUser](call.ValidatedBody(&user)).
Custom(func(u TestUser) bool { return len(u.Name) >= 2 }, "...").
Get()
Go 1.27 generic methods remove the cause, but not via the .Typed[T]() method #54 originally
proposed. Typed[T]() was itself a workaround for ValidatedBody not being generic. Call is not
a generic type, but a generic method on it can return a generic type with T inferred from the
argument — so ValidatedBody simply becomes generic and the extra hop disappears:
func (call *Call) ValidatedBody[T any](target *T) *BodyValidator[T]
err := call.ValidatedBody(&user).
Custom(func(u TestUser) bool { return len(u.Name) >= 2 }, "Name must be at least 2 characters").
Get()
T is inferred from &user; the caller never writes a type argument. Verified compiling on go1.27.0.
Scope
Add
func (call *Call) ValidatedBody[T any](target *T) *BodyValidator[T] in call.go
BodyValidator[T] in validation.go, with Custom(fn func(T) bool, message string) *BodyValidator[T]
attributing failures to "body"
Delete
TypedValidator[T], WithTyped, WithTypedCustom — the entire typed section of validation/validation.go
BodyValidator.AddRule (validation.go) — it has no caller except the two functions above
- the
validator interface{ AddRule(...); Get() error } indirection, which only existed to avoid
the import direction
- the runtime "target must be a pointer" check on this path —
*T makes it a compile error
Carry unchanged
BodyFieldValidator becomes BodyFieldValidator[T] so the reflection path still compiles. It is
deleted wholesale in part 3; do not improve it here.
Acceptance criteria
Note on formatting
The standalone gofumpt binary cannot parse generic methods yet (method must have no type parameters, mvdan/gofumpt#359, fix expected shortly). Until it ships, format with
golangci-lint fmt ./... — golangci-lint >= v2.13.1 built with go1.27 embeds a gofumpt that parses
them, and that is what CI runs.
Part 2 of #54. Depends on the Go 1.27 bump (part 1, branch
feat/54-go127-toolchain).Summary
validation.WithTyped[T]exists only because Go could not put a fresh type parameter on a method,which forced the type argument to the front of the chain:
Go 1.27 generic methods remove the cause, but not via the
.Typed[T]()method #54 originallyproposed.
Typed[T]()was itself a workaround forValidatedBodynot being generic.Callis nota generic type, but a generic method on it can return a generic type with
Tinferred from theargument — so
ValidatedBodysimply becomes generic and the extra hop disappears:Tis inferred from&user; the caller never writes a type argument. Verified compiling on go1.27.0.Scope
Add
func (call *Call) ValidatedBody[T any](target *T) *BodyValidator[T]incall.goBodyValidator[T]invalidation.go, withCustom(fn func(T) bool, message string) *BodyValidator[T]attributing failures to
"body"Delete
TypedValidator[T],WithTyped,WithTypedCustom— the entire typed section ofvalidation/validation.goBodyValidator.AddRule(validation.go) — it has no caller except the two functions abovevalidator interface{ AddRule(...); Get() error }indirection, which only existed to avoidthe import direction
*Tmakes it a compile errorCarry unchanged
BodyFieldValidatorbecomesBodyFieldValidator[T]so the reflection path still compiles. It isdeleted wholesale in part 3; do not improve it here.
Acceptance criteria
ValidatedBodyis a generic method inferringTfrom its argumentCustomonBodyValidator[T]takesfunc(T) boolwith no type assertion in the bodyWithTyped,WithTypedCustom,TypedValidator,AddRuleand the interface indirection are gonevalidation_test.gomigrated —TestBodyValidatorCustom,TestBodyValidatorWithTypedCustomand
TestCurryableTypedCustomValidationall lose theirvalidation.WithTyped*callsgo test ./...,go test -race ./...andGOVALIN_PERF_GATE=1 go test -run TestAllocationBudget .passgolangci-lint run ./...cleanNote on formatting
The standalone
gofumptbinary cannot parse generic methods yet (method must have no type parameters, mvdan/gofumpt#359, fix expected shortly). Until it ships, format withgolangci-lint fmt ./...— golangci-lint >= v2.13.1 built with go1.27 embeds a gofumpt that parsesthem, and that is what CI runs.