grpc-rest 0.1.5 → 0.1.7

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.
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