Skip to content

Commit 7239558

Browse files
authored
feat(sidekick): discovery doc methods (#2308)
Capture path and query parameters into a synthetic message. These messages are unique per method, and are all children of a different kind of synthetic message that is unique by service. That parent message creates the right naming structure. Updated the templates to demonstrate how this would be used.
1 parent 9d21e30 commit 7239558

File tree

9 files changed

+392
-46
lines changed

9 files changed

+392
-46
lines changed

internal/sidekick/internal/api/model.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ type Service struct {
191191
DefaultHost string
192192
// The Protobuf package this service belongs to.
193193
Package string
194+
194195
// The model this service belongs to, mustache templates use this field to
195196
// navigate the data structure.
196197
Model *API
@@ -565,6 +566,24 @@ type Message struct {
565566
// IsLocalToPackage is true if the message is defined in the current
566567
// namespace.
567568
IsLocalToPackage bool
569+
// If true, this is a synthetic request message.
570+
//
571+
// These messages are created by sidekick when parsing Discovery docs and
572+
// OpenAPI specifications. The query and request parameters for each method
573+
// are grouped into a synthetic message.
574+
SyntheticRequest bool
575+
// If true, this message is only intended as a namespace to group child
576+
// messages.
577+
//
578+
// These messages are created by sidekick when parsing Discovery docs and
579+
// OpenAPI specifications. All the synthetic messages for a service need to
580+
// be grouped under a unique namespace to avoid clashes with similar
581+
// synthetic messages in other service.
582+
//
583+
// That is, `service1` and `service2` may both have a synthetic `getRequest`
584+
// message, with different attributes. We need these to be different
585+
// messages, with different names.
586+
ChildrenOnly bool
568587
// Enums associated with the Message.
569588
Enums []*Enum
570589
// Messages associated with the Message. In protobuf these are referred to as

internal/sidekick/internal/parser/discovery/methods.go

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,32 +21,57 @@ import (
2121
"github.com/googleapis/librarian/internal/sidekick/internal/api"
2222
)
2323

24-
func makeServiceMethods(model *api.API, serviceID string, doc *document, resource *resource) ([]*api.Method, error) {
25-
var methods []*api.Method
24+
func makeServiceMethods(model *api.API, service *api.Service, doc *document, resource *resource) error {
25+
// It is Okay to reuse the ID, sidekick uses different the namespaces
26+
// for messages vs. services.
27+
parent := &api.Message{
28+
Name: service.Name,
29+
ID: service.ID,
30+
Package: service.Package,
31+
Documentation: fmt.Sprintf("Synthetic messages for the [%s][%s] service", service.Name, service.ID[1:]),
32+
ChildrenOnly: true,
33+
}
34+
model.State.MessageByID[parent.ID] = parent
35+
model.Messages = append(model.Messages, parent)
2636
for _, input := range resource.Methods {
27-
method, err := makeMethod(model, serviceID, doc, input)
37+
method, err := makeMethod(model, parent, doc, input)
2838
if err != nil {
29-
return nil, err
39+
return err
3040
}
31-
methods = append(methods, method)
41+
model.State.MethodByID[method.ID] = method
42+
service.Methods = append(service.Methods, method)
3243
}
3344

34-
return methods, nil
45+
return nil
3546
}
3647

37-
func makeMethod(model *api.API, serviceID string, doc *document, input *method) (*api.Method, error) {
38-
id := fmt.Sprintf("%s.%s", serviceID, input.Name)
48+
func makeMethod(model *api.API, parent *api.Message, doc *document, input *method) (*api.Method, error) {
49+
id := fmt.Sprintf("%s.%s", parent.ID, input.Name)
3950
if input.MediaUpload != nil {
4051
return nil, fmt.Errorf("media upload methods are not supported, id=%s", id)
4152
}
42-
inputID, err := getMethodType(model, id, "request type", input.Request)
53+
bodyID, err := getMethodType(model, id, "request type", input.Request)
4354
if err != nil {
4455
return nil, err
4556
}
4657
outputID, err := getMethodType(model, id, "response type", input.Response)
4758
if err != nil {
4859
return nil, err
4960
}
61+
62+
// Discovery doc methods get a synthetic request message.
63+
requestMessage := &api.Message{
64+
Name: fmt.Sprintf("%sRequest", input.Name),
65+
ID: fmt.Sprintf("%s.%sRequest", parent.ID, input.Name),
66+
Package: model.PackageName,
67+
SyntheticRequest: true,
68+
Documentation: fmt.Sprintf("Synthetic request message for the [%s()][%s] method.", input.Name, id[1:]),
69+
Parent: parent,
70+
// TODO(#2268) - deprecated if method is deprecated.
71+
}
72+
model.State.MessageByID[requestMessage.ID] = requestMessage
73+
parent.Messages = append(parent.Messages, requestMessage)
74+
5075
var uriTemplate string
5176
if strings.HasSuffix(doc.ServicePath, "/") {
5277
uriTemplate = fmt.Sprintf("%s%s", doc.ServicePath, input.Path)
@@ -58,33 +83,73 @@ func makeMethod(model *api.API, serviceID string, doc *document, input *method)
5883
if err != nil {
5984
return nil, err
6085
}
86+
6187
binding := &api.PathBinding{
6288
Verb: input.HTTPMethod,
6389
PathTemplate: path,
6490
QueryParameters: map[string]bool{},
6591
}
92+
fieldNames := map[string]bool{}
6693
for _, p := range input.Parameters {
6794
if p.Location != "path" {
6895
binding.QueryParameters[p.Name] = true
6996
}
97+
prop := &property{
98+
Name: p.Name,
99+
Schema: &p.schema,
100+
}
101+
field, err := makeField(fmt.Sprintf(requestMessage.ID, id), prop)
102+
if err != nil {
103+
return nil, err
104+
}
105+
field.Synthetic = true
106+
field.Optional = !p.Required
107+
requestMessage.Fields = append(requestMessage.Fields, field)
108+
fieldNames[field.Name] = true
70109
}
110+
111+
bodyPathField := ""
112+
if bodyID != ".google.protobuf.Empty" {
113+
name, err := bodyFieldName(fieldNames)
114+
if err != nil {
115+
return nil, err
116+
}
117+
body := &api.Field{
118+
Documentation: fmt.Sprintf("Synthetic request body field for the [%s()][%s] method.", input.Name, id[1:]),
119+
Name: name,
120+
JSONName: name,
121+
ID: fmt.Sprintf("%s.%s", requestMessage.ID, name),
122+
Typez: api.MESSAGE_TYPE,
123+
TypezID: bodyID,
124+
Optional: true,
125+
}
126+
requestMessage.Fields = append(requestMessage.Fields, body)
127+
bodyPathField = name
128+
}
129+
71130
method := &api.Method{
72131
ID: id,
73132
Name: input.Name,
74133
Documentation: input.Description,
75-
// TODO(#1850) - handle deprecated methods
134+
// TODO(#2268) - handle deprecated methods
76135
// Deprecated: ...,
77-
InputTypeID: inputID,
136+
InputTypeID: requestMessage.ID,
78137
OutputTypeID: outputID,
79138
PathInfo: &api.PathInfo{
80139
Bindings: []*api.PathBinding{binding},
81-
BodyFieldPath: "*",
140+
BodyFieldPath: bodyPathField,
82141
},
83142
}
84-
model.State.MethodByID[id] = method
85143
return method, nil
86144
}
87145

146+
func bodyFieldName(fieldNames map[string]bool) (string, error) {
147+
if _, ok := fieldNames["body"]; ok {
148+
return "", fmt.Errorf("body is a request or path parameter")
149+
}
150+
return "body", nil
151+
}
152+
88153
func getMethodType(model *api.API, methodID, name string, typez *schema) (string, error) {
89154
if typez == nil {
90155
return ".google.protobuf.Empty", nil

0 commit comments

Comments
 (0)