grpc-rest 0.1.5 → 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3ed7db60b29738e635e7b776539fc8207a5ec264e43912cb83562b1a44efdc58
4
- data.tar.gz: a1653a18dab17be5abad7968ae405254ccc23973f799f9c7def7cd318b485681
3
+ metadata.gz: 8851853946d82493dd832a39be201f3b5b0bc5e70c7837e9be7a3db4d1d30460
4
+ data.tar.gz: a44a3d29c174df98af07cc91693127fbfc8e6a5c42b282bb7119573af53e5d28
5
5
  SHA512:
6
- metadata.gz: 04b70b84065aa66b4441a4987519a351e0cafca0b58d5392a4e3a5a3b64429ca6bc4d6be877dfcf28f3c34fd77b04f7592500d89989737eab513b7c40591580f
7
- data.tar.gz: fd35771ff21bbd7dc98550633a0eb96cb44b518f3356f792ce92dc02e9655f2683d0eab08bc1d36a1f8abf36f23fbabdd68e8bdb7f0dd4423ad311a27e7f80e6
6
+ metadata.gz: dfa0ed32f96706a4d8e6b0bfccc72d2e0d018f64fa4e5e87372bc3527c7b7521d8b1aecc48520df695dda54ac717cc5d7440319ea767c075204464d1965c668c
7
+ data.tar.gz: bbaa98db8d37ec4b91325438422fa1681fc0a35186f3c3e427cd1cb933bceaba7f806fe9cbf4269f4bdbd7315bbe1870b28aae14f4c1d59294dc974333652c4c
@@ -8,7 +8,7 @@ on:
8
8
  workflow_dispatch:
9
9
 
10
10
  jobs:
11
- test:
11
+ test-go:
12
12
  runs-on: [ubuntu-latest]
13
13
  steps:
14
14
  - name: Checkout code
@@ -25,8 +25,22 @@ jobs:
25
25
  - run: git clean -f -d
26
26
  - run: cd protoc-gen-rails && go test ./...
27
27
 
28
+ test-ruby:
29
+ runs-on: [ubuntu-latest]
30
+ steps:
31
+ - name: Checkout code
32
+ uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 0
35
+ - name: Setup Ruby
36
+ uses: ruby/setup-ruby@v1
37
+ with:
38
+ ruby-version: '3.3'
39
+ bundler-cache: true
40
+ - run: bundle exec rspec
41
+
28
42
  build_and_deploy:
29
- needs: test
43
+ needs: [test-go, test-ruby]
30
44
  runs-on: [ubuntu-latest]
31
45
  steps:
32
46
  - name: Checkout code
data/.gitignore CHANGED
@@ -21,3 +21,5 @@
21
21
  go.work
22
22
 
23
23
  .idea
24
+ log
25
+ tmp
data/CHANGELOG CHANGED
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## UNRELEASED
9
9
 
10
+ # 0.1.7 - 2024-04-09
11
+ - Added tests.
12
+ - Fixed some bugs around path globbing.
13
+
14
+ # 0.1.6 - 2024-04-05
15
+ - Fixed bug where a blank routes file would be emitted when no services were found.
16
+ - Added a comment line indicating that files are auto-generated.
17
+
10
18
  # 0.1.5 - 2024-04-04
11
19
  - Parse Google Well Known Types (WKT) in request.
12
20
 
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,190 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ grpc-rest (0.1.6)
5
+ grpc
6
+ rails (>= 6.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ actioncable (7.0.8.1)
12
+ actionpack (= 7.0.8.1)
13
+ activesupport (= 7.0.8.1)
14
+ nio4r (~> 2.0)
15
+ websocket-driver (>= 0.6.1)
16
+ actionmailbox (7.0.8.1)
17
+ actionpack (= 7.0.8.1)
18
+ activejob (= 7.0.8.1)
19
+ activerecord (= 7.0.8.1)
20
+ activestorage (= 7.0.8.1)
21
+ activesupport (= 7.0.8.1)
22
+ mail (>= 2.7.1)
23
+ net-imap
24
+ net-pop
25
+ net-smtp
26
+ actionmailer (7.0.8.1)
27
+ actionpack (= 7.0.8.1)
28
+ actionview (= 7.0.8.1)
29
+ activejob (= 7.0.8.1)
30
+ activesupport (= 7.0.8.1)
31
+ mail (~> 2.5, >= 2.5.4)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
35
+ rails-dom-testing (~> 2.0)
36
+ actionpack (7.0.8.1)
37
+ actionview (= 7.0.8.1)
38
+ activesupport (= 7.0.8.1)
39
+ rack (~> 2.0, >= 2.2.4)
40
+ rack-test (>= 0.6.3)
41
+ rails-dom-testing (~> 2.0)
42
+ rails-html-sanitizer (~> 1.0, >= 1.2.0)
43
+ actiontext (7.0.8.1)
44
+ actionpack (= 7.0.8.1)
45
+ activerecord (= 7.0.8.1)
46
+ activestorage (= 7.0.8.1)
47
+ activesupport (= 7.0.8.1)
48
+ globalid (>= 0.6.0)
49
+ nokogiri (>= 1.8.5)
50
+ actionview (7.0.8.1)
51
+ activesupport (= 7.0.8.1)
52
+ builder (~> 3.1)
53
+ erubi (~> 1.4)
54
+ rails-dom-testing (~> 2.0)
55
+ rails-html-sanitizer (~> 1.1, >= 1.2.0)
56
+ activejob (7.0.8.1)
57
+ activesupport (= 7.0.8.1)
58
+ globalid (>= 0.3.6)
59
+ activemodel (7.0.8.1)
60
+ activesupport (= 7.0.8.1)
61
+ activerecord (7.0.8.1)
62
+ activemodel (= 7.0.8.1)
63
+ activesupport (= 7.0.8.1)
64
+ activestorage (7.0.8.1)
65
+ actionpack (= 7.0.8.1)
66
+ activejob (= 7.0.8.1)
67
+ activerecord (= 7.0.8.1)
68
+ activesupport (= 7.0.8.1)
69
+ marcel (~> 1.0)
70
+ mini_mime (>= 1.1.0)
71
+ activesupport (7.0.8.1)
72
+ concurrent-ruby (~> 1.0, >= 1.0.2)
73
+ i18n (>= 1.6, < 2)
74
+ minitest (>= 5.1)
75
+ tzinfo (~> 2.0)
76
+ builder (3.2.4)
77
+ concurrent-ruby (1.2.3)
78
+ crass (1.0.6)
79
+ date (3.3.4)
80
+ diff-lcs (1.5.1)
81
+ erubi (1.12.0)
82
+ globalid (1.2.1)
83
+ activesupport (>= 6.1)
84
+ google-protobuf (3.25.3-arm64-darwin)
85
+ google-protobuf (3.25.3-x86_64-linux)
86
+ googleapis-common-protos-types (1.14.0)
87
+ google-protobuf (~> 3.18)
88
+ grpc (1.62.0-arm64-darwin)
89
+ google-protobuf (~> 3.25)
90
+ googleapis-common-protos-types (~> 1.0)
91
+ grpc (1.62.0-x86_64-linux)
92
+ google-protobuf (~> 3.25)
93
+ googleapis-common-protos-types (~> 1.0)
94
+ i18n (1.14.4)
95
+ concurrent-ruby (~> 1.0)
96
+ loofah (2.22.0)
97
+ crass (~> 1.0.2)
98
+ nokogiri (>= 1.12.0)
99
+ mail (2.8.1)
100
+ mini_mime (>= 0.1.1)
101
+ net-imap
102
+ net-pop
103
+ net-smtp
104
+ marcel (1.0.4)
105
+ method_source (1.0.0)
106
+ mini_mime (1.1.5)
107
+ minitest (5.22.3)
108
+ net-imap (0.4.10)
109
+ date
110
+ net-protocol
111
+ net-pop (0.1.2)
112
+ net-protocol
113
+ net-protocol (0.2.2)
114
+ timeout
115
+ net-smtp (0.5.0)
116
+ net-protocol
117
+ nio4r (2.7.1)
118
+ nokogiri (1.16.3-arm64-darwin)
119
+ racc (~> 1.4)
120
+ nokogiri (1.16.3-x86_64-linux)
121
+ racc (~> 1.4)
122
+ racc (1.7.3)
123
+ rack (2.2.9)
124
+ rack-test (2.1.0)
125
+ rack (>= 1.3)
126
+ rails (7.0.8.1)
127
+ actioncable (= 7.0.8.1)
128
+ actionmailbox (= 7.0.8.1)
129
+ actionmailer (= 7.0.8.1)
130
+ actionpack (= 7.0.8.1)
131
+ actiontext (= 7.0.8.1)
132
+ actionview (= 7.0.8.1)
133
+ activejob (= 7.0.8.1)
134
+ activemodel (= 7.0.8.1)
135
+ activerecord (= 7.0.8.1)
136
+ activestorage (= 7.0.8.1)
137
+ activesupport (= 7.0.8.1)
138
+ bundler (>= 1.15.0)
139
+ railties (= 7.0.8.1)
140
+ rails-dom-testing (2.2.0)
141
+ activesupport (>= 5.0.0)
142
+ minitest
143
+ nokogiri (>= 1.6)
144
+ rails-html-sanitizer (1.6.0)
145
+ loofah (~> 2.21)
146
+ nokogiri (~> 1.14)
147
+ railties (7.0.8.1)
148
+ actionpack (= 7.0.8.1)
149
+ activesupport (= 7.0.8.1)
150
+ method_source
151
+ rake (>= 12.2)
152
+ thor (~> 1.0)
153
+ zeitwerk (~> 2.5)
154
+ rake (13.2.1)
155
+ rspec-core (3.13.0)
156
+ rspec-support (~> 3.13.0)
157
+ rspec-expectations (3.13.0)
158
+ diff-lcs (>= 1.2.0, < 2.0)
159
+ rspec-support (~> 3.13.0)
160
+ rspec-mocks (3.13.0)
161
+ diff-lcs (>= 1.2.0, < 2.0)
162
+ rspec-support (~> 3.13.0)
163
+ rspec-rails (6.1.2)
164
+ actionpack (>= 6.1)
165
+ activesupport (>= 6.1)
166
+ railties (>= 6.1)
167
+ rspec-core (~> 3.13)
168
+ rspec-expectations (~> 3.13)
169
+ rspec-mocks (~> 3.13)
170
+ rspec-support (~> 3.13)
171
+ rspec-support (3.13.1)
172
+ thor (1.3.1)
173
+ timeout (0.4.1)
174
+ tzinfo (2.0.6)
175
+ concurrent-ruby (~> 1.0)
176
+ websocket-driver (0.7.6)
177
+ websocket-extensions (>= 0.1.0)
178
+ websocket-extensions (0.1.5)
179
+ zeitwerk (2.6.13)
180
+
181
+ PLATFORMS
182
+ arm64-darwin
183
+ x86_64-linux
184
+
185
+ DEPENDENCIES
186
+ grpc-rest!
187
+ rspec-rails
188
+
189
+ BUNDLED WITH
190
+ 2.5.6
data/README.md CHANGED
@@ -79,7 +79,7 @@ plugins:
79
79
  - name: rails
80
80
  out: .
81
81
  ```
82
-
82
+
83
83
  Then, you can run `buf generate` to generate the Ruby files. This will generate:
84
84
  * the Protobuf Ruby files for grpc, in `app/gen`
85
85
  * A new route file, in `config/routes/grpc.rb`
@@ -126,40 +126,24 @@ Rails.application.routes.draw do
126
126
  end
127
127
  ```
128
128
 
129
- ### Hooking up Callbacks
130
-
131
- If you're using [gruf](https://github.com/bigcommerce/gruf), as long as your Gruf controllers are loaded on application load, you don't have to do anything else - grpc-rest will automatically hook the callbacks up. If you're not, you have to tell GrpcRest about your server. An example might look like this:
132
-
133
- ```ruby
134
- # grpc_setup.rb, a shared library file somewhere in the app
135
-
136
- def grpc_server
137
- s = GRPC::RpcServer.new
138
- s.handle(MyImpl.new) # handler inheriting from your service class - see https://grpc.io/docs/languages/ruby/basics/
139
- s
140
- end
141
-
142
- # startup script for gRPC
129
+ ## Caveats
143
130
 
144
- require "grpc_setup"
145
- server = grpc_server
146
- server.run_till_terminated_or_interrupted([1, 'int', 'SIGQUIT'])
147
-
148
- # Rails initializer
149
- require "grpc_setup"
150
- server = grpc_server
151
- GrpcRest.register_server(server)
152
- ```
131
+ This gem does not currently support the full path expression capabilities of grpc-gateway or the Google [http proto](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto). It only supports very basic single wildcard globbing (`*`). Contributions are welcome for more complex cases if they are needed.
153
132
 
154
133
  ## To Do
155
134
 
156
- * Support repeated fields via comma-separation (matches grpc-gateway, but is it really useful?)
157
- * Install via homebrew and/or have the binary in the gem itself
135
+ * Replace Go implementation with Ruby (+ executable) once [service and method lookup](https://github.com/protocolbuffers/protobuf/pull/15817) is released.
158
136
 
159
137
  ## Contributing
160
138
 
161
139
  Bug reports and pull requests are welcome on GitHub at https://github.com/flipp-oss/grpc-rest.
162
140
 
141
+ To regenerate Ruby protobuf code for tests, install the `grpc-tools` gem and run this from the base directory:
142
+
143
+ ```
144
+ grpc_tools_ruby_protoc -I=./protoc-gen-rails/testdata --proto_path=./protoc-gen-rails/google-deps --ruby_out=./spec --grpc_out=./spec ./protoc-gen-rails/testdata/test_service.proto
145
+ ```
146
+
163
147
  ## License
164
148
 
165
149
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/grpc-rest.gemspec CHANGED
@@ -20,4 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_runtime_dependency('grpc')
22
22
  spec.add_runtime_dependency('rails', '>= 6.0')
23
+
24
+ spec.add_development_dependency('rspec-rails')
23
25
  end
@@ -1,3 +1,3 @@
1
1
  module GrpcRest
2
- VERSION = "0.1.5"
2
+ VERSION = "0.1.7"
3
3
  end
data/lib/grpc_rest.rb CHANGED
@@ -1,17 +1,16 @@
1
+ require 'google/protobuf/well_known_types'
2
+ require 'grpc'
3
+
1
4
  module GrpcRest
2
5
  class << self
3
6
 
4
- def register_server(server)
5
- @server = server
6
- end
7
-
8
7
  def underscore(s)
9
8
  GRPC::GenericService.underscore(s)
10
9
  end
11
10
 
12
11
  # Gets a sub record from a proto. If it doesn't exist, initialize it and set it to the proto,
13
12
  # then return it.
14
- def sub_field(proto, name)
13
+ def sub_field(proto, name, parameters)
15
14
  existing = proto.public_send(name.to_sym)
16
15
  return existing if existing
17
16
 
@@ -19,7 +18,11 @@ module GrpcRest
19
18
  return nil if descriptor.nil?
20
19
 
21
20
  klass = descriptor.submsg_name.split('.').map(&:camelize).join('::').constantize
22
- sub_record = klass.new
21
+ params = klass.descriptor.to_a.map(&:name).to_h do |key|
22
+ [key, parameters.delete(key)]
23
+ end
24
+
25
+ sub_record = klass.new(params)
23
26
  proto.public_send(:"#{name}=", sub_record)
24
27
  sub_record
25
28
  end
@@ -27,7 +30,7 @@ module GrpcRest
27
30
  def assign_value(proto, path, value)
28
31
  tokens = path.split('.')
29
32
  tokens[0...-1].each do |path_seg|
30
- proto = sub_field(proto, path_seg)
33
+ proto = sub_field(proto, path_seg, {})
31
34
  return if proto.nil?
32
35
  end
33
36
  proto.public_send(:"#{tokens.last}=", value) if proto.respond_to?(:"#{tokens.last}=")
@@ -36,15 +39,22 @@ module GrpcRest
36
39
  def map_wkt(proto, params)
37
40
  proto.to_a.each do |descriptor|
38
41
  field = descriptor.name
42
+ val = params[field]
43
+ next if val.nil?
44
+
39
45
  case descriptor.subtype&.name
40
46
  when 'google.protobuf.Struct'
41
- params[field] = Google::Protobuf::Struct.from_hash(params[field])
47
+ params[field] = Google::Protobuf::Struct.from_hash(val)
42
48
  when 'google.protobuf.Timestamp'
43
- params[field] = Google::Protobuf::Timestamp.from_time(Time.new(params[field]))
49
+ if val.is_a?(Numeric)
50
+ params[field] = Google::Protobuf::Timestamp.from_time(Time.at(val))
51
+ else
52
+ params[field] = Google::Protobuf::Timestamp.from_time(Time.new(val))
53
+ end
44
54
  when 'google.protobuf.Value'
45
- params[field] = Google::Protobuf::Value.from_ruby(params[field])
55
+ params[field] = Google::Protobuf::Value.from_ruby(val)
46
56
  when 'google.protobuf.ListValue'
47
- params[field] = Google::Protobuf::ListValue.from_a(params[field])
57
+ params[field] = Google::Protobuf::ListValue.from_a(val)
48
58
  else
49
59
  map_wkt(descriptor.subtype, params[field]) if descriptor.subtype
50
60
  end
@@ -68,16 +78,14 @@ module GrpcRest
68
78
  name_tokens = entry[:split_name]
69
79
  value_to_use = parameters.delete(name_tokens.last)
70
80
  if entry[:val]
71
- value_to_use = value_to_use.gsub('*', entry[:val])
81
+ regex = entry[:val].tr('*', '')
82
+ value_to_use = value_to_use.gsub(regex, '')
72
83
  end
73
84
  assign_value(request, entry[:name], value_to_use)
74
85
  end
75
86
  if body_string.present? && body_string != '*'
76
87
  # we need to "splat" the body parameters into the given sub-record rather than into the top-level.
77
- sub_record = sub_field(request, body_string)
78
- sub_record.class.descriptor.to_a.map(&:name).each do |key|
79
- sub_record.public_send(:"#{key}=", parameters.delete(key))
80
- end
88
+ sub_record = sub_field(request, body_string, parameters)
81
89
  end
82
90
  end
83
91
 
@@ -106,11 +114,8 @@ module GrpcRest
106
114
  end
107
115
 
108
116
  def send_grpc_request(service, method, request)
109
- server_parts = service.split('::')
110
- service_name = (server_parts[..-2].map { |p| underscore(p)} + [server_parts[-1]]).join('.')
111
- route = "/#{service_name}/#{method.classify}"
112
- handler = @server.send(:rpc_handlers)[route.to_sym]
113
- handler.call(request)
117
+ klass = service.constantize::Service.subclasses.first
118
+ klass.new.public_send(method, request)
114
119
  end
115
120
 
116
121
  def send_request(service, method, request)
@@ -39,6 +39,8 @@ type Route struct {
39
39
  }
40
40
 
41
41
  var controllerTemplate = `
42
+ ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
43
+
42
44
  require 'grpc_rest'
43
45
  class {{.ControllerName}}Controller < ActionController::Base
44
46
  protect_from_forgery with: :null_session
@@ -116,9 +118,12 @@ func ProcessService(service *descriptorpb.ServiceDescriptorProto, pkg string) (F
116
118
  }, routes, nil
117
119
  }
118
120
 
119
- var routeTemplate = `
121
+ var routeTemplate = `# frozen_string_literal: true
122
+
123
+ ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
124
+
120
125
  {{range . -}}
121
- {{.HttpMethod}} "{{.Path}}" => "{{.Controller}}#{{.MethodName}}"
126
+ {{.HttpMethod}} '{{.Path}}' => '{{.Controller}}#{{.MethodName}}'
122
127
  {{end -}}
123
128
  `
124
129
 
@@ -51,7 +51,7 @@ func SanitizePath(s string) string {
51
51
  if dot != -1 {
52
52
  repl = repl[dot+1:]
53
53
  }
54
- s = strings.Replace(s, match[0], ":"+repl, 1)
54
+ s = strings.Replace(s, match[0], "*"+repl, 1)
55
55
  }
56
56
  return s
57
57
  }
@@ -49,11 +49,13 @@ func main() {
49
49
  files = append(files, fileResult)
50
50
  }
51
51
  }
52
- routeOutput, err := routeFile()
53
- if err != nil {
54
- log.Fatalf("%s", fmt.Errorf("error processing routes: %w", err))
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)
55
58
  }
56
- files = append(files, routeOutput)
57
59
 
58
60
  // process registry
59
61
  writeResponse(files)
@@ -33,7 +33,9 @@ func fileNames(directory string, appendDirectory bool, extension string) ([]stri
33
33
  return nil
34
34
  }
35
35
  files = append(files, info)
36
- fullPaths = append(fullPaths, path)
36
+ if info.Name() != ".keep" {
37
+ fullPaths = append(fullPaths, path)
38
+ }
37
39
  if err != nil {
38
40
  fmt.Println("ERROR:", err)
39
41
  }
@@ -89,6 +91,25 @@ func Test_Base(t *testing.T) {
89
91
  runTest(t, "base", map[string]string{})
90
92
  }
91
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
+
92
113
  func assertEqualFiles(t *testing.T, original, generated string) {
93
114
  names, err := fileNames(original, false, "")
94
115
  if err != nil {
@@ -1,4 +1,6 @@
1
1
 
2
+ ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
3
+
2
4
  require 'grpc_rest'
3
5
  class MyServiceController < ActionController::Base
4
6
  protect_from_forgery with: :null_session
@@ -9,8 +11,7 @@ class MyServiceController < ActionController::Base
9
11
  METHOD_PARAM_MAP = {
10
12
 
11
13
  "test" => [
12
- {name: "blah", val: "foobar/*", split_name:["blah"]},
13
- {name: "repeated_string", val: nil, split_name:["repeated_string"]},
14
+ {name: "foobar", val: "blah/*", split_name:["foobar"]},
14
15
  ],
15
16
 
16
17
  "test_2" => [
@@ -19,12 +20,15 @@ class MyServiceController < ActionController::Base
19
20
  "test_3" => [
20
21
  {name: "sub_record.sub_id", val: nil, split_name:["sub_record","sub_id"]},
21
22
  ],
23
+
24
+ "test_4" => [
25
+ ],
22
26
  }.freeze
23
27
 
24
28
  def test
25
29
  fields = Testdata::TestRequest.descriptor.to_a.map(&:name)
26
30
  grpc_request = GrpcRest.init_request(Testdata::TestRequest, request.parameters.to_h.slice(*fields))
27
- GrpcRest.assign_params(grpc_request, METHOD_PARAM_MAP["test"], "*", request.parameters)
31
+ GrpcRest.assign_params(grpc_request, METHOD_PARAM_MAP["test"], "", request.parameters)
28
32
  render json: GrpcRest.send_request("Testdata::MyService", "test", grpc_request)
29
33
  end
30
34
 
@@ -42,4 +46,11 @@ class MyServiceController < ActionController::Base
42
46
  render json: GrpcRest.send_request("Testdata::MyService", "test_3", grpc_request)
43
47
  end
44
48
 
49
+ def test_4
50
+ fields = Testdata::TestRequest.descriptor.to_a.map(&:name)
51
+ grpc_request = GrpcRest.init_request(Testdata::TestRequest, request.parameters.to_h.slice(*fields))
52
+ GrpcRest.assign_params(grpc_request, METHOD_PARAM_MAP["test_4"], "*", request.parameters)
53
+ render json: GrpcRest.send_request("Testdata::MyService", "test_4", grpc_request)
54
+ end
55
+
45
56
  end
@@ -1,4 +1,8 @@
1
+ # frozen_string_literal: true
1
2
 
2
- get "/test/:blah/:repeated_string" => "my_service#test"
3
- post "/test2" => "my_service#test_2"
4
- post "/test3/:sub_id" => "my_service#test_3"
3
+ ####### THIS FILE IS AUTOMATICALLY GENERATED BY protoc-gen-rails. DO NOT MODIFY. #######
4
+
5
+ get '/test/*foobar' => 'my_service#test'
6
+ post '/test2' => 'my_service#test_2'
7
+ post '/test3/*sub_id' => 'my_service#test_3'
8
+ post '/test4' => 'my_service#test_4'
File without changes
@@ -0,0 +1,6 @@
1
+ syntax = "proto3";
2
+ package test;
3
+
4
+ message Test {
5
+ string test = 1;
6
+ }
@@ -3,6 +3,8 @@ syntax = "proto3";
3
3
  package testdata;
4
4
 
5
5
  import "google/api/annotations.proto";
6
+ import "google/protobuf/struct.proto";
7
+ import "google/protobuf/timestamp.proto";
6
8
 
7
9
  message TestRequest {
8
10
  string test_id = 1;
@@ -10,6 +12,10 @@ message TestRequest {
10
12
  repeated string repeated_string = 3;
11
13
  SubRecord sub_record = 4;
12
14
  SubRecord second_record = 5;
15
+ google.protobuf.Struct struct_field = 6;
16
+ google.protobuf.Timestamp timestamp_field = 7;
17
+ google.protobuf.ListValue list_value = 8;
18
+ google.protobuf.Value bare_value = 9;
13
19
  }
14
20
 
15
21
  message SubRecord {
@@ -25,8 +31,7 @@ message TestResponse {
25
31
  service MyService {
26
32
  rpc Test(TestRequest) returns (TestResponse) {
27
33
  option (google.api.http) = {
28
- get: "/test/{blah=foobar/*}/{repeated_string}"
29
- body: "*"
34
+ get: "/test/{foobar=blah/*}"
30
35
  };
31
36
  }
32
37
 
@@ -43,4 +48,11 @@ service MyService {
43
48
  };
44
49
  }
45
50
 
51
+ rpc Test4(TestRequest) returns (TestResponse) {
52
+ option (google.api.http) = {
53
+ post: "/test4",
54
+ body: '*'
55
+ };
56
+ }
57
+
46
58
  }
@@ -0,0 +1,117 @@
1
+ # See https://github.com/rspec/rspec-rails/issues/1596#issuecomment-834282306
2
+
3
+ require_relative './spec_helper'
4
+ require_relative './test_service_services_pb'
5
+
6
+ class ServerImpl < Testdata::MyService::Service
7
+ def test(req)
8
+ Testdata::TestResponse.new(some_int: 1, full_response: req.to_json)
9
+ end
10
+
11
+ def test_2(req)
12
+ Testdata::TestResponse.new(some_int: 2, full_response: req.to_json)
13
+ end
14
+
15
+ def test_3(req)
16
+ Testdata::TestResponse.new(some_int: 3, full_response: req.to_json)
17
+ end
18
+
19
+ def test_4(req)
20
+ Testdata::TestResponse.new(some_int: 4, full_response: req.to_json)
21
+ end
22
+ end
23
+
24
+ RSpec.describe MyServiceController, type: :request do
25
+
26
+ describe 'using get' do
27
+ it 'should be successful' do
28
+ get "/test/blah/xyz?test_id=abc"
29
+ expect(response).to be_successful
30
+ expect(response.parsed_body).to eq({
31
+ 'some_int' => 1,
32
+ 'full_response' => %({"testId":"abc","foobar":"xyz"})
33
+ })
34
+ end
35
+ end
36
+
37
+ describe 'using body parameter' do
38
+ it "should be successful" do
39
+ params = {
40
+ sub_id: "id1",
41
+ another_id: "id2"
42
+ }
43
+
44
+ post '/test2?test_id=abc&foobar=xyz&timestamp_field=2024-04-03+01:02:03+UTC', params: params, as: :json
45
+ expect(response).to be_successful
46
+ expect(response.parsed_body).to eq({
47
+ 'some_int' => 2,
48
+ 'full_response' => %({"testId":"abc","foobar":"xyz","secondRecord":{"subId":"id1","anotherId":"id2"},"timestampField":"2024-04-03T01:02:03Z"})
49
+ })
50
+ end
51
+
52
+ end
53
+
54
+ describe 'using sub-record splat' do
55
+ it 'should be successful' do
56
+ post '/test3/xyz?test_id=abc'
57
+ expect(response).to be_successful
58
+ expect(response.parsed_body).to eq({
59
+ 'some_int' => 3,
60
+ 'full_response' => %({"testId":"abc","subRecord":{"subId":"xyz"}})
61
+ })
62
+ end
63
+ end
64
+
65
+ describe 'full body splat' do
66
+ it 'should be successful' do
67
+ params = {
68
+ test_id: 'abc',
69
+ foobar: 'xyz',
70
+ repeated_string: ['W', 'T', 'F'],
71
+ sub_record: {
72
+ sub_id: 'id1',
73
+ another_id: 'id2'
74
+ },
75
+ second_record: {
76
+ sub_id: 'id3',
77
+ another_id: 'id4'
78
+ },
79
+ struct_field: {
80
+ "str_key": "val",
81
+ "int_key": 123,
82
+ "bool_key": true,
83
+ "nil_key": nil,
84
+ "list_key": [
85
+ {
86
+ "inner_key": "inner_val"
87
+ }
88
+ ]
89
+ },
90
+ list_value: ['F', 'Y', 'I'],
91
+ bare_value: 45,
92
+ timestamp_field: '2024-04-03 01:02:03 UTC',
93
+ }
94
+ post '/test4', params: params, as: :json
95
+ expect(response).to be_successful
96
+ expect(response.parsed_body).to eq({
97
+ 'some_int' => 4,
98
+ 'full_response' => %({"testId":"abc","foobar":"xyz","repeatedString":["W","T","F"],"subRecord":{"subId":"id1","anotherId":"id2"},"secondRecord":{"subId":"id3","anotherId":"id4"},"structField":{"bool_key":true,"str_key":"val","nil_key":null,"list_key":[{"inner_key":"inner_val"}],"int_key":123},"timestampField":"2024-04-03T01:02:03Z","listValue":["F","Y","I"],"bareValue":45})
99
+ })
100
+ end
101
+ end
102
+
103
+ describe 'numeric timestamp' do
104
+ it 'should be successful' do
105
+ params = {
106
+ timestamp_field: 1712692452
107
+ }
108
+ post "/test4", params: params, as: :json
109
+ expect(response).to be_successful
110
+ expect(response.parsed_body).to eq({
111
+ 'some_int' => 4,
112
+ 'full_response' => %({"timestampField":"2024-04-09T19:54:12Z"})
113
+ })
114
+ end
115
+ end
116
+
117
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_controller/railtie"
4
+
5
+ class GrpcApp < Rails::Application
6
+ initializer(:host_config) do
7
+ Rails.application.config.hosts << "www.example.com"
8
+ end
9
+ end
10
+ GrpcApp.initialize!
11
+
12
+ require 'rspec/rails'
13
+
14
+ $LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
15
+
16
+ RSpec.configure do |config|
17
+ config.full_backtrace = true
18
+ config.render_views
19
+
20
+ config.mock_with(:rspec) do |mocks|
21
+ mocks.yield_receiver_to_any_instance_implementation_blocks = true
22
+ mocks.verify_partial_doubles = true
23
+ end
24
+ end
25
+
26
+ require_relative '../protoc-gen-rails/testdata/base/app/controllers/my_service_controller'
27
+ Rails.application.routes.draw_paths.push("#{Rails.root}/protoc-gen-rails/testdata/base/config/routes")
28
+ Rails.application.routes.draw do
29
+ draw(:grpc)
30
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: test_service.proto
4
+
5
+ require 'google/protobuf'
6
+
7
+ require 'google/api/annotations_pb'
8
+ require 'google/protobuf/struct_pb'
9
+ require 'google/protobuf/timestamp_pb'
10
+
11
+
12
+ descriptor_data = "\n\x12test_service.proto\x12\x08testdata\x1a\x1cgoogle/api/annotations.proto\x1a\x1cgoogle/protobuf/struct.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"\xdc\x02\n\x0bTestRequest\x12\x0f\n\x07test_id\x18\x01 \x01(\t\x12\x0e\n\x06\x66oobar\x18\x02 \x01(\t\x12\x17\n\x0frepeated_string\x18\x03 \x03(\t\x12\'\n\nsub_record\x18\x04 \x01(\x0b\x32\x13.testdata.SubRecord\x12*\n\rsecond_record\x18\x05 \x01(\x0b\x32\x13.testdata.SubRecord\x12-\n\x0cstruct_field\x18\x06 \x01(\x0b\x32\x17.google.protobuf.Struct\x12\x33\n\x0ftimestamp_field\x18\x07 \x01(\x0b\x32\x1a.google.protobuf.Timestamp\x12.\n\nlist_value\x18\x08 \x01(\x0b\x32\x1a.google.protobuf.ListValue\x12*\n\nbare_value\x18\t \x01(\x0b\x32\x16.google.protobuf.Value\"/\n\tSubRecord\x12\x0e\n\x06sub_id\x18\x01 \x01(\t\x12\x12\n\nanother_id\x18\x02 \x01(\t\"7\n\x0cTestResponse\x12\x10\n\x08some_int\x18\x01 \x01(\x05\x12\x15\n\rfull_response\x18\x02 \x01(\t2\xdf\x02\n\tMyService\x12T\n\x04Test\x12\x15.testdata.TestRequest\x1a\x16.testdata.TestResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\x12\x15/test/{foobar=blah/*}\x12U\n\x05Test2\x12\x15.testdata.TestRequest\x1a\x16.testdata.TestResponse\"\x1d\x82\xd3\xe4\x93\x02\x17\"\x06/test2:\rsecond_record\x12Z\n\x05Test3\x12\x15.testdata.TestRequest\x1a\x16.testdata.TestResponse\"\"\x82\xd3\xe4\x93\x02\x1c\"\x1a/test3/{sub_record.sub_id}\x12I\n\x05Test4\x12\x15.testdata.TestRequest\x1a\x16.testdata.TestResponse\"\x11\x82\xd3\xe4\x93\x02\x0b\"\x06/test4:\x01*b\x06proto3"
13
+
14
+ pool = Google::Protobuf::DescriptorPool.generated_pool
15
+
16
+ begin
17
+ pool.add_serialized_file(descriptor_data)
18
+ rescue TypeError
19
+ # Compatibility code: will be removed in the next major version.
20
+ require 'google/protobuf/descriptor_pb'
21
+ parsed = Google::Protobuf::FileDescriptorProto.decode(descriptor_data)
22
+ parsed.clear_dependency
23
+ serialized = parsed.class.encode(parsed)
24
+ file = pool.add_serialized_file(serialized)
25
+ warn "Warning: Protobuf detected an import path issue while loading generated file #{__FILE__}"
26
+ imports = [
27
+ ["google.protobuf.Struct", "google/protobuf/struct.proto"],
28
+ ["google.protobuf.Timestamp", "google/protobuf/timestamp.proto"],
29
+ ]
30
+ imports.each do |type_name, expected_filename|
31
+ import_file = pool.lookup(type_name).file_descriptor
32
+ if import_file.name != expected_filename
33
+ warn "- #{file.name} imports #{expected_filename}, but that import was loaded as #{import_file.name}"
34
+ end
35
+ end
36
+ warn "Each proto file must use a consistent fully-qualified name."
37
+ warn "This will become an error in the next major version."
38
+ end
39
+
40
+ module Testdata
41
+ TestRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("testdata.TestRequest").msgclass
42
+ SubRecord = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("testdata.SubRecord").msgclass
43
+ TestResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("testdata.TestResponse").msgclass
44
+ end
@@ -0,0 +1,25 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: test_service.proto for package 'testdata'
3
+
4
+ require 'grpc'
5
+ require 'test_service_pb'
6
+
7
+ module Testdata
8
+ module MyService
9
+ class Service
10
+
11
+ include ::GRPC::GenericService
12
+
13
+ self.marshal_class_method = :encode
14
+ self.unmarshal_class_method = :decode
15
+ self.service_name = 'testdata.MyService'
16
+
17
+ rpc :Test, ::Testdata::TestRequest, ::Testdata::TestResponse
18
+ rpc :Test2, ::Testdata::TestRequest, ::Testdata::TestResponse
19
+ rpc :Test3, ::Testdata::TestRequest, ::Testdata::TestResponse
20
+ rpc :Test4, ::Testdata::TestRequest, ::Testdata::TestResponse
21
+ end
22
+
23
+ Stub = Service.rpc_stub_class
24
+ end
25
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Orner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-04-04 00:00:00.000000000 Z
11
+ date: 2024-04-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grpc
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '6.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description:
42
56
  email:
43
57
  - daniel.orner@flipp.com
@@ -49,6 +63,8 @@ files:
49
63
  - ".gitignore"
50
64
  - CHANGELOG
51
65
  - CODE_OF_CONDUCT.md
66
+ - Gemfile
67
+ - Gemfile.lock
52
68
  - README.md
53
69
  - grpc-rest.gemspec
54
70
  - lib/grpc_rest.rb
@@ -66,7 +82,13 @@ files:
66
82
  - protoc-gen-rails/main_test.go
67
83
  - protoc-gen-rails/testdata/base/app/controllers/my_service_controller.rb
68
84
  - protoc-gen-rails/testdata/base/config/routes/grpc.rb
85
+ - protoc-gen-rails/testdata/no_service/.keep
86
+ - protoc-gen-rails/testdata/test.proto
69
87
  - protoc-gen-rails/testdata/test_service.proto
88
+ - spec/grpc_rest_spec.rb
89
+ - spec/spec_helper.rb
90
+ - spec/test_service_pb.rb
91
+ - spec/test_service_services_pb.rb
70
92
  homepage: ''
71
93
  licenses:
72
94
  - MIT
@@ -90,4 +112,8 @@ rubygems_version: 3.4.10
90
112
  signing_key:
91
113
  specification_version: 4
92
114
  summary: Generate Rails controllers and routes from gRPC definitions.
93
- test_files: []
115
+ test_files:
116
+ - spec/grpc_rest_spec.rb
117
+ - spec/spec_helper.rb
118
+ - spec/test_service_pb.rb
119
+ - spec/test_service_services_pb.rb