Skip to content

Commit 68541f3

Browse files
authored
fix(protoanalysis): resolve qualified and nested RPC request messages (#4910)
* fix(protoanalysis): resolve qualified and nested RPC request messages * add changelog
1 parent 8200a6c commit 68541f3

7 files changed

Lines changed: 197 additions & 15 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Fixes
66

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

910
## [`v29.9.2`](https://github.com/ignite/cli/releases/tag/v29.9.2)

ignite/pkg/cosmosanalysis/module/module.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ func (d *moduleDiscoverer) discover(pkg protoanalysis.Package) (Module, error) {
269269
// fill queries & messages.
270270
for _, s := range pkg.Services {
271271
for _, q := range s.RPCFuncs {
272-
pkgmsg, err := pkg.MessageByName(q.RequestType)
273-
if err != nil {
272+
pkgmsg, ok := pkg.FindMessageByName(q.RequestType)
273+
if !ok {
274274
// no msg found in the proto defs corresponds to discovered sdk message.
275275
// if it cannot be found, nothing to worry about, this means that it is used
276276
// only internally and not open for actual use.

ignite/pkg/protoanalysis/builder.go

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,7 @@ func (b builder) elementsToRPCFunc(elems []proto.Visitee) (rpcFuncs []RPCFunc) {
117117
continue
118118
}
119119

120-
var requestMessage *proto.Message
121-
122-
for _, message := range b.p.messages() {
123-
if message.Name != rpc.RequestType {
124-
continue
125-
}
126-
requestMessage = message
127-
}
128-
120+
requestMessage := findProtoMessageByTypeName(b.p.name, b.p.messages(), rpc.RequestType)
129121
if requestMessage == nil {
130122
continue
131123
}
@@ -159,6 +151,38 @@ func (b builder) elementsToHTTPRules(requestMessage *proto.Message, elems []prot
159151
return
160152
}
161153

154+
func findProtoMessageByTypeName(pkgName string, messages []*proto.Message, typeName string) *proto.Message {
155+
var exactMatch *proto.Message
156+
157+
canonicalTypeName := canonicalMessageName(pkgName, typeName)
158+
for _, message := range messages {
159+
if message.Name == typeName {
160+
exactMatch = message
161+
}
162+
163+
if flattenProtoMessageName(message) == canonicalTypeName {
164+
return message
165+
}
166+
}
167+
168+
return exactMatch
169+
}
170+
171+
func flattenProtoMessageName(message *proto.Message) string {
172+
name := message.Name
173+
for parent := message.Parent; parent != nil; {
174+
parentMessage, ok := parent.(*proto.Message)
175+
if !ok {
176+
break
177+
}
178+
179+
name = fmt.Sprintf("%s_%s", parentMessage.Name, name)
180+
parent = parentMessage.Parent
181+
}
182+
183+
return name
184+
}
185+
162186
// Regexp to extract HTTP rule URL parameter names.
163187
// The expression extracts parameter names defined within "{}".
164188
// Extra parameter arguments are ignored. These arguments are normally

ignite/pkg/protoanalysis/package.go

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type (
3939

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

42+
// ErrMessageNotFound is returned when a proto message cannot be found in a package.
43+
var ErrMessageNotFound = errors.New("no message found")
44+
4245
// ModuleName retrieves the single module name of the package.
4346
func (p Package) ModuleName() (name string) {
4447
names := strings.Split(p.Name, ".")
@@ -53,12 +56,41 @@ func (p Package) ModuleName() (name string) {
5356

5457
// MessageByName finds a message by its name inside Package.
5558
func (p Package) MessageByName(name string) (Message, error) {
56-
for _, message := range p.Messages {
57-
if message.Name == name {
58-
return message, nil
59+
message, ok := p.FindMessageByName(name)
60+
if !ok {
61+
return Message{}, ErrMessageNotFound
62+
}
63+
64+
return message, nil
65+
}
66+
67+
// FindMessageByName finds a message by its name inside Package.
68+
// It accepts plain message names, current-package qualified names and nested message names.
69+
func (p Package) FindMessageByName(name string) (Message, bool) {
70+
for _, candidate := range candidateMessageNames(p.Name, name) {
71+
for _, message := range p.Messages {
72+
if message.Name == candidate {
73+
return message, true
74+
}
75+
}
76+
}
77+
78+
if !strings.Contains(strings.TrimPrefix(name, "."), ".") {
79+
leafName := leafMessageName(name)
80+
81+
var leafMatches []Message
82+
for _, message := range p.Messages {
83+
if leafMessageName(message.Name) == leafName {
84+
leafMatches = append(leafMatches, message)
85+
}
86+
}
87+
88+
if len(leafMatches) == 1 {
89+
return leafMatches[0], true
5990
}
6091
}
61-
return Message{}, errors.New("no message found")
92+
93+
return Message{}, false
6294
}
6395

6496
// GoImportPath retrieves the Go import path.
@@ -87,6 +119,38 @@ type (
87119
}
88120
)
89121

122+
func candidateMessageNames(pkgName, name string) []string {
123+
candidates := []string{name}
124+
125+
canonical := canonicalMessageName(pkgName, name)
126+
if canonical != "" && canonical != name {
127+
candidates = append(candidates, canonical)
128+
}
129+
130+
return candidates
131+
}
132+
133+
func canonicalMessageName(pkgName, name string) string {
134+
name = strings.TrimPrefix(name, ".")
135+
if pkgName != "" {
136+
name = strings.TrimPrefix(name, pkgName+".")
137+
}
138+
139+
return strings.ReplaceAll(name, ".", "_")
140+
}
141+
142+
func leafMessageName(name string) string {
143+
if index := strings.LastIndex(name, "_"); index >= 0 {
144+
return name[index+1:]
145+
}
146+
147+
if index := strings.LastIndex(name, "."); index >= 0 {
148+
return name[index+1:]
149+
}
150+
151+
return name
152+
}
153+
90154
// Paths retrieves the list of paths from the files.
91155
func (f Files) Paths() []string {
92156
var paths []string

ignite/pkg/protoanalysis/package_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,59 @@ func TestPackage_ModuleName(t *testing.T) {
8080
})
8181
}
8282
}
83+
84+
func TestPackage_MessageByName(t *testing.T) {
85+
pkg := Package{
86+
Name: "foo.bar",
87+
Messages: []Message{
88+
{Name: "Request"},
89+
{Name: "Outer_Inner"},
90+
},
91+
}
92+
93+
tests := []struct {
94+
name string
95+
messageName string
96+
want string
97+
wantErr error
98+
}{
99+
{
100+
name: "plain name",
101+
messageName: "Request",
102+
want: "Request",
103+
},
104+
{
105+
name: "qualified name",
106+
messageName: ".foo.bar.Request",
107+
want: "Request",
108+
},
109+
{
110+
name: "nested qualified name",
111+
messageName: ".foo.bar.Outer.Inner",
112+
want: "Outer_Inner",
113+
},
114+
{
115+
name: "nested leaf name",
116+
messageName: "Inner",
117+
want: "Outer_Inner",
118+
},
119+
{
120+
name: "missing name",
121+
messageName: "Missing",
122+
wantErr: ErrMessageNotFound,
123+
},
124+
}
125+
126+
for _, tt := range tests {
127+
t.Run(tt.name, func(t *testing.T) {
128+
message, err := pkg.MessageByName(tt.messageName)
129+
if tt.wantErr != nil {
130+
require.ErrorIs(t, err, tt.wantErr)
131+
return
132+
}
133+
134+
require.NoError(t, err)
135+
require.Equal(t, tt.want, message.Name)
136+
})
137+
}
138+
}

ignite/pkg/protoanalysis/protoanalysis_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,21 @@ func TestNestedMessages(t *testing.T) {
1717
require.Equal(t, "A_B_C", pkg.Messages[2].Name)
1818
}
1919

20+
func TestQualifiedServiceTypes(t *testing.T) {
21+
packages, err := Parse(context.Background(), nil, "testdata/qualified_service")
22+
require.NoError(t, err)
23+
24+
require.Len(t, packages, 1)
25+
require.Len(t, packages[0].Services, 1)
26+
require.Len(t, packages[0].Services[0].RPCFuncs, 2)
27+
require.Equal(t, ".qualified_service.PingRequest", packages[0].Services[0].RPCFuncs[0].RequestType)
28+
require.Equal(t, ".qualified_service.Outer.NestedRequest", packages[0].Services[0].RPCFuncs[1].RequestType)
29+
30+
message, err := packages[0].MessageByName(".qualified_service.Outer.NestedRequest")
31+
require.NoError(t, err)
32+
require.Equal(t, "Outer_NestedRequest", message.Name)
33+
}
34+
2035
func TestLiquidity(t *testing.T) {
2136
packages, err := Parse(context.Background(), nil, "testdata/liquidity")
2237
require.NoError(t, err)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
syntax = "proto3";
2+
3+
package qualified_service;
4+
5+
option go_package = "github.com/ignite/qualified_service";
6+
7+
service Query {
8+
rpc Ping(.qualified_service.PingRequest) returns (.qualified_service.PingResponse);
9+
rpc Nested(.qualified_service.Outer.NestedRequest) returns (.qualified_service.PingResponse);
10+
}
11+
12+
message PingRequest {
13+
string id = 1;
14+
}
15+
16+
message PingResponse {}
17+
18+
message Outer {
19+
message NestedRequest {
20+
string id = 1;
21+
}
22+
}

0 commit comments

Comments
 (0)