diff --git a/app.go b/app.go index 59f8691f..040633bb 100644 --- a/app.go +++ b/app.go @@ -39,6 +39,7 @@ import ( "github.com/topfreegames/pitaya/config" "github.com/topfreegames/pitaya/constants" pcontext "github.com/topfreegames/pitaya/context" + "github.com/topfreegames/pitaya/defaultpipelines" "github.com/topfreegames/pitaya/errors" "github.com/topfreegames/pitaya/internal/codec" "github.com/topfreegames/pitaya/internal/message" @@ -137,6 +138,7 @@ func Configure( app.server.Metadata = serverMetadata app.messageEncoder = message.NewMessagesEncoder(app.config.GetBool("pitaya.handler.messages.compression")) configureMetrics(serverType) + configureDefaultPipelines(app.config) app.configured = true } @@ -161,7 +163,12 @@ func configureMetrics(serverType string) { AddMetricsReporter(metricsReporter) } } +} +func configureDefaultPipelines(config *config.Config) { + if config.GetBool("pitaya.defaultpipelines.structvalidation.enabled") { + BeforeHandler(defaultpipelines.StructValidatorInstance.Validate) + } } // AddAcceptor adds a new acceptor to app diff --git a/config/config.go b/config/config.go index 052092a8..61bae593 100644 --- a/config/config.go +++ b/config/config.go @@ -94,6 +94,7 @@ func (c *Config) fillDefaultValues() { "pitaya.metrics.prometheus.port": 9090, "pitaya.metrics.prometheus.enabled": false, "pitaya.metrics.tags": map[string]string{}, + "pitaya.defaultpipelines.structvalidation.enabled": false, } for param := range defaultsMap { diff --git a/defaultpipelines/default_struct_validator.go b/defaultpipelines/default_struct_validator.go new file mode 100644 index 00000000..6e72f0e0 --- /dev/null +++ b/defaultpipelines/default_struct_validator.go @@ -0,0 +1,34 @@ +package defaultpipelines + +import ( + "context" + "sync" + + validator "gopkg.in/go-playground/validator.v9" +) + +// DefaultValidator is the default arguments validator for handlers +// in pitaya +type DefaultValidator struct { + once sync.Once + validate *validator.Validate +} + +// Validate is the the function responsible for validating the 'in' parameter +// based on the struct tags the parameter has. +// This function has the pipeline.Handler signature so +// it is possible to use it as a pipeline function +func (v *DefaultValidator) Validate(ctx context.Context, in interface{}) (interface{}, error) { + v.lazyinit() + if err := v.validate.Struct(in); err != nil { + return nil, err + } + + return in, nil +} + +func (v *DefaultValidator) lazyinit() { + v.once.Do(func() { + v.validate = validator.New() + }) +} diff --git a/defaultpipelines/struct_validator.go b/defaultpipelines/struct_validator.go new file mode 100644 index 00000000..a47d89f6 --- /dev/null +++ b/defaultpipelines/struct_validator.go @@ -0,0 +1,17 @@ +package defaultpipelines + +import ( + "context" +) + +// StructValidator is the interface that must be implemented +// by a struct validator for the request arguments on pitaya. +// +// The default struct validator used by pitaya is https://github.com/go-playground/validator. +type StructValidator interface { + Validate(context.Context, interface{}) (interface{}, error) +} + +// StructValidatorInstance holds the default validator +// on start but can be overridden if needed. +var StructValidatorInstance StructValidator = &DefaultValidator{} diff --git a/docs/configuration.rst b/docs/configuration.rst index 18c41a00..72a5bf05 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -245,3 +245,21 @@ These configurations are only used if the modules are created. It is recommended - time.Time - Duration of the etcd lease before automatic renewal +Default Pipelines +================= + +These configurations control if the default pipelines should be enabled or not + +.. list-table:: + :widths: 15 10 10 50 + :header-rows: 1 + :stub-columns: 1 + + * - Configuration + - Default value + - Type + - Description + * - pitaya.defaultpipelines.structvalidation.enabled + - false + - bool + - Whether Pitaya should use the default struct validator for handler arguments diff --git a/examples/demo/pipeline/main.go b/examples/demo/pipeline/main.go index 21dc9a90..23298e6c 100644 --- a/examples/demo/pipeline/main.go +++ b/examples/demo/pipeline/main.go @@ -6,11 +6,11 @@ import ( "fmt" "github.com/sirupsen/logrus" + "github.com/spf13/viper" "github.com/topfreegames/pitaya" "github.com/topfreegames/pitaya/acceptor" "github.com/topfreegames/pitaya/component" "github.com/topfreegames/pitaya/serialize/json" - validator "gopkg.in/go-playground/validator.v9" ) // MetagameServer ... @@ -26,7 +26,10 @@ func NewMetagameMock() *MetagameServer { } } -// CreatePlayerCheatArgs ... +// CreatePlayerCheatArgs is the struct used as parameter for the CreatePlayerCheat handler +// Using the 'validate' tag it's possible to add validations on all struct fields. +// For reference on the default validator see https://github.com/go-playground/validator. +// Also, to enable this validation pipeline see docs/configuration.rst. type CreatePlayerCheatArgs struct { Name string `json:"name"` Email string `json:"email" validate:"email"` @@ -47,20 +50,6 @@ func (g *MetagameServer) CreatePlayerCheat(ctx context.Context, args *CreatePlay }, nil } -// This is a beforeHandler that validates the handler argument based on the struct tags. -// As for this example, the CreatePlayerCheatArgs has the 'validate' tags for email, -// softCurrency and hardCurrency. If any of the validations fail an error will be returned -func handlerParamsValidator(ctx context.Context, in interface{}) (interface{}, error) { - var validate *validator.Validate - validate = validator.New() - - if err := validate.Struct(in); err != nil { - return nil, err - } - - return in, nil -} - // Simple example of a before pipeline that actually asserts the type of the // in parameter. // IMPORTANT: that this kind of pipeline will be hard to exist in real code @@ -99,13 +88,17 @@ func main() { ) // Pipelines registration - pitaya.BeforeHandler(handlerParamsValidator) pitaya.BeforeHandler(metagameServer.simpleBefore) pitaya.AfterHandler(metagameServer.simpleAfter) port := 3251 tcp := acceptor.NewTCPAcceptor(fmt.Sprintf(":%d", port)) pitaya.AddAcceptor(tcp) - pitaya.Configure(*isFrontend, *svType, pitaya.Cluster, map[string]string{}) + + config := viper.New() + + // Enable default validator + config.Set("pitaya.defaultpipelines.structvalidation.enabled", true) + pitaya.Configure(*isFrontend, *svType, pitaya.Cluster, map[string]string{}, config) pitaya.Start() }