diff --git a/plugins/community/mercari-grpc-federation/v1.3.1/.dockerignore b/plugins/community/mercari-grpc-federation/v1.3.1/.dockerignore new file mode 100644 index 000000000..5d0f124ff --- /dev/null +++ b/plugins/community/mercari-grpc-federation/v1.3.1/.dockerignore @@ -0,0 +1,2 @@ +* +!Dockerfile diff --git a/plugins/community/mercari-grpc-federation/v1.3.1/Dockerfile b/plugins/community/mercari-grpc-federation/v1.3.1/Dockerfile new file mode 100644 index 000000000..e525a5b26 --- /dev/null +++ b/plugins/community/mercari-grpc-federation/v1.3.1/Dockerfile @@ -0,0 +1,16 @@ +# syntax=docker/dockerfile:1.9 +FROM --platform=$BUILDPLATFORM golang:1.23.0-bookworm AS build + +ARG TARGETOS TARGETARCH +ENV CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH + +RUN --mount=type=cache,target=/go/pkg/mod \ + go install -ldflags="-s -w" -trimpath github.com/mercari/grpc-federation/cmd/protoc-gen-grpc-federation@v1.3.1 \ + && mv /go/bin/${GOOS}_${GOARCH}/protoc-gen-grpc-federation /go/bin/protoc-gen-grpc-federation || true + +FROM scratch +COPY --from=build --link --chown=root:root /etc/passwd /etc/passwd +COPY --from=build --link --chown=root:root /go/bin/protoc-gen-grpc-federation . +ENV GOROOT / +USER nobody +ENTRYPOINT [ "/protoc-gen-grpc-federation" ] diff --git a/plugins/community/mercari-grpc-federation/v1.3.1/buf.plugin.yaml b/plugins/community/mercari-grpc-federation/v1.3.1/buf.plugin.yaml new file mode 100644 index 000000000..6078603a4 --- /dev/null +++ b/plugins/community/mercari-grpc-federation/v1.3.1/buf.plugin.yaml @@ -0,0 +1,10 @@ +version: v1 +name: buf.build/community/mercari-grpc-federation +plugin_version: v1.3.1 +source_url: https://github.com/mercari/grpc-federation +integration_guide_url: https://github.com/mercari/grpc-federation/blob/main/docs/getting_started.md +description: Generates a gRPC server by writing a custom option in Protocol Buffers +output_languages: + - go +spdx_license_id: MIT +license_url: https://github.com/mercari/grpc-federation/blob/v1.3.1/LICENSE diff --git a/plugins/grpc-ecosystem/gateway/v2.22.0/.dockerignore b/plugins/grpc-ecosystem/gateway/v2.22.0/.dockerignore new file mode 100644 index 000000000..12458999e --- /dev/null +++ b/plugins/grpc-ecosystem/gateway/v2.22.0/.dockerignore @@ -0,0 +1,3 @@ +* +!Dockerfile +!separate_pkg_additional_imports.patch diff --git a/plugins/grpc-ecosystem/gateway/v2.22.0/Dockerfile b/plugins/grpc-ecosystem/gateway/v2.22.0/Dockerfile new file mode 100644 index 000000000..dccc0c39b --- /dev/null +++ b/plugins/grpc-ecosystem/gateway/v2.22.0/Dockerfile @@ -0,0 +1,21 @@ +# syntax=docker/dockerfile:1.9 +FROM --platform=$BUILDPLATFORM golang:1.23.0-bookworm AS build + +ARG TARGETOS TARGETARCH +ENV CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH + +WORKDIR /tmp +RUN git clone --depth 1 --branch v2.22.0 https://github.com/grpc-ecosystem/grpc-gateway.git +COPY --link separate_pkg_additional_imports.patch /tmp/separate_pkg_additional_imports.patch +WORKDIR /tmp/grpc-gateway +RUN git apply /tmp/separate_pkg_additional_imports.patch +WORKDIR /tmp/grpc-gateway/protoc-gen-grpc-gateway +RUN --mount=type=cache,target=/go/pkg/mod \ + go install -ldflags="-s -w" -trimpath \ + && mv /go/bin/${GOOS}_${GOARCH}/protoc-gen-grpc-gateway /go/bin/protoc-gen-grpc-gateway || true + +FROM scratch +COPY --from=build --link --chown=root:root /etc/passwd /etc/passwd +COPY --from=build --link --chown=root:root /go/bin/protoc-gen-grpc-gateway / +USER nobody +ENTRYPOINT [ "/protoc-gen-grpc-gateway" ] diff --git a/plugins/grpc-ecosystem/gateway/v2.22.0/buf.plugin.yaml b/plugins/grpc-ecosystem/gateway/v2.22.0/buf.plugin.yaml new file mode 100644 index 000000000..7b843374f --- /dev/null +++ b/plugins/grpc-ecosystem/gateway/v2.22.0/buf.plugin.yaml @@ -0,0 +1,23 @@ +version: v1 +name: buf.build/grpc-ecosystem/gateway +plugin_version: v2.22.0 +source_url: https://github.com/grpc-ecosystem/grpc-gateway +integration_guide_url: https://github.com/grpc-ecosystem/grpc-gateway#usage +description: gRPC to JSON proxy generator following the gRPC HTTP spec. +output_languages: + - go +registry: + go: + min_version: "1.21" + deps: + - module: github.com/grpc-ecosystem/grpc-gateway/v2 + version: v2.22.0 + opts: + - paths=source_relative + - standalone=true + - separate_package=true +deps: + - plugin: buf.build/protocolbuffers/go:v1.34.2 + - plugin: buf.build/grpc/go:v1.5.1 +spdx_license_id: BSD-3-Clause +license_url: https://github.com/grpc-ecosystem/grpc-gateway/blob/v2.22.0/LICENSE.txt diff --git a/plugins/grpc-ecosystem/gateway/v2.22.0/separate_pkg_additional_imports.patch b/plugins/grpc-ecosystem/gateway/v2.22.0/separate_pkg_additional_imports.patch new file mode 100644 index 000000000..770917adb --- /dev/null +++ b/plugins/grpc-ecosystem/gateway/v2.22.0/separate_pkg_additional_imports.patch @@ -0,0 +1,779 @@ +diff --git a/internal/descriptor/buf_build.go b/internal/descriptor/buf_build.go +new file mode 100644 +index 00000000..f2e1e13c +--- /dev/null ++++ b/internal/descriptor/buf_build.go +@@ -0,0 +1,63 @@ ++package descriptor ++ ++import ( ++ "path/filepath" ++ "strings" ++) ++ ++const ( ++ BaseTypePackageSubPath = baseTypePackageName + "/go" ++ // TODO: change "v2" to "v3" when v3 of grpc gateway is released, ++ // or, even better, stop generating at the extra location. ++ GatewayPackageSubPath = "grpc-ecosystem/gateway/v2" ++) ++ ++const ( ++ baseTypePackageName = "protocolbuffers" ++ grpcPackageName = "grpc" ++ grpcPackageSubPath = grpcPackageName + "/go" ++) ++ ++// SetSeparatePackage sets separatePackage ++func (r *Registry) SetSeparatePackage(use bool) { ++ r.separatePackage = use ++} ++ ++// IncludeAdditionalImports adds additionalImports to the registry on a per-package basis ++func (r *Registry) IncludeAdditionalImports(svc *Service, goPkg GoPackage) { ++ if !r.separatePackage { ++ return ++ } ++ if r.additionalImports == nil { ++ r.additionalImports = make(map[string][]string) ++ } ++ // when generating a separate package for the gateway, we need to generate an import statement ++ // for the gRPC stubs that are no longer in the same package. This is done by adding the grpc ++ // package to the additionalImports list. In order to prepare a valid import statement, we'll replace ++ // the source package name, something like: ../pet/v1/v1petgateway with ../pet/v1/v1petgrpc ++ ++ packageName := strings.TrimSuffix(goPkg.Name, "gateway") + grpcPackageName ++ svc.GRPCFile = &File{ ++ GoPkg: GoPackage{ ++ // additionally, as the `go_package` option is passed through from the generator, and can only be ++ // set the one time, without making major changes, we'll use the package name sent through the ++ // options as a basis, and replace the source package name with the grpc package name. ++ Path: strings.Replace( ++ filepath.Join(goPkg.Path, packageName), ++ BaseTypePackageSubPath, ++ grpcPackageSubPath, ++ 1, ++ ), ++ Name: strings.Replace(packageName, BaseTypePackageSubPath, grpcPackageSubPath, 1), ++ }, ++ } ++ r.additionalImports[goPkg.Path] = append(r.additionalImports[goPkg.Path], svc.GRPCFile.GoPkg.Path) ++} ++ ++// GetAdditionalImports returns additionalImports ++func (r *Registry) GetAdditionalImports(goPkg GoPackage) []string { ++ if !r.separatePackage || r.additionalImports == nil { ++ return nil ++ } ++ return r.additionalImports[goPkg.Path] ++} +diff --git a/internal/descriptor/registry.go b/internal/descriptor/registry.go +index 684fc5b6..c1b69363 100644 +--- a/internal/descriptor/registry.go ++++ b/internal/descriptor/registry.go +@@ -157,6 +157,13 @@ type Registry struct { + // allowPatchFeature determines whether to use PATCH feature involving update masks (using google.protobuf.FieldMask). + allowPatchFeature bool + ++ // separatePackage determines whether to output the generated code into a separate package. ++ separatePackage bool ++ ++ // additionalImports is a list of additional imports to be added to the generated code. ++ // N.B. additional imports is not a flag option ++ additionalImports map[string][]string ++ + // preserveRPCOrder, if true, will ensure the order of paths emitted in openapi swagger files mirror + // the order of RPC methods found in proto files. If false, emitted paths will be ordered alphabetically. + preserveRPCOrder bool +@@ -255,6 +262,9 @@ func (r *Registry) loadFile(filePath string, file *protogen.File) { + pkg.Alias = "ext" + cases.Title(language.AmericanEnglish).String(pkg.Name) + } + ++ if r.separatePackage { ++ pkg.Name += "gateway" ++ } + if err := r.ReserveGoPackageAlias(pkg.Name, pkg.Path); err != nil { + for i := 0; ; i++ { + alias := fmt.Sprintf("%s_%d", pkg.Name, i) +diff --git a/internal/descriptor/services.go b/internal/descriptor/services.go +index ad1764ce..ae58f7e3 100644 +--- a/internal/descriptor/services.go ++++ b/internal/descriptor/services.go +@@ -29,6 +29,7 @@ func (r *Registry) loadServices(file *File) error { + ServiceDescriptorProto: sd, + ForcePrefixedName: r.standalone, + } ++ r.IncludeAdditionalImports(svc, file.GoPkg) + for _, md := range sd.GetMethod() { + if grpclog.V(2) { + grpclog.Infof("Processing %s.%s", sd.GetName(), md.GetName()) +diff --git a/internal/descriptor/types.go b/internal/descriptor/types.go +index 5a43472b..c0c02966 100644 +--- a/internal/descriptor/types.go ++++ b/internal/descriptor/types.go +@@ -164,6 +164,9 @@ type Service struct { + *descriptorpb.ServiceDescriptorProto + // File is the file where this service is defined. + File *File ++ // GRPCFile is the file where this service's gRPC stubs are defined. ++ // This is nil if the service's gRPC stubs are defined alongside the messages. ++ GRPCFile *File + // Methods is the list of methods defined in this service. + Methods []*Method + // ForcePrefixedName when set to true, prefixes a type with a package prefix. +@@ -173,7 +176,9 @@ type Service struct { + // FQSN returns the fully qualified service name of this service. + func (s *Service) FQSN() string { + components := []string{""} +- if s.File.Package != nil { ++ if s.GRPCFile != nil && s.GRPCFile.GetPackage() != "" { ++ components = append(components, s.GRPCFile.GetPackage()) ++ } else if s.File.Package != nil { + components = append(components, s.File.GetPackage()) + } + components = append(components, s.GetName()) +@@ -185,7 +190,11 @@ func (s *Service) InstanceName() string { + if !s.ForcePrefixedName { + return s.GetName() + } +- return fmt.Sprintf("%s.%s", s.File.Pkg(), s.GetName()) ++ pkg := s.File.Pkg() ++ if s.GRPCFile != nil { ++ pkg = s.GRPCFile.Pkg() ++ } ++ return fmt.Sprintf("%s.%s", pkg, s.GetName()) + } + + // ClientConstructorName returns name of the Client constructor with package prefix if needed +@@ -194,7 +203,11 @@ func (s *Service) ClientConstructorName() string { + if !s.ForcePrefixedName { + return constructor + } +- return fmt.Sprintf("%s.%s", s.File.Pkg(), constructor) ++ pkg := s.File.Pkg() ++ if s.GRPCFile != nil { ++ pkg = s.GRPCFile.Pkg() ++ } ++ return fmt.Sprintf("%s.%s", pkg, constructor) + } + + // Method wraps descriptorpb.MethodDescriptorProto for richer features. +diff --git a/protoc-gen-grpc-gateway/internal/gengateway/generator.go b/protoc-gen-grpc-gateway/internal/gengateway/generator.go +index 62766254..70f7dd7b 100644 +--- a/protoc-gen-grpc-gateway/internal/gengateway/generator.go ++++ b/protoc-gen-grpc-gateway/internal/gengateway/generator.go +@@ -5,6 +5,7 @@ import ( + "fmt" + "go/format" + "path" ++ "strings" + + "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor" + gen "github.com/grpc-ecosystem/grpc-gateway/v2/internal/generator" +@@ -22,11 +23,18 @@ type generator struct { + registerFuncSuffix string + allowPatchFeature bool + standalone bool ++ separatePackage bool + } + + // New returns a new generator which generates grpc gateway files. +-func New(reg *descriptor.Registry, useRequestContext bool, registerFuncSuffix string, +- allowPatchFeature, standalone bool) gen.Generator { ++func New( ++ reg *descriptor.Registry, ++ useRequestContext bool, ++ registerFuncSuffix string, ++ allowPatchFeature bool, ++ standalone bool, ++ separatePackage bool, ++) gen.Generator { + var imports []descriptor.GoPackage + for _, pkgpath := range []string{ + "context", +@@ -65,6 +73,7 @@ func New(reg *descriptor.Registry, useRequestContext bool, registerFuncSuffix st + registerFuncSuffix: registerFuncSuffix, + allowPatchFeature: allowPatchFeature, + standalone: standalone, ++ separatePackage: separatePackage, + } + } + +@@ -75,7 +84,7 @@ func (g *generator) Generate(targets []*descriptor.File) ([]*descriptor.Response + grpclog.Infof("Processing %s", file.GetName()) + } + +- code, err := g.generate(file) ++ code, err := g.generate(file, nil) + if errors.Is(err, errNoTargetService) { + if grpclog.V(1) { + grpclog.Infof("%s: %v", file.GetName(), err) +@@ -90,10 +99,64 @@ func (g *generator) Generate(targets []*descriptor.File) ([]*descriptor.Response + grpclog.Errorf("%v: %s", err, code) + return nil, err + } ++ if !g.separatePackage { ++ files = append(files, &descriptor.ResponseFile{ ++ GoPkg: file.GoPkg, ++ CodeGeneratorResponse_File: &pluginpb.CodeGeneratorResponse_File{ ++ Name: proto.String(file.GeneratedFilenamePrefix + ".pb.gw.go"), ++ Content: proto.String(string(formatted)), ++ }, ++ }) ++ continue ++ } ++ goPkg := descriptor.GoPackage{ ++ Path: path.Join(file.GoPkg.Path, file.GoPkg.Name), ++ Name: file.GoPkg.Name, ++ } ++ fileNamePrefix := path.Join(path.Dir(file.GeneratedFilenamePrefix), file.GoPkg.Name, path.Base(file.GeneratedFilenamePrefix)) + files = append(files, &descriptor.ResponseFile{ +- GoPkg: file.GoPkg, ++ GoPkg: goPkg, + CodeGeneratorResponse_File: &pluginpb.CodeGeneratorResponse_File{ +- Name: proto.String(file.GeneratedFilenamePrefix + ".pb.gw.go"), ++ Name: proto.String(fileNamePrefix + ".pb.gw.go"), ++ Content: proto.String(string(formatted)), ++ }, ++ }) ++ // There was a bug where we include an extra path element (the filename), resulting ++ // in a stuttering import path. Fixing this bug cannot involve removing the Go file ++ // generated at the wrong path, because that would be a breaking change. ++ // ++ // Instead, we generate the same file both at the right path and at the wrong path, ++ // marking the file (its package) at the wrong path as deprecated. ++ // ++ // If gateway has a new major version, we should then stop generating at the wrong path. ++ aliasedPackage := &descriptor.GoPackage{ ++ // When generating for generated SDK, the original goPkg points to code generated by "protocolbuffers/go", ++ // but we are aliasing to a package generated by "grpc-ecosystem/gateway". ++ Path: strings.Replace(goPkg.Path, "/"+descriptor.BaseTypePackageSubPath, "/"+descriptor.GatewayPackageSubPath, 1), ++ Name: goPkg.Name, ++ Alias: "gateway", ++ } ++ code, err = g.generate(file, aliasedPackage) ++ if errors.Is(err, errNoTargetService) { ++ if grpclog.V(1) { ++ grpclog.Infof("%s: %v", file.GetName(), err) ++ } ++ continue ++ } ++ if err != nil { ++ return nil, err ++ } ++ formatted, err = format.Source([]byte(code)) ++ if err != nil { ++ grpclog.Errorf("%v: %s", err, code) ++ return nil, err ++ } ++ // The prefix is incorrect, but we are still generating it for backwards compatibility. ++ fileNamePrefix = path.Join(file.GeneratedFilenamePrefix, file.GoPkg.Name, path.Base(file.GeneratedFilenamePrefix)) ++ files = append(files, &descriptor.ResponseFile{ ++ GoPkg: goPkg, ++ CodeGeneratorResponse_File: &pluginpb.CodeGeneratorResponse_File{ ++ Name: proto.String(fileNamePrefix + ".pb.gw.go"), + Content: proto.String(string(formatted)), + }, + }) +@@ -101,7 +164,7 @@ func (g *generator) Generate(targets []*descriptor.File) ([]*descriptor.Response + return files, nil + } + +-func (g *generator) generate(file *descriptor.File) (string, error) { ++func (g *generator) generate(file *descriptor.File, aliasedPkg *descriptor.GoPackage) (string, error) { + pkgSeen := make(map[string]bool) + var imports []descriptor.GoPackage + for _, pkg := range g.baseImports { +@@ -109,6 +172,14 @@ func (g *generator) generate(file *descriptor.File) (string, error) { + imports = append(imports, pkg) + } + ++ for _, additionalImport := range g.reg.GetAdditionalImports(file.GoPkg) { ++ elems := strings.Split(additionalImport, "/") ++ imports = append(imports, descriptor.GoPackage{ ++ Path: additionalImport, ++ Name: elems[len(elems)-1], ++ }) ++ } ++ + if g.standalone { + imports = append(imports, file.GoPkg) + } +@@ -126,6 +197,7 @@ func (g *generator) generate(file *descriptor.File) (string, error) { + } + } + params := param{ ++ AliasedPkg: aliasedPkg, + File: file, + Imports: imports, + UseRequestContext: g.useRequestContext, +diff --git a/protoc-gen-grpc-gateway/internal/gengateway/generator_test.go b/protoc-gen-grpc-gateway/internal/gengateway/generator_test.go +index 2c5fe023..02d3a000 100644 +--- a/protoc-gen-grpc-gateway/internal/gengateway/generator_test.go ++++ b/protoc-gen-grpc-gateway/internal/gengateway/generator_test.go +@@ -1,6 +1,9 @@ + package gengateway + + import ( ++ "fmt" ++ "path/filepath" ++ "strings" + "testing" + + "github.com/grpc-ecosystem/grpc-gateway/v2/internal/descriptor" +@@ -75,6 +78,18 @@ func newExampleFileDescriptorWithGoPkg(gp *descriptor.GoPackage, filenamePrefix + } + } + ++func newExampleFileDescriptorWithGoPkgWithoutBinding(gp *descriptor.GoPackage, filenamePrefix string) *descriptor.File { ++ file := newExampleFileDescriptorWithGoPkg(gp, filenamePrefix) ++ for _, service := range file.Services { ++ for _, method := range service.Methods { ++ if method != nil { ++ method.Bindings = nil ++ } ++ } ++ } ++ return file ++} ++ + func TestGenerator_Generate(t *testing.T) { + g := new(generator) + g.reg = descriptor.NewRegistry() +@@ -96,3 +111,232 @@ func TestGenerator_Generate(t *testing.T) { + t.Fatalf("invalid name %q, expected %q", gotName, expectedName) + } + } ++ ++func TestGenerator_GenerateSeparatePackage(t *testing.T) { ++ reg := descriptor.NewRegistry() ++ reg.SetSeparatePackage(true) ++ reg.SetStandalone(true) ++ g := New(reg, true, "Handler", true, true, true) ++ targets := []*descriptor.File{ ++ crossLinkFixture(newExampleFileDescriptorWithGoPkg(&descriptor.GoPackage{ ++ Path: "example.com/mymodule/foo/bar/v1", ++ Name: "v1" + "gateway", // Name is appended with "gateway" with standalone set to true. ++ Alias: "extalias", ++ }, "foo/bar/v1/example")), ++ } ++ // Set ForcePrefixedName (usually set when standalone=true). ++ for _, f := range targets { ++ for _, msg := range f.Messages { ++ msg.ForcePrefixedName = true ++ for _, field := range msg.Fields { ++ field.ForcePrefixedName = true ++ } ++ } ++ for _, enum := range f.Enums { ++ enum.ForcePrefixedName = true ++ } ++ for _, svc := range f.Services { ++ packageName := strings.TrimSuffix(svc.File.GoPkg.Name, "gateway") + "grpc" ++ svc.ForcePrefixedName = true ++ // replicates behavior in internal/descriptor/services.go (loadServices) ++ svc.GRPCFile = &descriptor.File{ ++ GoPkg: descriptor.GoPackage{ ++ Path: strings.Replace( ++ filepath.Join(svc.File.GoPkg.Path, packageName), ++ "protocolbuffers/go", ++ "grpc/go", ++ 1, ++ ), ++ Name: strings.Replace(packageName, "protocolbuffers/go", "grpc/go", 1), ++ }, ++ } ++ reg.IncludeAdditionalImports(svc, f.GoPkg) ++ } ++ } ++ result, err := g.Generate(targets) ++ if err != nil { ++ t.Fatalf("failed to generate stubs: %v", err) ++ } ++ if len(result) != 2 { ++ t.Fatalf("expected to generate 2 files, got: %d", len(result)) ++ } ++ expectedName := "foo/bar/v1/v1gateway/example.pb.gw.go" ++ expectedGoPkgPath := "example.com/mymodule/foo/bar/v1/v1gateway" ++ expectedGoPkgName := "v1gateway" ++ correctFile := result[0] ++ if correctFile == nil { ++ t.Fatal("result is nil") ++ } ++ if correctFile.GetName() != expectedName { ++ t.Errorf("invalid name %q, expected %q", correctFile.GetName(), expectedName) ++ } ++ if correctFile.GoPkg.Path != expectedGoPkgPath { ++ t.Errorf("invalid path %q, expected %q", result[0].GoPkg.Path, expectedGoPkgPath) ++ } ++ if correctFile.GoPkg.Name != expectedGoPkgName { ++ t.Errorf("invalid name %q, expected %q", result[0].GoPkg.Name, expectedGoPkgName) ++ } ++ // Require the two dependencies to be declared as imported packages ++ correctFileContent := correctFile.GetContent() ++ for _, expectedImport := range []string{ ++ `extalias "example.com/mymodule/foo/bar/v1"`, ++ `"example.com/mymodule/foo/bar/v1/v1grpc"`, ++ } { ++ if !strings.Contains(correctFileContent, expectedImport) { ++ t.Errorf("expected to find import %q in the generated file: %s", expectedImport, correctFileContent[:400]) ++ } ++ } ++ ++ expectedName = "foo/bar/v1/example/v1gateway/example.pb.gw.go" ++ // wrong path but correct go package ++ aliasFile := result[1] ++ if aliasFile == nil { ++ t.Fatal("result is nil") ++ } ++ if aliasFile.GetName() != expectedName { ++ t.Errorf("invalid name %q, expected %q", aliasFile.GetName(), expectedName) ++ } ++ if aliasFile.GoPkg.Path != expectedGoPkgPath { ++ t.Errorf("invalid path %q, expected %q", aliasFile.GoPkg.Path, expectedGoPkgPath) ++ } ++ if aliasFile.GoPkg.Name != expectedGoPkgName { ++ t.Errorf("invalid name %q, expected %q", aliasFile.GoPkg.Name, expectedGoPkgName) ++ } ++ aliasFileContent := aliasFile.GetContent() ++ // Require the two dependencies to be declared as imported packages ++ expectedImport := `gateway "example.com/mymodule/foo/bar/v1/v1gateway"` ++ if !strings.Contains(aliasFileContent, expectedImport) { ++ t.Errorf("expected to find import %q in the generated file: %s...", expectedImport, aliasFileContent[:500]) ++ } ++ aliasedFunctions := []string{ ++ "RegisterExampleServiceHandlerServer", ++ "RegisterExampleServiceHandlerClient", ++ "RegisterExampleServiceHandlerFromEndpoint", ++ "RegisterExampleServiceHandler", ++ } ++ for _, aliasedFunction := range aliasedFunctions { ++ aliasDefinition := fmt.Sprintf("%[1]s = gateway.%[1]s", aliasedFunction) ++ if !strings.Contains(aliasFileContent, aliasDefinition) { ++ t.Fatalf("expected %q in the alias file: %s", aliasDefinition, aliasFileContent) ++ } ++ if strings.Contains(correctFileContent, aliasDefinition) { ++ t.Fatalf("unexpected alias %q in the correct file: %s", aliasDefinition, correctFileContent) ++ } ++ } ++} ++ ++func TestGenerator_GenerateSeparatePackage_WithoutBinding(t *testing.T) { ++ reg := descriptor.NewRegistry() ++ reg.SetSeparatePackage(true) ++ reg.SetStandalone(true) ++ g := New(reg, true, "Handler", true, true, true) ++ targets := []*descriptor.File{ ++ crossLinkFixture(newExampleFileDescriptorWithGoPkgWithoutBinding(&descriptor.GoPackage{ ++ Path: "example.com/mymodule/foo/bar/v1", ++ Name: "v1" + "gateway", ++ Alias: "extalias", ++ }, "foo/bar/v1/example")), ++ } ++ result, err := g.Generate(targets) ++ if err != nil { ++ t.Fatalf("failed to generate stubs: %v", err) ++ } ++ if len(result) != 0 { ++ t.Fatalf("expected to generate 0 file, got: %d", len(result)) ++ } ++} ++ ++func TestGenerator_GenerateSeparatePackage_WithOmitPackageDoc_Local(t *testing.T) { ++ reg := descriptor.NewRegistry() ++ reg.SetSeparatePackage(true) ++ reg.SetStandalone(true) ++ reg.SetOmitPackageDoc(true) ++ g := New(reg, true, "Handler", true, true, true) ++ targets := []*descriptor.File{ ++ crossLinkFixture(newExampleFileDescriptorWithGoPkg(&descriptor.GoPackage{ ++ Path: "example.com/mymodule/foo/bar/v1", ++ Name: "v1" + "gateway", ++ Alias: "extalias", ++ }, "foo/bar/v1/example")), ++ } ++ result, err := g.Generate(targets) ++ if err != nil { ++ t.Fatalf("failed to generate stubs: %v", err) ++ } ++ if len(result) != 2 { ++ t.Fatalf("expected to generate 2 files, got: %d", len(result)) ++ } ++ correctFileContent := result[0].GetContent() ++ if strings.Contains(correctFileContent, "Deprecated:") { ++ t.Errorf("the correct file should not be deprecated: %s...", correctFileContent[:500]) ++ } ++ deprecationDoc := `/* ++Deprecated: This package has moved to "example.com/mymodule/foo/bar/v1/v1gateway". Use that import path instead. ++*/` ++ aliasFileContent := result[1].GetContent() ++ // Even though omit_package_doc is set, we still need to deprecate the package. ++ if !strings.Contains(aliasFileContent, deprecationDoc) { ++ t.Errorf("expected to find deprecation doc in the alias file: %s...", aliasFileContent[:500]) ++ } ++} ++ ++func TestGenerator_GenerateSeparatePackage_WithOmitPackageDoc_Generate_SDK(t *testing.T) { ++ reg := descriptor.NewRegistry() ++ reg.SetSeparatePackage(true) ++ reg.SetStandalone(true) ++ reg.SetOmitPackageDoc(true) ++ g := New(reg, true, "Handler", true, true, true) ++ targets := []*descriptor.File{ ++ crossLinkFixture(newExampleFileDescriptorWithGoPkg(&descriptor.GoPackage{ ++ Path: "example.com/gen/go/owner/module/protocolbuffers/go/foo/bar/v1", ++ Name: "v1" + "gateway", ++ Alias: "extalias", ++ }, "foo/bar/v1/example")), ++ } ++ result, err := g.Generate(targets) ++ if err != nil { ++ t.Fatalf("failed to generate stubs: %v", err) ++ } ++ if len(result) != 2 { ++ t.Fatalf("expected to generate 2 files, got: %d", len(result)) ++ } ++ correctFileContent := result[0].GetContent() ++ if strings.Contains(correctFileContent, "Deprecated:") { ++ t.Errorf("the correct file should not be deprecated: %s...", correctFileContent[:500]) ++ } ++ deprecationDoc := `/* ++Deprecated: This package has moved to "example.com/gen/go/owner/module/grpc-ecosystem/gateway/v2/foo/bar/v1/v1gateway". Use that import path instead. ++*/` ++ aliasFileContent := result[1].GetContent() ++ // Even though omit_package_doc is set, we still need to deprecate the package. ++ if !strings.Contains(aliasFileContent, deprecationDoc) { ++ t.Errorf("expected to find deprecation doc in the alias file: %s...", aliasFileContent[:500]) ++ } ++} ++ ++func TestGenerator_GenerateSeparatePackage_WithoutService(t *testing.T) { ++ reg := descriptor.NewRegistry() ++ reg.SetSeparatePackage(true) ++ reg.SetStandalone(true) ++ g := New(reg, true, "Handler", true, true, true) ++ targets := []*descriptor.File{ ++ { ++ FileDescriptorProto: &descriptorpb.FileDescriptorProto{ ++ Name: proto.String("example.proto"), ++ Package: proto.String("example"), ++ }, ++ GoPkg: descriptor.GoPackage{ ++ Path: "foo/bar/baz/gen/v1", ++ Name: "v1", ++ }, ++ GeneratedFilenamePrefix: "gen/v1/example", ++ }, ++ } ++ result, err := g.Generate(targets) ++ if err != nil { ++ t.Fatalf("failed to generate stubs: %v", err) ++ } ++ if len(result) != 0 { ++ t.Fatalf("expected to generate 0 file, got: %d", len(result)) ++ } ++} +diff --git a/protoc-gen-grpc-gateway/internal/gengateway/template.go b/protoc-gen-grpc-gateway/internal/gengateway/template.go +index 9975e424..13332ef7 100644 +--- a/protoc-gen-grpc-gateway/internal/gengateway/template.go ++++ b/protoc-gen-grpc-gateway/internal/gengateway/template.go +@@ -15,6 +15,7 @@ import ( + + type param struct { + *descriptor.File ++ AliasedPkg *descriptor.GoPackage + Imports []descriptor.GoPackage + UseRequestContext bool + RegisterFuncSuffix string +@@ -143,6 +144,7 @@ func (f queryParamFilter) String() string { + } + + type trailerParams struct { ++ AliasedPkg *descriptor.GoPackage + Services []*descriptor.Service + UseRequestContext bool + RegisterFuncSuffix string +@@ -172,11 +174,14 @@ func applyTemplate(p param, reg *descriptor.Registry) (string, error) { + methName := casing.Camel(*meth.Name) + meth.Name = &methName + for _, b := range meth.Bindings { ++ methodWithBindingsSeen = true ++ if p.AliasedPkg != nil { ++ break ++ } + if err := reg.CheckDuplicateAnnotation(b.HTTPMethod, b.PathTmpl.Template, svc); err != nil { + return "", err + } + +- methodWithBindingsSeen = true + if err := handlerTemplate.Execute(w, binding{ + Binding: b, + Registry: reg, +@@ -204,6 +209,7 @@ func applyTemplate(p param, reg *descriptor.Registry) (string, error) { + } + + tp := trailerParams{ ++ AliasedPkg: p.AliasedPkg, + Services: targetServices, + UseRequestContext: p.UseRequestContext, + RegisterFuncSuffix: p.RegisterFuncSuffix, +@@ -228,8 +234,19 @@ var ( + Package {{.GoPkg.Name}} is a reverse proxy. + + It translates gRPC into RESTful JSON APIs. +-*/{{end}} ++{{if $.AliasedPkg}} ++Deprecated: This package has moved to "{{$.AliasedPkg.Path}}". Use that import path instead. ++{{- end}} ++*/ ++{{- else if $.AliasedPkg}} ++/* ++Deprecated: This package has moved to "{{$.AliasedPkg.Path}}". Use that import path instead. ++*/ ++{{- end}} + package {{.GoPkg.Name}} ++{{- if $.AliasedPkg}} ++import {{$.AliasedPkg}} ++{{- else}} + import ( + {{range $i := .Imports}}{{if $i.Standard}}{{$i | printf "%s\n"}}{{end}}{{end}} + +@@ -243,6 +260,7 @@ var _ status.Status + var _ = runtime.String + var _ = utilities.NewDoubleArray + var _ = metadata.Join ++{{- end}} + `)) + + handlerTemplate = template.Must(template.New("handler").Parse(` +@@ -606,6 +624,9 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct + }`)) + + localTrailerTemplate = template.Must(template.New("local-trailer").Parse(` ++{{- if $.AliasedPkg }} ++var ( ++{{- end }} + {{$UseRequestContext := .UseRequestContext}} + {{range $svc := .Services}} + // Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server registers the http handlers for service {{$svc.GetName}} to "mux". +@@ -613,6 +634,9 @@ func local_request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ct + // StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. + // Note that using this registration option will cause many gRPC library features to stop working. Consider using Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint instead. + // GRPC interceptors will not work for this type of registration. To use interceptors, you must use the "runtime.WithMiddlewares" option in the "runtime.NewServeMux" call. ++{{- if $.AliasedPkg}} ++ Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server = {{$.AliasedPkg.Alias}}.Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server ++{{- else}} + func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, mux *runtime.ServeMux, server {{$svc.InstanceName}}Server) error { + {{range $m := $svc.Methods}} + {{range $b := $m.Bindings}} +@@ -664,6 +688,7 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, + {{end}} + return nil + } ++{{end}} + {{end}}`)) + + trailerTemplate = template.Must(template.New("trailer").Parse(` +@@ -671,6 +696,9 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Server(ctx context.Context, + {{range $svc := .Services}} + // Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint is same as Register{{$svc.GetName}}{{$.RegisterFuncSuffix}} but + // automatically dials to "endpoint" and closes the connection when "ctx" gets done. ++{{- if $.AliasedPkg}} ++ Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint = {{$.AliasedPkg.Alias}}.Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint ++{{- else}} + func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.NewClient(endpoint, opts...) + if err != nil { +@@ -693,18 +721,26 @@ func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}FromEndpoint(ctx context.Co + + return Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}(ctx, mux, conn) + } ++{{- end}} + + // Register{{$svc.GetName}}{{$.RegisterFuncSuffix}} registers the http handlers for service {{$svc.GetName}} to "mux". + // The handlers forward requests to the grpc endpoint over "conn". ++{{- if $.AliasedPkg}} ++ Register{{$svc.GetName}}{{$.RegisterFuncSuffix}} = {{$.AliasedPkg.Alias}}.Register{{$svc.GetName}}{{$.RegisterFuncSuffix}} ++{{- else}} + func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client(ctx, mux, {{$svc.ClientConstructorName}}(conn)) + } ++{{- end}} + + // Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client registers the http handlers for service {{$svc.GetName}} + // to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "{{$svc.InstanceName}}Client". + // Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "{{$svc.InstanceName}}Client" + // doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in + // "{{$svc.InstanceName}}Client" to call the correct interceptors. This client ignores the HTTP middlewares. ++{{- if $.AliasedPkg}} ++ Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client = {{$.AliasedPkg.Alias}}.Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client ++{{- else}} + func Register{{$svc.GetName}}{{$.RegisterFuncSuffix}}Client(ctx context.Context, mux *runtime.ServeMux, client {{$svc.InstanceName}}Client) error { + {{range $m := $svc.Methods}} + {{range $b := $m.Bindings}} +@@ -785,5 +821,9 @@ var ( + {{end}} + {{end}} + ) +-{{end}}`)) ++{{end}} ++{{end}} ++{{- if $.AliasedPkg}} ++) ++{{- end}}`)) + ) +diff --git a/protoc-gen-grpc-gateway/main.go b/protoc-gen-grpc-gateway/main.go +index 086a4624..5f65b894 100644 +--- a/protoc-gen-grpc-gateway/main.go ++++ b/protoc-gen-grpc-gateway/main.go +@@ -10,6 +10,7 @@ + package main + + import ( ++ "errors" + "flag" + "fmt" + "os" +@@ -36,6 +37,7 @@ var ( + versionFlag = flag.Bool("version", false, "print the current version") + warnOnUnboundMethods = flag.Bool("warn_on_unbound_methods", false, "emit a warning message if an RPC method has no HttpRule annotation") + generateUnboundMethods = flag.Bool("generate_unbound_methods", false, "generate proxy methods even for RPC methods that have no HttpRule annotation") ++ separatePackage = flag.Bool("separate_package", false, "generate gateway code to v1gateway package (requires standalone=true).") + + _ = flag.Bool("logtostderr", false, "Legacy glog compatibility. This flag is a no-op, you can safely remove it") + ) +@@ -73,15 +75,22 @@ func main() { + ParamFunc: flag.CommandLine.Set, + }.Run(func(gen *protogen.Plugin) error { + reg := descriptor.NewRegistry() +- + if err := applyFlags(reg); err != nil { + return err + } +- ++ if *separatePackage && !*standalone { ++ return errors.New("option separate_package=true must be specified with standalone=true") ++ } ++ generator := gengateway.New( ++ reg, ++ *useRequestContext, ++ *registerFuncSuffix, ++ *allowPatchFeature, ++ *standalone, ++ *separatePackage, ++ ) + codegenerator.SetSupportedFeaturesOnPluginGen(gen) + +- generator := gengateway.New(reg, *useRequestContext, *registerFuncSuffix, *allowPatchFeature, *standalone) +- + if grpclog.V(1) { + grpclog.Infof("Parsing code generator request") + } +@@ -135,6 +144,7 @@ func applyFlags(reg *descriptor.Registry) error { + } + reg.SetStandalone(*standalone) + reg.SetAllowDeleteBody(*allowDeleteBody) ++ reg.SetSeparatePackage(*separatePackage) + + flag.Visit(func(f *flag.Flag) { + if f.Name == "allow_repeated_fields_in_body" { diff --git a/plugins/grpc-ecosystem/openapiv2/v2.22.0/.dockerignore b/plugins/grpc-ecosystem/openapiv2/v2.22.0/.dockerignore new file mode 100644 index 000000000..5d0f124ff --- /dev/null +++ b/plugins/grpc-ecosystem/openapiv2/v2.22.0/.dockerignore @@ -0,0 +1,2 @@ +* +!Dockerfile diff --git a/plugins/grpc-ecosystem/openapiv2/v2.22.0/Dockerfile b/plugins/grpc-ecosystem/openapiv2/v2.22.0/Dockerfile new file mode 100644 index 000000000..1158970e0 --- /dev/null +++ b/plugins/grpc-ecosystem/openapiv2/v2.22.0/Dockerfile @@ -0,0 +1,17 @@ +# syntax=docker/dockerfile:1.9 +FROM --platform=$BUILDPLATFORM golang:1.23.0-bookworm AS build + +ARG TARGETOS TARGETARCH +ENV CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH + +FROM golang:1.23.0-bookworm AS build +RUN --mount=type=cache,target=/go/pkg/mod \ + CGO_ENABLED=0 \ + go install -ldflags="-s -w" -trimpath github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@v2.22.0 \ + && mv /go/bin/${GOOS}_${GOARCH}/protoc-gen-openapiv2 /go/bin/protoc-gen-openapiv2 || true + +FROM scratch +COPY --from=build --link --chown=root:root /etc/passwd /etc/passwd +COPY --from=build --link /go/bin/protoc-gen-openapiv2 / +USER nobody +ENTRYPOINT [ "/protoc-gen-openapiv2" ] diff --git a/plugins/grpc-ecosystem/openapiv2/v2.22.0/buf.plugin.yaml b/plugins/grpc-ecosystem/openapiv2/v2.22.0/buf.plugin.yaml new file mode 100644 index 000000000..c1ccad0e0 --- /dev/null +++ b/plugins/grpc-ecosystem/openapiv2/v2.22.0/buf.plugin.yaml @@ -0,0 +1,7 @@ +version: v1 +name: buf.build/grpc-ecosystem/openapiv2 +plugin_version: v2.22.0 +source_url: https://github.com/grpc-ecosystem/grpc-gateway +description: Generates OpenAPI definitions for Protobuf services. +spdx_license_id: BSD-3-Clause +license_url: https://github.com/grpc-ecosystem/grpc-gateway/blob/v2.22.0/LICENSE.txt diff --git a/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/eliza/plugin.sum b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/eliza/plugin.sum new file mode 100644 index 000000000..7d9c0f31d --- /dev/null +++ b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/eliza/plugin.sum @@ -0,0 +1 @@ +h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= diff --git a/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/grpc-federation-v0.13.6/plugin.sum b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/grpc-federation-v0.13.6/plugin.sum new file mode 100644 index 000000000..f3c4fafde --- /dev/null +++ b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/grpc-federation-v0.13.6/plugin.sum @@ -0,0 +1 @@ +h1:S3Do5iLgfxflm3KE5Ti400G0XJqjmAuK6RsPCPXKXjg= diff --git a/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/petapis/plugin.sum b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/petapis/plugin.sum new file mode 100644 index 000000000..7d9c0f31d --- /dev/null +++ b/tests/testdata/buf.build/community/mercari-grpc-federation/v1.3.1/petapis/plugin.sum @@ -0,0 +1 @@ +h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= diff --git a/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/eliza/plugin.sum b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/eliza/plugin.sum new file mode 100644 index 000000000..7d9c0f31d --- /dev/null +++ b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/eliza/plugin.sum @@ -0,0 +1 @@ +h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= diff --git a/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/grpc-gateway/plugin.sum b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/grpc-gateway/plugin.sum new file mode 100644 index 000000000..91f56fa2b --- /dev/null +++ b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/grpc-gateway/plugin.sum @@ -0,0 +1 @@ +h1:Sklf+L1CPS2OfNhZd3VD7BS3rt9gbRSzXID4VEADdpY= diff --git a/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/petapis/plugin.sum b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/petapis/plugin.sum new file mode 100644 index 000000000..7d9c0f31d --- /dev/null +++ b/tests/testdata/buf.build/grpc-ecosystem/gateway/v2.22.0/petapis/plugin.sum @@ -0,0 +1 @@ +h1:47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU= diff --git a/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/eliza/plugin.sum b/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/eliza/plugin.sum new file mode 100644 index 000000000..06875f679 --- /dev/null +++ b/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/eliza/plugin.sum @@ -0,0 +1 @@ +h1:i/zjVkZaxoFgKQESrbFwisCaDNzRLPWmUND0XCR+IlI= diff --git a/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/petapis/plugin.sum b/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/petapis/plugin.sum new file mode 100644 index 000000000..7b9624ad7 --- /dev/null +++ b/tests/testdata/buf.build/grpc-ecosystem/openapiv2/v2.22.0/petapis/plugin.sum @@ -0,0 +1 @@ +h1:SybfdW8LTRLCVcNSux9/dP9iFEQFhWbADwdaILiM0EY=