grpc-rest 0.1.24 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +2 -41
  3. data/.rubocop.yml +3 -0
  4. data/CHANGELOG +6 -1
  5. data/Gemfile +3 -1
  6. data/Gemfile.lock +21 -10
  7. data/README.md +7 -8
  8. data/bin/protoc-gen-rails +6 -0
  9. data/grpc-rest.gemspec +4 -1
  10. data/lib/generator/controller.rb.erb +27 -0
  11. data/lib/generator/grpc.rb.erb +9 -0
  12. data/lib/generator/method.rb +96 -0
  13. data/lib/generator/output.rb +28 -0
  14. data/lib/generator/plugin_pb.rb +25 -0
  15. data/lib/generator/service.rb +36 -0
  16. data/lib/generator.rb +88 -0
  17. data/lib/grpc_rest/version.rb +3 -1
  18. data/lib/grpc_rest.rb +40 -38
  19. data/{protoc-gen-rails/testdata/base/app/controllers/my_service_controller.rb → spec/__snapshots__/service.snap} +33 -27
  20. data/spec/base_interceptor_spec.rb +6 -3
  21. data/spec/{protoc-gen-openapiv2 → gen/protoc-gen-openapiv2}/options/annotations_pb.rb +5 -4
  22. data/spec/{protoc-gen-openapiv2 → gen/protoc-gen-openapiv2}/options/openapiv2_pb.rb +30 -29
  23. data/spec/gen/test_pb.rb +15 -0
  24. data/spec/gen/test_service_pb.rb +23 -0
  25. data/spec/gen/test_service_services_pb.rb +26 -0
  26. data/spec/generator_spec.rb +49 -0
  27. data/spec/grpc_rest_spec.rb +71 -25
  28. data/spec/gruf_spec.rb +4 -3
  29. data/spec/spec_helper.rb +8 -6
  30. data/spec/test_service_pb.rb +1 -1
  31. data/spec/test_service_services_pb.rb +2 -1
  32. data/spec/testdata/base/app/controllers/my_service_controller.rb +65 -0
  33. data/spec/testdata/no_service/.keep +0 -0
  34. data/{protoc-gen-rails → spec}/testdata/test_service.proto +1 -0
  35. metadata +90 -26
  36. data/protoc-gen-rails/.goreleaser.yml +0 -27
  37. data/protoc-gen-rails/LICENSE +0 -21
  38. data/protoc-gen-rails/go.mod +0 -25
  39. data/protoc-gen-rails/go.sum +0 -49
  40. data/protoc-gen-rails/internal/output.go +0 -165
  41. data/protoc-gen-rails/internal/parse.go +0 -101
  42. data/protoc-gen-rails/internal/utils.go +0 -57
  43. data/protoc-gen-rails/main.go +0 -104
  44. data/protoc-gen-rails/main_test.go +0 -158
  45. /data/{protoc-gen-rails/testdata/no_service/.keep → spec/__snapshots__/no_service.snap} +0 -0
  46. /data/{protoc-gen-rails → spec}/google-deps/google/api/annotations.proto +0 -0
  47. /data/{protoc-gen-rails → spec}/google-deps/google/api/http.proto +0 -0
  48. /data/{protoc-gen-rails → spec}/google-deps/protoc-gen-openapiv2/options/annotations.proto +0 -0
  49. /data/{protoc-gen-rails → spec}/google-deps/protoc-gen-openapiv2/options/openapiv2.proto +0 -0
  50. /data/{protoc-gen-rails → spec}/testdata/base/config/routes/grpc.rb +0 -0
  51. /data/{protoc-gen-rails → spec}/testdata/test.proto +0 -0
