Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Fixes

- [#4910](https://github.com/ignite/cli/pull/4910) Resolve qualified and nested RPC request messages.
- [#4909](https://github.com/ignite/cli/pull/4909) Ignore `context.Canceled` errors in Sentry reporting.

## [`v29.9.2`](https://github.com/ignite/cli/releases/tag/v29.9.2)
Expand Down
4 changes: 2 additions & 2 deletions ignite/pkg/cosmosanalysis/module/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,8 @@ func (d *moduleDiscoverer) discover(pkg protoanalysis.Package) (Module, error) {
// fill queries & messages.
for _, s := range pkg.Services {
for _, q := range s.RPCFuncs {
pkgmsg, err := pkg.MessageByName(q.RequestType)
if err != nil {
pkgmsg, ok := pkg.FindMessageByName(q.RequestType)
if !ok {
// no msg found in the proto defs corresponds to discovered sdk message.
// if it cannot be found, nothing to worry about, this means that it is used
// only internally and not open for actual use.
Expand Down
42 changes: 33 additions & 9 deletions ignite/pkg/protoanalysis/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,15 +117,7 @@ func (b builder) elementsToRPCFunc(elems []proto.Visitee) (rpcFuncs []RPCFunc) {
continue
}

var requestMessage *proto.Message

for _, message := range b.p.messages() {
if message.Name != rpc.RequestType {
continue
}
requestMessage = message
}

requestMessage := findProtoMessageByTypeName(b.p.name, b.p.messages(), rpc.RequestType)
if requestMessage == nil {
continue
}
Expand Down Expand Up @@ -159,6 +151,38 @@ func (b builder) elementsToHTTPRules(requestMessage *proto.Message, elems []prot
return
}

func findProtoMessageByTypeName(pkgName string, messages []*proto.Message, typeName string) *proto.Message {
var exactMatch *proto.Message

canonicalTypeName := canonicalMessageName(pkgName, typeName)
for _, message := range messages {
if message.Name == typeName {
exactMatch = message
}

if flattenProtoMessageName(message) == canonicalTypeName {
return message
}
}

return exactMatch
}

func flattenProtoMessageName(message *proto.Message) string {
name := message.Name
for parent := message.Parent; parent != nil; {
parentMessage, ok := parent.(*proto.Message)
if !ok {
break
}

name = fmt.Sprintf("%s_%s", parentMessage.Name, name)
parent = parentMessage.Parent
}

return name
}

// Regexp to extract HTTP rule URL parameter names.
// The expression extracts parameter names defined within "{}".
// Extra parameter arguments are ignored. These arguments are normally
Expand Down
72 changes: 68 additions & 4 deletions ignite/pkg/protoanalysis/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ type (

var regexBetaVersion = regexp.MustCompile("^v[0-9]+(beta|alpha)[0-9]+")

// ErrMessageNotFound is returned when a proto message cannot be found in a package.
var ErrMessageNotFound = errors.New("no message found")

// ModuleName retrieves the single module name of the package.
func (p Package) ModuleName() (name string) {
names := strings.Split(p.Name, ".")
Expand All @@ -53,12 +56,41 @@ func (p Package) ModuleName() (name string) {

// MessageByName finds a message by its name inside Package.
func (p Package) MessageByName(name string) (Message, error) {
for _, message := range p.Messages {
if message.Name == name {
return message, nil
message, ok := p.FindMessageByName(name)
if !ok {
return Message{}, ErrMessageNotFound
}

return message, nil
}

// FindMessageByName finds a message by its name inside Package.
// It accepts plain message names, current-package qualified names and nested message names.
func (p Package) FindMessageByName(name string) (Message, bool) {
for _, candidate := range candidateMessageNames(p.Name, name) {
for _, message := range p.Messages {
if message.Name == candidate {
return message, true
}
}
}

if !strings.Contains(strings.TrimPrefix(name, "."), ".") {
leafName := leafMessageName(name)

var leafMatches []Message
for _, message := range p.Messages {
if leafMessageName(message.Name) == leafName {
leafMatches = append(leafMatches, message)
}
}

if len(leafMatches) == 1 {
return leafMatches[0], true
}
}
return Message{}, errors.New("no message found")

return Message{}, false
}

// GoImportPath retrieves the Go import path.
Expand Down Expand Up @@ -87,6 +119,38 @@ type (
}
)

func candidateMessageNames(pkgName, name string) []string {
candidates := []string{name}

canonical := canonicalMessageName(pkgName, name)
if canonical != "" && canonical != name {
candidates = append(candidates, canonical)
}

return candidates
}

func canonicalMessageName(pkgName, name string) string {
name = strings.TrimPrefix(name, ".")
if pkgName != "" {
name = strings.TrimPrefix(name, pkgName+".")
}

return strings.ReplaceAll(name, ".", "_")
}

func leafMessageName(name string) string {
if index := strings.LastIndex(name, "_"); index >= 0 {
return name[index+1:]
}

if index := strings.LastIndex(name, "."); index >= 0 {
return name[index+1:]
}

return name
}

// Paths retrieves the list of paths from the files.
func (f Files) Paths() []string {
var paths []string
Expand Down
56 changes: 56 additions & 0 deletions ignite/pkg/protoanalysis/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,59 @@ func TestPackage_ModuleName(t *testing.T) {
})
}
}

func TestPackage_MessageByName(t *testing.T) {
pkg := Package{
Name: "foo.bar",
Messages: []Message{
{Name: "Request"},
{Name: "Outer_Inner"},
},
}

tests := []struct {
name string
messageName string
want string
wantErr error
}{
{
name: "plain name",
messageName: "Request",
want: "Request",
},
{
name: "qualified name",
messageName: ".foo.bar.Request",
want: "Request",
},
{
name: "nested qualified name",
messageName: ".foo.bar.Outer.Inner",
want: "Outer_Inner",
},
{
name: "nested leaf name",
messageName: "Inner",
want: "Outer_Inner",
},
{
name: "missing name",
messageName: "Missing",
wantErr: ErrMessageNotFound,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
message, err := pkg.MessageByName(tt.messageName)
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
return
}

require.NoError(t, err)
require.Equal(t, tt.want, message.Name)
})
}
}
15 changes: 15 additions & 0 deletions ignite/pkg/protoanalysis/protoanalysis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ func TestNestedMessages(t *testing.T) {
require.Equal(t, "A_B_C", pkg.Messages[2].Name)
}

func TestQualifiedServiceTypes(t *testing.T) {
packages, err := Parse(context.Background(), nil, "testdata/qualified_service")
require.NoError(t, err)

require.Len(t, packages, 1)
require.Len(t, packages[0].Services, 1)
require.Len(t, packages[0].Services[0].RPCFuncs, 2)
require.Equal(t, ".qualified_service.PingRequest", packages[0].Services[0].RPCFuncs[0].RequestType)
require.Equal(t, ".qualified_service.Outer.NestedRequest", packages[0].Services[0].RPCFuncs[1].RequestType)

message, err := packages[0].MessageByName(".qualified_service.Outer.NestedRequest")
require.NoError(t, err)
require.Equal(t, "Outer_NestedRequest", message.Name)
}

func TestLiquidity(t *testing.T) {
packages, err := Parse(context.Background(), nil, "testdata/liquidity")
require.NoError(t, err)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";

package qualified_service;

option go_package = "github.com/ignite/qualified_service";

service Query {
rpc Ping(.qualified_service.PingRequest) returns (.qualified_service.PingResponse);
rpc Nested(.qualified_service.Outer.NestedRequest) returns (.qualified_service.PingResponse);
}

message PingRequest {
string id = 1;
}

message PingResponse {}

message Outer {
message NestedRequest {
string id = 1;
}
}
Loading