Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the business level input validation #34

Open
RussellLuo opened this issue Feb 28, 2023 · 1 comment
Open

Add support for the business level input validation #34

RussellLuo opened this issue Feb 28, 2023 · 1 comment

Comments

@RussellLuo
Copy link
Owner

RussellLuo commented Feb 28, 2023

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 kun must 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:

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)
}
@RussellLuo
Copy link
Owner Author

This feature has been implemented in a standalone repo protogodev/validate. For runnable examples, see helloworld and usersvc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant