feat: Validate Msg proto annotations (#13793)
Co-authored-by: Marko <marbar3778@yahoo.com>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package msgservice
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
msg "cosmossdk.io/api/cosmos/msg/v1"
|
||||
)
|
||||
|
||||
// ValidateAnnotations validates that the proto annotations are correct.
|
||||
// More specifically, it verifies:
|
||||
// - all services named "Msg" have `(cosmos.msg.v1.service) = true`,
|
||||
//
|
||||
// More validations can be added here in the future.
|
||||
//
|
||||
// If `protoFiles` is nil, then protoregistry.GlobalFile will be used.
|
||||
func ValidateProtoAnnotations(protoFiles *protoregistry.Files) error {
|
||||
if protoFiles == nil {
|
||||
protoFiles = protoregistry.GlobalFiles
|
||||
}
|
||||
|
||||
var serviceErrs []error
|
||||
protoFiles.RangeFiles(func(fd protoreflect.FileDescriptor) bool {
|
||||
for i := 0; i < fd.Services().Len(); i++ {
|
||||
sd := fd.Services().Get(i)
|
||||
if sd.Name() == "Msg" {
|
||||
// We use the heuristic that services name Msg are exactly the
|
||||
// ones that need the proto annotations check.
|
||||
err := validateMsgServiceAnnotations(protoFiles, sd)
|
||||
if err != nil {
|
||||
serviceErrs = append(serviceErrs, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return errors.Join(serviceErrs...)
|
||||
}
|
||||
|
||||
// validateMsgServiceAnnotations validates that the service has the
|
||||
// `(cosmos.msg.v1.service) = true` proto annotation.
|
||||
func validateMsgServiceAnnotations(protoFiles *protoregistry.Files, sd protoreflect.ServiceDescriptor) error {
|
||||
ext := proto.GetExtension(sd.Options(), msg.E_Service)
|
||||
isService, ok := ext.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("expected bool, got %T", ext)
|
||||
}
|
||||
|
||||
if !isService {
|
||||
return fmt.Errorf("service %s does not have cosmos.msg.v1.service proto annotation", sd.FullName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package msgservice
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
"google.golang.org/protobuf/reflect/protoregistry"
|
||||
|
||||
_ "cosmossdk.io/api/cosmos/bank/v1beta1"
|
||||
)
|
||||
|
||||
func TestValidateServiceAnnotations(t *testing.T) {
|
||||
// Find an arbitrary query service that hasn't the service=true annotation.
|
||||
sd, err := protoregistry.GlobalFiles.FindDescriptorByName("cosmos.bank.v1beta1.Query")
|
||||
require.NoError(t, err)
|
||||
err = validateMsgServiceAnnotations(nil, sd.(protoreflect.ServiceDescriptor))
|
||||
require.Error(t, err)
|
||||
|
||||
sd, err = protoregistry.GlobalFiles.FindDescriptorByName("cosmos.bank.v1beta1.Msg")
|
||||
require.NoError(t, err)
|
||||
err = validateMsgServiceAnnotations(nil, sd.(protoreflect.ServiceDescriptor))
|
||||
require.NoError(t, err)
|
||||
}
|
||||
Reference in New Issue
Block a user