diff --git a/internal/gengapic/example.go b/internal/gengapic/example.go index a8621eb6251..756ad08865c 100644 --- a/internal/gengapic/example.go +++ b/internal/gengapic/example.go @@ -20,6 +20,7 @@ import ( longrunning "cloud.google.com/go/longrunning/autogen/longrunningpb" "github.com/googleapis/gapic-generator-go/internal/pbinfo" + "google.golang.org/genproto/googleapis/api/annotations" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/descriptorpb" ) @@ -96,35 +97,40 @@ func (g *generator) exampleMethod(pkgName, servName string, m *descriptorpb.Meth return nil } +// TODO(codyoss): This if can be removed once the public protos +// have been migrated to their new package. This should be soon after this +// code is merged. +func temporarilyHardcodeInSpecPath(inSpec pbinfo.ImportSpec) pbinfo.ImportSpec { + if inSpec.Path == "google.golang.org/genproto/googleapis/longrunning" { + inSpec.Path = "cloud.google.com/go/longrunning/autogen/longrunningpb" + } else if inSpec.Path == "google.golang.org/genproto/googleapis/iam/v1" { + inSpec.Path = "cloud.google.com/go/iam/apiv1/iampb" + } + return inSpec +} + func (g *generator) exampleMethodBody(pkgName, servName string, m *descriptorpb.MethodDescriptorProto) error { + p := g.printf + if m.GetClientStreaming() != m.GetServerStreaming() { // TODO(pongad): implement this correctly. return nil } - p := g.printf - + // m.GetInputType() returns a string in the format + // `.google.cloud...`. inType := g.descInfo.Type[m.GetInputType()] if inType == nil { - return fmt.Errorf("cannot find type %q, malformed descriptor?", m.GetInputType()) + return fmt.Errorf("exampleMethodBody: cannot find type %q, malformed descriptor?", m.GetInputType()) } inSpec, err := g.descInfo.ImportSpec(inType) if err != nil { return err } - // TODO(codyoss): This if can be removed once the public protos - // have been migrated to their new package. This should be soon after this - // code is merged. - if inSpec.Path == "google.golang.org/genproto/googleapis/longrunning" { - inSpec.Path = "cloud.google.com/go/longrunning/autogen/longrunningpb" - } else if inSpec.Path == "google.golang.org/genproto/googleapis/iam/v1" { - inSpec.Path = "cloud.google.com/go/iam/apiv1/iampb" - } - - httpInfo := getHTTPInfo(m) - + inSpec = temporarilyHardcodeInSpecPath(inSpec) g.imports[inSpec] = true + // Pick the first transport for simplicity. We don't need examples // of each method for both transports when they have the same surface. t := g.opts.transports[0] @@ -134,14 +140,19 @@ func (g *generator) exampleMethodBody(pkgName, servName string, m *descriptorpb. } g.exampleInitClient(pkgName, s) + // name := "projects/{project-id}/books/{book}" + a := inType.(*descriptorpb.DescriptorProto) + requestName := g.constructRequestName(a) if !m.GetClientStreaming() && !m.GetServerStreaming() { p("") p("req := &%s.%s{", inSpec.Name, inType.GetName()) p(" // TODO: Fill request struct fields.") p(" // See https://pkg.go.dev/%s#%s.", inSpec.Path, inType.GetName()) + p(" Name: %q", requestName) p("}") } + httpInfo := getHTTPInfo(m) pf, _, err := g.getPagingFields(m) if err != nil { return err @@ -159,10 +170,27 @@ func (g *generator) exampleMethodBody(pkgName, servName string, m *descriptorpb. } else { g.exampleUnaryCall(m) } - return nil } +func (g *generator) constructRequestName(method *descriptorpb.DescriptorProto) string { + a := getResourceReference(method) + parts := strings.Split(a, ".") + typName := fmt.Sprintf(".google.cloud.%s.v1.%s", parts[0], strings.Split(parts[2], "/")[1]) + inType2 := g.descInfo.Type[typName] + b := inType2.(*descriptorpb.DescriptorProto) + + // GetResource + r := proto.GetExtension(b.GetOptions(), annotations.E_Resource) + resource := r.(*annotations.ResourceDescriptor) + return fmt.Sprintf("projects/{project-id}/%s", resource.Pattern[0]) +} + +func getResourceReference(inType *descriptorpb.DescriptorProto) string { + r := proto.GetExtension(inType.Field[0].GetOptions(), annotations.E_ResourceReference) + return r.(*annotations.ResourceReference).Type +} + func (g *generator) exampleLROCall(m *descriptorpb.MethodDescriptorProto) { p := g.printf retVars := "resp, err :=" diff --git a/internal/gengapic/pattern_test.go b/internal/gengapic/pattern_test.go new file mode 100644 index 00000000000..945d050321b --- /dev/null +++ b/internal/gengapic/pattern_test.go @@ -0,0 +1,127 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package gengapic + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/googleapis/gapic-generator-go/internal/pbinfo" + "github.com/googleapis/gapic-generator-go/internal/snippets" + "github.com/googleapis/gapic-generator-go/internal/testing/sample" + "github.com/googleapis/gapic-generator-go/internal/txtdiff" + "google.golang.org/genproto/googleapis/api/annotations" + "google.golang.org/genproto/googleapis/api/serviceconfig" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/descriptorpb" + "google.golang.org/protobuf/types/known/apipb" +) + +// TestExampleMethodBody_Pattern tests +// https://github.com/googleapis/gapic-generator-go/issues/1372, using the +// example in +// https://github.com/googleapis/gapic-generator-go/issues/1372#issuecomment-1633101248. +func TestExampleMethodBody_Pattern(t *testing.T) { + g := generator{ + serviceConfig: &serviceconfig.Service{ + Apis: []*apipb.Api{ + { + Name: fmt.Sprintf("%s.%s", sample.ProtoPackagePath, sample.ServiceName), + }, + }, + }, + imports: map[pbinfo.ImportSpec]bool{ + {Path: "context"}: true, + {Name: sample.GoProtoPackageName, Path: sample.GoProtoPackagePath}: true, + }, + snippetMetadata: snippets.NewMetadata(sample.ProtoPackagePath, sample.GoPackagePath, sample.GoPackageName), + descInfo: pbinfo.Of([]*descriptorpb.FileDescriptorProto{}), + opts: &options{ + pkgName: sample.GoPackageName, + transports: []transport{grpc, rest}, + }, + } + g.snippetMetadata.AddService(sample.ServiceName, sample.ProtoPackagePath) + + serv := &descriptorpb.ServiceDescriptorProto{ + Name: proto.String(sample.ServiceName), + Method: []*descriptorpb.MethodDescriptorProto{ + { + Name: proto.String(sample.GetRequest), + InputType: proto.String(sample.DescriptorInfoTypeName(sample.GetRequest)), + OutputType: proto.String(sample.DescriptorInfoTypeName(sample.Resource)), + }, + }, + } + + name := "name" + inputType := &descriptorpb.DescriptorProto{ + Name: proto.String(sample.GetRequest), + Field: []*descriptorpb.FieldDescriptorProto{ + { + Name: &name, + Options: &descriptorpb.FieldOptions{}, + }, + }, + } + proto.SetExtension( + inputType.Field[0].GetOptions(), + annotations.E_ResourceReference, + &annotations.ResourceReference{ + Type: sample.ResourceType, + }, + ) + + outputType := &descriptorpb.DescriptorProto{ + Name: proto.String(sample.Resource), + Field: []*descriptorpb.FieldDescriptorProto{ + { + Name: &name, + }, + }, + Options: &descriptorpb.MessageOptions{}, + } + proto.SetExtension( + outputType.GetOptions(), + annotations.E_Resource, + &annotations.ResourceDescriptor{ + Type: sample.ResourceType, + Pattern: []string{sample.ResourcePattern}, + }) + + for _, typ := range []*descriptorpb.DescriptorProto{ + inputType, + outputType, + } { + typName := sample.DescriptorInfoTypeName(typ.GetName()) + g.descInfo.Type[typName] = typ + g.descInfo.ParentFile[typ] = &descriptorpb.FileDescriptorProto{ + Options: &descriptorpb.FileOptions{ + GoPackage: proto.String(sample.GoProtoPackagePath), + }, + Package: proto.String(sample.ProtoPackagePath), + } + } + + for _, m := range serv.Method { + if err := g.genSnippetFile(serv, m); err != nil { + t.Fatal(err) + } + } + g.commit(filepath.Join("cloud.google.com/go", "internal", "generated", "snippets", sample.GoPackageName, "main.go"), "main") + got := *g.resp.File[1].Content + txtdiff.Diff(t, got, filepath.Join("testdata", "snippet_Library.want")) +} diff --git a/internal/gengapic/snippets.go b/internal/gengapic/snippets.go index 25a0ceb145a..1ab61283db9 100644 --- a/internal/gengapic/snippets.go +++ b/internal/gengapic/snippets.go @@ -100,7 +100,7 @@ func (g *generator) genAndCommitSnippets(s *descriptorpb.ServiceDescriptorProto) return nil } -// genSnippetFile generates a single RPC snippet by leveraging exampleMethodBody in gengapic/example.go. +// genSnippetFile generates a RPC snippet for a given method body. func (g *generator) genSnippetFile(s *descriptorpb.ServiceDescriptorProto, m *descriptorpb.MethodDescriptorProto) error { regionTag := g.snippetMetadata.RegionTag(s.GetName(), m.GetName()) g.headerComment(fmt.Sprintf("[START %s]", regionTag)) diff --git a/internal/gengapic/testdata/snippet_Library.want b/internal/gengapic/testdata/snippet_Library.want new file mode 100644 index 00000000000..7a6056e349e --- /dev/null +++ b/internal/gengapic/testdata/snippet_Library.want @@ -0,0 +1,27 @@ +func main() { + ctx := context.Background() + // This snippet has been automatically generated and should be regarded as a code template only. + // It will require modifications to work: + // - It may require correct/in-range values for request initialization. + // - It may require specifying regional endpoints when creating the service client as shown in: + // https://pkg.go.dev/cloud.google.com/go#hdr-Client_Options + c, err := library.NewClient(ctx) + if err != nil { + // TODO: Handle error. + } + defer c.Close() + + req := &librarypb.GetBookRequest{ + // TODO: Fill request struct fields. + // See https://pkg.go.dev/cloud.google.com/go/library/apiv1/librarypb#GetBookRequest. + Name: "projects/{project-id}/books/{book}" + } + resp, err := c.GetBookRequest(ctx, req) + if err != nil { + // TODO: Handle error. + } + // TODO: Use resp. + _ = resp +} + +// [END google_v1_generated_LibraryService_GetBookRequest_sync] diff --git a/internal/testing/sample/sample.go b/internal/testing/sample/sample.go index f145143613f..d292f895017 100644 --- a/internal/testing/sample/sample.go +++ b/internal/testing/sample/sample.go @@ -75,8 +75,24 @@ const ( // // Example: // https://pkg.go.dev/cloud.google.com/go/bigquery/migration/apiv2/migrationpb#MigrationWorkflow - // https://github.com/googleapis/googleapis/blob/master/google/cloud/bigquery/migration/v2alpha/migration_entities.proto#L38 + // https://github.com/googleapis/googleapis/blob/f7df662a24c56ecaab79cb7d808fed4d2bb4981d/google/cloud/bigquery/migration/v2/migration_entities.proto#L36 Resource = "MigrationWorkflow" + + // ResourceType is the type of the `google.api.resource` option. + // + // Example: + // https://github.com/googleapis/googleapis/blob/f7df662a24c56ecaab79cb7d808fed4d2bb4981d/google/cloud/bigquery/migration/v2/migration_entities.proto#L38 + // + // It is also the value of the resource reference type in the GetRequest + // for `google.api.resource_reference`. + // https://github.com/googleapis/googleapis/blob/f7df662a24c56ecaab79cb7d808fed4d2bb4981d/google/cloud/bigquery/migration/v2/migration_service.proto#L132 + ResourceType = "bigquerymigration.googleapis.com/MigrationWorkflow" + + // ResourcePattern is the pattern of the `google.api.resource` option. + // + // Example: + // https://github.com/googleapis/googleapis/blob/f7df662a24c56ecaab79cb7d808fed4d2bb4981d/google/cloud/bigquery/migration/v2/migration_entities.proto#L39 + ResourcePattern = "projects/{project}/locations/{location}/workflows/{workflow}" ) const (