Skip to content

Refactor: make ValidatedBody a generic method and drop the WithTyped workaround #100

Description

@pkkummermo

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

  • ValidatedBody is a generic method inferring T from its argument
  • Custom on BodyValidator[T] takes func(T) bool with no type assertion in the body
  • WithTyped, WithTypedCustom, TypedValidator, AddRule and the interface indirection are gone
  • validation_test.go migrated — TestBodyValidatorCustom, TestBodyValidatorWithTypedCustom
    and TestCurryableTypedCustomValidation all lose their validation.WithTyped* calls
  • go test ./..., go test -race ./... and GOVALIN_PERF_GATE=1 go test -run TestAllocationBudget . pass
  • golangci-lint run ./... clean

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions