Skip to content

Commit 043cf65

Browse files
authored
feat(sidekick): inline messages in discovery docs (#2394)
1 parent 862c7d7 commit 043cf65

File tree

3 files changed

+298
-10
lines changed

3 files changed

+298
-10
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package discovery
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/googleapis/librarian/internal/sidekick/internal/api"
21+
)
22+
23+
func maybeInlineObjectField(model *api.API, parent *api.Message, name string, input *schema) (*api.Field, error) {
24+
if input.Type != "object" || input.Properties == nil {
25+
return nil, nil
26+
}
27+
id := fmt.Sprintf("%s.%s", parent.ID, name)
28+
documentation := fmt.Sprintf("The message type for the [%s][%s] field.", name, id[1:])
29+
message := &api.Message{
30+
Name: name,
31+
ID: id,
32+
Package: parent.Package,
33+
Documentation: documentation,
34+
Parent: parent,
35+
}
36+
if err := makeMessageFields(model, message, input); err != nil {
37+
return nil, err
38+
}
39+
parent.Messages = append(parent.Messages, message)
40+
model.State.MessageByID[id] = message
41+
42+
field := &api.Field{
43+
Name: name,
44+
JSONName: name,
45+
ID: id,
46+
Documentation: input.Description,
47+
Optional: true,
48+
Typez: api.MESSAGE_TYPE,
49+
TypezID: id,
50+
}
51+
return field, nil
52+
}
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package discovery
16+
17+
import (
18+
"testing"
19+
20+
"github.com/google/go-cmp/cmp"
21+
"github.com/googleapis/librarian/internal/sidekick/internal/api"
22+
"github.com/googleapis/librarian/internal/sidekick/internal/api/apitest"
23+
)
24+
25+
func TestMaybeInlineObject(t *testing.T) {
26+
model := api.NewTestAPI([]*api.Message{}, []*api.Enum{}, []*api.Service{})
27+
model.PackageName = "package"
28+
input := &schema{
29+
Type: "object",
30+
Description: "A field with an inline object.",
31+
Properties: []*property{
32+
{
33+
Name: "stringField",
34+
Schema: &schema{
35+
Type: "string",
36+
Description: "The stringField field.",
37+
},
38+
},
39+
{
40+
Name: "intField",
41+
Schema: &schema{
42+
Type: "string",
43+
Format: "uint64",
44+
Description: "The intField field.",
45+
},
46+
},
47+
},
48+
}
49+
message := &api.Message{ID: ".package.Message"}
50+
field, err := maybeInlineObjectField(model, message, "inline", input)
51+
if err != nil {
52+
t.Fatal(err)
53+
}
54+
55+
wantField := &api.Field{
56+
Name: "inline",
57+
JSONName: "inline",
58+
ID: ".package.Message.inline",
59+
Documentation: "A field with an inline object.",
60+
Optional: true,
61+
Typez: api.MESSAGE_TYPE,
62+
TypezID: ".package.Message.inline",
63+
}
64+
if diff := cmp.Diff(wantField, field); diff != "" {
65+
t.Errorf("mismatch (-want, +got):\n%s", diff)
66+
}
67+
68+
wantInlineMessage := &api.Message{
69+
ID: ".package.Message.inline",
70+
Name: "inline",
71+
Documentation: "The message type for the [inline][package.Message.inline] field.",
72+
Fields: []*api.Field{
73+
{
74+
Name: "stringField",
75+
JSONName: "stringField",
76+
ID: ".package.Message.inline.stringField",
77+
Documentation: "The stringField field.",
78+
Typez: api.STRING_TYPE,
79+
TypezID: "string",
80+
},
81+
{
82+
Name: "intField",
83+
JSONName: "intField",
84+
ID: ".package.Message.inline.intField",
85+
Documentation: "The intField field.",
86+
Typez: api.UINT64_TYPE,
87+
TypezID: "uint64",
88+
},
89+
},
90+
Parent: message,
91+
}
92+
gotInlineMessage, ok := model.State.MessageByID[wantInlineMessage.ID]
93+
if !ok {
94+
t.Fatalf("missing inline message %s", wantInlineMessage.ID)
95+
}
96+
apitest.CheckMessage(t, gotInlineMessage, wantInlineMessage)
97+
if gotInlineMessage.Parent != message {
98+
t.Errorf("mismatched parent in inline message, got=%v, want=%v", gotInlineMessage.Parent, message)
99+
}
100+
}
101+
102+
func TestArrayWithInlineObject(t *testing.T) {
103+
model := api.NewTestAPI([]*api.Message{}, []*api.Enum{}, []*api.Service{})
104+
model.PackageName = "package"
105+
input := &property{
106+
Name: "arrayWithObject",
107+
Schema: &schema{
108+
Type: "array",
109+
Description: "An array field with an inline object.",
110+
ItemSchema: &schema{
111+
Type: "object",
112+
Properties: []*property{
113+
{
114+
Name: "stringField",
115+
Schema: &schema{
116+
Type: "string",
117+
Description: "The stringField field.",
118+
},
119+
},
120+
{
121+
Name: "intField",
122+
Schema: &schema{
123+
Type: "string",
124+
Format: "uint64",
125+
Description: "The intField field.",
126+
},
127+
},
128+
},
129+
},
130+
},
131+
}
132+
message := &api.Message{ID: ".package.Message"}
133+
field, err := makeArrayField(model, message, input)
134+
if err != nil {
135+
t.Fatal(err)
136+
}
137+
138+
wantField := &api.Field{
139+
Name: "arrayWithObject",
140+
JSONName: "arrayWithObject",
141+
ID: ".package.Message.arrayWithObject",
142+
Documentation: "An array field with an inline object.",
143+
Repeated: true,
144+
Typez: api.MESSAGE_TYPE,
145+
TypezID: ".package.Message.arrayWithObject",
146+
}
147+
if diff := cmp.Diff(wantField, field); diff != "" {
148+
t.Errorf("mismatch (-want, +got):\n%s", diff)
149+
}
150+
151+
wantInlineMessage := &api.Message{
152+
ID: ".package.Message.arrayWithObject",
153+
Name: "arrayWithObject",
154+
Documentation: "The message type for the [arrayWithObject][package.Message.arrayWithObject] field.",
155+
Fields: []*api.Field{
156+
{
157+
Name: "stringField",
158+
JSONName: "stringField",
159+
ID: ".package.Message.arrayWithObject.stringField",
160+
Documentation: "The stringField field.",
161+
Typez: api.STRING_TYPE,
162+
TypezID: "string",
163+
},
164+
{
165+
Name: "intField",
166+
JSONName: "intField",
167+
ID: ".package.Message.arrayWithObject.intField",
168+
Documentation: "The intField field.",
169+
Typez: api.UINT64_TYPE,
170+
TypezID: "uint64",
171+
},
172+
},
173+
Parent: message,
174+
}
175+
gotInlineMessage, ok := model.State.MessageByID[wantInlineMessage.ID]
176+
if !ok {
177+
t.Fatalf("missing inline message %s", wantInlineMessage.ID)
178+
}
179+
apitest.CheckMessage(t, gotInlineMessage, wantInlineMessage)
180+
if gotInlineMessage.Parent != message {
181+
t.Errorf("mismatched parent in inline message, got=%v, want=%v", gotInlineMessage.Parent, message)
182+
}
183+
}
184+
185+
func TestMaybeInlineObjectErrors(t *testing.T) {
186+
model := api.NewTestAPI([]*api.Message{}, []*api.Enum{}, []*api.Service{})
187+
model.PackageName = "package"
188+
input := &schema{
189+
Type: "object",
190+
Description: "A field with an inline object.",
191+
Properties: []*property{
192+
{
193+
Name: "badField",
194+
Schema: &schema{
195+
Type: "string",
196+
Format: "--invalid--",
197+
},
198+
},
199+
},
200+
}
201+
message := &api.Message{ID: ".package.Message"}
202+
if field, err := maybeInlineObjectField(model, message, "inline", input); err == nil {
203+
t.Errorf("expected an error with an invalid inline object, got=%v", field)
204+
}
205+
}
206+
207+
func TestArrayWithInlineObjectError(t *testing.T) {
208+
model := api.NewTestAPI([]*api.Message{}, []*api.Enum{}, []*api.Service{})
209+
model.PackageName = "package"
210+
input := &property{
211+
Name: "arrayWithObject",
212+
Schema: &schema{
213+
Type: "array",
214+
Description: "An array field with an inline object.",
215+
ItemSchema: &schema{
216+
Type: "object",
217+
Properties: []*property{
218+
{
219+
Name: "stringField",
220+
Schema: &schema{
221+
Type: "string",
222+
Format: "--invalid--",
223+
Description: "The stringField field.",
224+
},
225+
},
226+
},
227+
},
228+
},
229+
}
230+
message := &api.Message{ID: ".package.Message"}
231+
if field, err := makeArrayField(model, message, input); err == nil {
232+
t.Errorf("expected an error with an invalid inline object, got=%v", field)
233+
}
234+
}

internal/sidekick/internal/parser/discovery/message_fields.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,6 @@ func makeMessageFields(model *api.API, message *api.Message, schema *schema) err
2626
if err != nil {
2727
return err
2828
}
29-
if field == nil {
30-
continue
31-
}
3229
message.Fields = append(message.Fields, field)
3330
}
3431
return nil
@@ -41,19 +38,24 @@ func makeField(model *api.API, message *api.Message, input *property) (*api.Fiel
4138
if field, err := maybeMapField(model, message, input); err != nil || field != nil {
4239
return field, err
4340
}
44-
if input.Schema.Type == "object" && input.Schema.Properties != nil {
45-
// TODO(#2265) - handle inline object...
46-
return nil, nil
41+
if field, err := maybeInlineObjectField(model, message, input.Name, input.Schema); err != nil || field != nil {
42+
return field, err
4743
}
4844
return makeScalarField(model, message, input.Name, input.Schema)
4945
}
5046

5147
func makeArrayField(model *api.API, message *api.Message, input *property) (*api.Field, error) {
52-
if input.Schema.ItemSchema.Type == "object" && input.Schema.ItemSchema.Properties != nil {
53-
// TODO(#2265) - handle inline object...
54-
return nil, nil
48+
field, err := maybeInlineObjectField(model, message, input.Name, input.Schema.ItemSchema)
49+
if err != nil {
50+
return nil, err
51+
}
52+
if field != nil {
53+
field.Documentation = input.Schema.Description
54+
field.Repeated = true
55+
field.Optional = false
56+
return field, nil
5557
}
56-
field, err := makeScalarField(model, message, input.Name, input.Schema.ItemSchema)
58+
field, err = makeScalarField(model, message, input.Name, input.Schema.ItemSchema)
5759
if err != nil {
5860
return nil, err
5961
}

0 commit comments

Comments
 (0)