@@ -1,165 +0,0 @@
1
- package internal
2
-
3
- import (
4
- "bytes"
5
- "fmt"
6
- "strings"
7
- "text/template"
8
-
9
- "github.com/iancoleman/strcase"
10
- "google.golang.org/protobuf/types/descriptorpb"
11
- )
12
-
13
- type FileResult struct {
14
- Name string
15
- Content string
16
- }
17
-
18
- type controller struct {
19
- ControllerName string
20
- Methods []method
21
- ServiceName string
22
- FullServiceName string
23
- MethodName string
24
- }
25
-
26
- type method struct {
27
- Name string
28
- RequestType string
29
- Path string
30
- PathInfo []PathInfo
31
- Body string
32
- HttpMethod string
33
- RestOptions string
34
- }
35
-
36
- func rubyRestOptions(restOptions map[string]bool) string {
37
- var opts []string
38
- for opt, val := range restOptions {
39
- if val {
40
- opts = append(opts, fmt.Sprintf("%v: true", opt))
41
- }
42
- }
43
- return "{" + strings.Join(opts, ", ") + "}"
44
- }
45
-
46
- type Route struct {
47
- MethodName string
48
- Path string
49
- Controller string
50
- HttpMethod string
51
- }
52
-
53
- var controllerTemplate = `
54
- ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
55
-
56
- require 'grpc_rest'
57
- class {{.ControllerName}}Controller < ActionController::Base
58
- protect_from_forgery with: :null_session
59
-
60
- rescue_from StandardError do |e|
61
- render json: GrpcRest.error_msg(e), status: :internal_server_error
62
- end
63
- rescue_from GRPC::BadStatus do |e|
64
- render json: GrpcRest.error_msg(e), status: GrpcRest.grpc_http_status(e.code)
65
- end
66
- rescue_from ActionDispatch::Http::Parameters::ParseError, Google::Protobuf::TypeError do |e|
67
- render json: GrpcRest.error_msg(e), status: :bad_request
68
- end
69
- METHOD_PARAM_MAP = {
70
- {{range .Methods }}
71
- "{{.Name}}" => [
72
- {{range .PathInfo -}}
73
- {name: "{{.Name}}", val: {{if .HasValPattern}}"{{.ValPattern}}"{{else}}nil{{end}}, split_name:{{.SplitName}}},
74
- {{end -}}
75
- ],
76
- {{end -}}
77
- }.freeze
78
- {{$fullServiceName := .FullServiceName -}}
79
- {{range .Methods }}
80
- def {{.Name}}
81
- fields = {{.RequestType}}.descriptor.to_a.map(&:name)
82
- parameters = request.parameters.to_h.deep_transform_keys(&:underscore)
83
- grpc_request = GrpcRest.init_request({{.RequestType}}, parameters.slice(*fields))
84
- GrpcRest.assign_params(grpc_request, METHOD_PARAM_MAP["{{.Name}}"], "{{.Body}}", request.parameters)
85
- render json: GrpcRest.send_request("{{$fullServiceName}}", "{{.Name}}", grpc_request, {{.RestOptions}})
86
- end
87
- {{end}}
88
- end
89
- `
90
-
91
- func ProcessService(service *descriptorpb.ServiceDescriptorProto, pkg string) (FileResult, []Route, error) {
92
- var routes []Route
93
- data := controller{
94
- Methods: []method{},
95
- ServiceName: Classify(service.GetName()),
96
- ControllerName: Demodulize(service.GetName()),
97
- FullServiceName: Classify(pkg + "." + service.GetName()),
98
- }
99
- for _, m := range service.GetMethod() {
100
- opts, err := ExtractAPIOptions(m)
101
- if err != nil {
102
- return FileResult{}, routes, err
103
- }
104
- restOpts, err := ExtractRestOptions(m)
105
- if err != nil {
106
- return FileResult{}, routes, err
107
- }
108
- httpMethod, path, err := MethodAndPath(opts.Pattern)
109
- pathInfo, err := ParsedPath(path)
110
- if err != nil {
111
- return FileResult{}, routes, err
112
- }
113
- controllerMethod := method{
114
- Name: strcase.ToSnake(m.GetName()),
115
- RequestType: Classify(m.GetInputType()),
116
- Path: path,
117
- RestOptions: rubyRestOptions(restOpts),
118
- HttpMethod: httpMethod,
119
- Body: opts.Body,
120
- PathInfo: pathInfo,
121
- }
122
- data.Methods = append(data.Methods, controllerMethod)
123
- routes = append(routes, Route{
124
- HttpMethod: strings.ToLower(httpMethod),
125
- Path: SanitizePath(path),
126
- Controller: strcase.ToSnake(data.ControllerName),
127
- MethodName: strcase.ToSnake(m.GetName()),
128
- })
129
- }
130
- resultTemplate, err := template.New("controller").Parse(controllerTemplate)
131
- if err != nil {
132
- return FileResult{}, routes, fmt.Errorf("can't parse controller template: %w", err)
133
- }
134
- var resultContent bytes.Buffer
135
- err = resultTemplate.Execute(&resultContent, data)
136
- if err != nil {
137
- return FileResult{}, routes, fmt.Errorf("can't execute controller template: %w", err)
138
- }
139
- return FileResult{
140
- Content: resultContent.String(),
141
- Name: fmt.Sprintf("app/controllers/%s_controller.rb", strcase.ToSnake(data.ControllerName)),
142
- }, routes, nil
143
- }
144
-
145
- var routeTemplate = `# frozen_string_literal: true
146
-
147
- ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
148
-
149
- {{range . -}}
150
- {{.HttpMethod}} '{{.Path}}' => '{{.Controller}}#{{.MethodName}}'
151
- {{end -}}
152
- `
153
-
154
- func OutputRoutes(routes []Route) (string, error) {
155
- resultTemplate, err := template.New("routes").Parse(routeTemplate)
156
- if err != nil {
157
- return "", err
158
- }
159
- var resultContent bytes.Buffer
160
- err = resultTemplate.Execute(&resultContent, routes)
161
- if err != nil {
162
- return "", err
163
- }
164
- return resultContent.String(), nil
165
- }
@@ -1,101 +0,0 @@
1
- package internal
2
-
3
- import (
4
- "encoding/json"
5
- "fmt"
6
- options "google.golang.org/genproto/googleapis/api/annotations"
7
- options2 "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2/options"
8
- "google.golang.org/protobuf/proto"
9
- "google.golang.org/protobuf/types/descriptorpb"
10
- "regexp"
11
- "strings"
12
- )
13
-
14
- func MethodAndPath(pattern any) (string, string, error) {
15
-
16
- switch typedPattern := pattern.(type) {
17
- case *options.HttpRule_Get:
18
- return "GET", typedPattern.Get, nil
19
- case *options.HttpRule_Post:
20
- return "POST", typedPattern.Post, nil
21
- case *options.HttpRule_Put:
22
- return "PUT", typedPattern.Put, nil
23
- case *options.HttpRule_Delete:
24
- return "DELETE", typedPattern.Delete, nil
25
- case *options.HttpRule_Patch:
26
- return "PATCH", typedPattern.Patch, nil
27
- case *options.HttpRule_Custom:
28
- return typedPattern.Custom.Kind, typedPattern.Custom.Path, nil
29
- default:
30
- return "", "", fmt.Errorf("unknown pattern type %T", pattern)
31
- }
32
- }
33
-
34
- type PathInfo struct {
35
- Name string
36
- ValPattern string
37
- SplitName string
38
- HasValPattern bool
39
- }
40
-
41
- func ParsedPath(path string) ([]PathInfo, error) {
42
- var infos []PathInfo
43
- re := regexp.MustCompile("\\{(.*?)}")
44
- matches := re.FindAllString(path, -1)
45
- for _, match := range matches {
46
- name := match[1:len(match)-1]
47
- val := ""
48
- equal := strings.Index(match, "=")
49
- if equal != -1 {
50
- val = name[equal:]
51
- name = name[0:equal-1]
52
- }
53
- splitName := strings.Split(name, ".")
54
- jsonSplit, err := json.Marshal(splitName)
55
- if err != nil {
56
- return nil, fmt.Errorf("error marshalling splitName: %w", err)
57
- }
58
- infos = append(infos, PathInfo{
59
- Name: name,
60
- ValPattern: val,
61
- SplitName: string(jsonSplit),
62
- HasValPattern: val != "",
63
- })
64
- }
65
- return infos, nil
66
- }
67
-
68
- func ExtractAPIOptions(meth *descriptorpb.MethodDescriptorProto) (*options.HttpRule, error) {
69
- if meth.Options == nil {
70
- return nil, nil
71
- }
72
- if !proto.HasExtension(meth.Options, options.E_Http) {
73
- return nil, nil
74
- }
75
- ext := proto.GetExtension(meth.Options, options.E_Http)
76
- opts, ok := ext.(*options.HttpRule)
77
- if !ok {
78
- return nil, fmt.Errorf("extension is %T; want an HttpRule", ext)
79
- }
80
- return opts, nil
81
- }
82
-
83
- func ExtractRestOptions(meth *descriptorpb.MethodDescriptorProto) (map[string]bool, error) {
84
- if meth.Options == nil {
85
- return nil, nil
86
- }
87
- if !proto.HasExtension(meth.Options, options2.E_Openapiv2Operation) {
88
- return nil, nil
89
- }
90
- ext := proto.GetExtension(meth.Options, options2.E_Openapiv2Operation)
91
- operation, ok := ext.(*options2.Operation)
92
- if !ok {
93
- return nil, fmt.Errorf("extension is %T; want an Operation", ext)
94
- }
95
- operationExt := operation.GetExtensions()
96
- emitDefaults := operationExt["x-grpc-rest-emit-defaults"].GetBoolValue()
97
- return map[string]bool{
98
- "emit_defaults": emitDefaults,
99
- }, nil
100
- }
101
-
@@ -1,57 +0,0 @@
1
- package internal
2
-
3
- import (
4
- "fmt"
5
- "github.com/iancoleman/strcase"
6
- "os"
7
- "regexp"
8
- "strings"
9
- )
10
-
11
- func LogMsg(msg string, args ...any) {
12
- fmt.Fprintf(os.Stderr, fmt.Sprintf(msg, args...))
13
- fmt.Fprintln(os.Stderr)
14
- }
15
-
16
- func FilePathify(s string) string {
17
- var result []string
18
- s = strings.Trim(s, ".")
19
- tokens := strings.Split(s, ".")
20
- for _, token := range tokens {
21
- result = append(result, strcase.ToSnake(token))
22
- }
23
- return strings.Join(result, "/")
24
- }
25
-
26
- func Classify(s string) string {
27
- var result []string
28
- s = strings.Trim(s, ".")
29
- tokens := strings.Split(s, ".")
30
- for _, token := range tokens {
31
- result = append(result, strcase.ToCamel(token))
32
- }
33
- return strings.Join(result, "::")
34
- }
35
-
36
- func Demodulize(s string) string {
37
- tokens := strings.Split(s, ".")
38
- return tokens[len(tokens)-1]
39
- }
40
-
41
- func SanitizePath(s string) string {
42
- re := regexp.MustCompile("\\{(.*?)}")
43
- matches := re.FindAllStringSubmatch(s, -1)
44
- for _, match := range matches {
45
- repl := match[1]
46
- equal := strings.Index(match[1], "=")
47
- if equal != -1 {
48
- repl = repl[0:equal]
49
- }
50
- dot := strings.Index(repl, ".")
51
- if dot != -1 {
52
- repl = repl[dot+1:]
53
- }
54
- s = strings.Replace(s, match[0], "*"+repl, 1)
55
- }
56
- return s
57
- }
@@ -1,104 +0,0 @@
1
- package main
2
-
3
- import (
4
- "fmt"
5
- "github.com/flipp-oss/protoc-gen-rails/internal"
6
- "google.golang.org/protobuf/proto"
7
- "google.golang.org/protobuf/types/descriptorpb"
8
- "google.golang.org/protobuf/types/pluginpb"
9
- "io"
10
- "log"
11
- "os"
12
- "slices"
13
- )
14
-
15
- var routes = []internal.Route{}
16
-
17
- func processService(service *descriptorpb.ServiceDescriptorProto, pkg string) (internal.FileResult, error) {
18
- result, serviceRoutes, err := internal.ProcessService(service, pkg)
19
- if err != nil {
20
- return internal.FileResult{}, err
21
- }
22
- routes = slices.Concat(routes, serviceRoutes)
23
- return result, nil
24
- }
25
-
26
- func routeFile() (internal.FileResult, error) {
27
- content, err := internal.OutputRoutes(routes)
28
- if err != nil {
29
- return internal.FileResult{}, err
30
- }
31
- return internal.FileResult{
32
- Name: "config/routes/grpc.rb",
33
- Content: content,
34
- }, nil
35
- }
36
-
37
- func main() {
38
- req, err := ReadRequest()
39
- if err != nil {
40
- log.Fatalf("%s", fmt.Errorf("error reading request: %w", err))
41
- }
42
- files := []internal.FileResult{}
43
- for _, file := range req.GetProtoFile() {
44
- for _, service := range file.GetService() {
45
- fileResult, err := processService(service, file.GetPackage())
46
- if err != nil {
47
- log.Fatalf("%s", fmt.Errorf("error processing service %v: %w", service.GetName(), err))
48
- }
49
- files = append(files, fileResult)
50
- }
51
- }
52
- if len(files) > 0 {
53
- routeOutput, err := routeFile()
54
- if err != nil {
55
- log.Fatalf("%s", fmt.Errorf("error processing routes: %w", err))
56
- }
57
- files = append(files, routeOutput)
58
- }
59
-
60
- // process registry
61
- writeResponse(files)
62
- }
63
-
64
- func ReadRequest() (*pluginpb.CodeGeneratorRequest, error) {
65
- in, err := io.ReadAll(os.Stdin)
66
- if err != nil {
67
- return nil, err
68
- }
69
- req := &pluginpb.CodeGeneratorRequest{}
70
- err = proto.Unmarshal(in, req)
71
- if err != nil {
72
- return nil, err
73
- }
74
- return req, nil
75
- }
76
-
77
- func generateResponse(files []internal.FileResult) *pluginpb.CodeGeneratorResponse {
78
- feature := uint64(pluginpb.CodeGeneratorResponse_FEATURE_PROTO3_OPTIONAL)
79
- respFiles := make([]*pluginpb.CodeGeneratorResponse_File, len(files))
80
- for i, file := range files {
81
- respFiles[i] = &pluginpb.CodeGeneratorResponse_File{
82
- Name: &file.Name,
83
- Content: &file.Content,
84
- }
85
-
86
- }
87
- return &pluginpb.CodeGeneratorResponse{
88
- SupportedFeatures: &feature,
89
- File: respFiles,
90
- }
91
- }
92
-
93
- func writeResponse(files []internal.FileResult) {
94
- response := generateResponse(files)
95
- out, err := proto.Marshal(response)
96
- if err != nil {
97
- log.Fatalf("%s", fmt.Errorf("error marshalling response: %w", err))
98
- }
99
- _, err = os.Stdout.Write(out)
100
- if err != nil {
101
- log.Fatalf("%s", fmt.Errorf("error writing response: %w", err))
102
- }
103
- }
104
-
@@ -1,158 +0,0 @@
1
- package main
2
-
3
- import (
4
- "fmt"
5
- "github.com/stretchr/testify/assert"
6
- "os"
7
- "os/exec"
8
- "path/filepath"
9
- "strings"
10
- "testing"
11
- )
12
-
13
- // Overall approach taken from https://github.com/mix-php/mix/blob/master/src/grpc/protoc-gen-mix/plugin_test.go
14
-
15
- // When the environment variable RUN_AS_PROTOC_GEN_RAILS is set, we skip running
16
- // tests and instead act as protoc-gen-rails. This allows the test binary to
17
- // pass itself to protoc.
18
- func init() {
19
- if os.Getenv("RUN_AS_PROTOC_GEN_RAILS") != "" {
20
- main()
21
- os.Exit(0)
22
- }
23
- }
24
-
25
- func fileNames(directory string, appendDirectory bool, extension string) ([]string, error) {
26
- var files []os.FileInfo
27
- var fullPaths []string
28
- err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
29
- if extension != "" && filepath.Ext(path) != extension {
30
- return nil
31
- }
32
- if info.IsDir() {
33
- return nil
34
- }
35
- files = append(files, info)
36
- if info.Name() != ".keep" {
37
- fullPaths = append(fullPaths, path)
38
- }
39
- if err != nil {
40
- fmt.Println("ERROR:", err)
41
- }
42
- return nil
43
- })
44
- if err != nil {
45
- return nil, fmt.Errorf("can't read %s directory: %w", directory, err)
46
- }
47
- if appendDirectory {
48
- return fullPaths, nil
49
- }
50
- var names []string
51
- for _, file := range fullPaths {
52
- names = append(names, strings.Replace(file, directory, "", 1)[1:])
53
- }
54
- return names, nil
55
- }
56
-
57
- func runTest(t *testing.T, directory string, options map[string]string) {
58
- workdir, _ := os.Getwd()
59
- tmpdir, err := os.MkdirTemp(workdir, "proto-test.")
60
- if err != nil {
61
- t.Fatal(err)
62
- }
63
- defer os.RemoveAll(tmpdir)
64
-
65
- args := []string{
66
- "-I.",
67
- "--rails_out=" + tmpdir,
68
- }
69
- names, err := fileNames(workdir + "/testdata", false, ".proto")
70
- if err != nil {
71
- t.Fatal(fmt.Errorf("testData fileNames %w", err))
72
- }
73
- for _, name := range names {
74
- args = append(args, fmt.Sprintf("testdata/%v", name))
75
- }
76
- for k, v := range options {
77
- args = append(args, "--rails_opt=" + k + "=" + v)
78
- }
79
- protoc(t, args)
80
-
81
- testDir := workdir + "/testdata/" + directory
82
- if os.Getenv("UPDATE_SNAPSHOTS") != "" {
83
- cmd := exec.Command("/bin/sh", "-c", fmt.Sprintf("cp -R %v/* %v", tmpdir, testDir))
84
- cmd.Run()
85
- } else {
86
- assertEqualFiles(t, testDir, tmpdir)
87
- }
88
- }
89
-
90
- func Test_Base(t *testing.T) {
91
- runTest(t, "base", map[string]string{})
92
- }
93
-
94
- func Test_NoServices(t *testing.T) {
95
- workdir, _ := os.Getwd()
96
- tmpdir, err := os.MkdirTemp(workdir, "proto-test.")
97
- if err != nil {
98
- t.Fatal(err)
99
- }
100
- defer os.RemoveAll(tmpdir)
101
-
102
- args := []string{
103
- "-I.",
104
- "--rails_out=" + tmpdir,
105
- }
106
- args = append(args, fmt.Sprintf("testdata/test.proto"))
107
- protoc(t, args)
108
-
109
- testDir := workdir + "/testdata/no_service"
110
- assertEqualFiles(t, testDir, tmpdir)
111
- }
112
-
113
- func assertEqualFiles(t *testing.T, original, generated string) {
114
- names, err := fileNames(original, false, "")
115
- if err != nil {
116
- t.Fatal(fmt.Errorf("original fileNames %w", err))
117
- }
118
- generatedNames, err := fileNames(generated, false, "")
119
- if err != nil {
120
- t.Fatal(fmt.Errorf("generated fileNames %w", err))
121
- }
122
- assert.Equal(t, names, generatedNames)
123
- // put back subdirectories
124
- names, _ = fileNames(original, true, "")
125
- generatedNames, _ = fileNames(generated, true, "")
126
- for i, name := range names {
127
- originalData, err := os.ReadFile(name)
128
- if err != nil {
129
- t.Fatal("Can't find original file for comparison")
130
- }
131
-
132
- generatedData, err := os.ReadFile(generatedNames[i])
133
- if err != nil {
134
- t.Fatal("Can't find generated file for comparison")
135
- }
136
- r := strings.NewReplacer("\r\n", "", "\n", "")
137
- assert.Equal(t, r.Replace(string(originalData)), r.Replace(string(generatedData)))
138
- }
139
- }
140
-
141
- func protoc(t *testing.T, args []string) {
142
- cmd := exec.Command("protoc", "--proto_path=.", "--proto_path=./google-deps", "--plugin=protoc-gen-rails=" + os.Args[0])
143
- cmd.Args = append(cmd.Args, args...)
144
- cmd.Env = append(os.Environ(), "RUN_AS_PROTOC_GEN_RAILS=1")
145
- out, err := cmd.CombinedOutput()
146
-
147
- if len(out) > 0 || err != nil {
148
- t.Log("RUNNING: ", strings.Join(cmd.Args, " "))
149
- }
150
-
151
- if len(out) > 0 {
152
- t.Log(string(out))
153
- }
154
-
155
- if err != nil {
156
- t.Fatalf("protoc: %v", err)
157
- }
158
- }
File without changes