-
Notifications
You must be signed in to change notification settings - Fork 17
Open
Description
Problem
kun has already supported request validation since #10. However, there are some disadvantages:
- The request validation is done at the transport layer, while the request fields to validate are actually designed at the service layer
- Input validation of pure services (i.e. without the transport layer) are not supported, which is a critical problem since "In-process function call" is the communication type
kunmust support. - Validation schemas are not visible from the documentation (i.e. Godoc for pure services, OAS for HTTP services, Protobuf for gRPC services, etc)
Validation Grammar
Validation grammars from the current leading Go frameworks:
- Gin: uses go-playground/validator/v10 (see example)
- go-zero: has very limited built-in support (see the "tag modifier" part) and recommends using third party validator
- gRPC-Gateway: uses mwitkow/go-proto-validators (see doc)
- Kratos: uses bufbuild/protoc-gen-validate (see doc)
- Micro: has no support?
- Go kit: uses a custom middleware (see discussion)
Other interesting references:
Proposed Solution
- Define validation schemas in the comments of interface methods.
- Generate the validation code, in the form of a service middleware (i.e. at the service layer), per the schema definitions.
Example
helloworld
Take the helloworld service as an example, the validation schemas become (see previous schema definition):
// Service is used for saying hello.
type Service interface {
// SayHello says hello to the given name.
//
// @schema:
// name: len(0, 10).msg("bad length") && match(`^\w+$`)
//
//kun:op POST /messages
SayHello(ctx context.Context, name string) (message string, err error)
}and this is the corresponding generated service middleware:
import (
v "github.com/RussellLuo/validating/v3"
)
func ValidatingMiddleware() func(next Service) Service {
return func(next Service) Service {
return &validatingMiddleware{
next: next,
}
}
}
type validatingMiddleware struct {
next Service
}
func (mw *validatingMiddleware) SayHello(ctx context.Context, name string) (string, error) {
schema := v.Schema{
v.F("name", name): v.All(
v.LenString(0, 10).Msg("bad length"),
v.Match(regexp.MustCompile(`^\w+$`)),
),
}
if err := v.Validate(schema); err != nil {
return "", werror.Wrap(gcode.ErrInvalidArgument, err)
}
return mw.next.SayHello(ctx, name)
}usersvc
Take the usersvc an example, the schema definition will be:
type User struct {
Name string
Age int
IP net.IP `kun:"in=header name=X-Forwarded-For, in=request name=RemoteAddr"`
}
func (u User) Schema() v.Schema {
return v.Schema{
v.F("name", u.Name): v.All(
v.LenString(0, 10),
v.Match(regexp.MustCompile(`^\w+$`)),
),
v.F("age", u.Age): v.Range(0, 100),
v.F("ip", user.IP): vext.IP(),
}
}
type Service interface {
// CreateUser creates a user with the given attributes.
//
//kun:op POST /users
//kun:param user
//kun:success body=result
CreateUser(ctx context.Context, user User) (result User, err error)
}and this is the corresponding generated service middleware:
import (
v "github.com/RussellLuo/validating/v3"
)
func ValidatingMiddleware() func(next Service) Service {
return func(next Service) Service {
return &validatingMiddleware{
next: next,
}
}
}
type validatingMiddleware struct {
next Service
}
func (mw *validatingMiddleware) CreateUser(ctx context.Context, user User) (result User, err error) {
schema := v.Schema{
v.F("user", user): user.Schema(),
}
if err := v.Validate(schema); err != nil {
return User{}, werror.Wrap(gcode.ErrInvalidArgument, err)
}
return mw.next.CreateUser(ctx, user)
}Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels