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 +4 -4
- data/.github/workflows/CI.yml +16 -2
- data/.gitignore +2 -0
- data/CHANGELOG +8 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +190 -0
- data/README.md +10 -26
- data/grpc-rest.gemspec +2 -0
- data/lib/grpc_rest/version.rb +1 -1
- data/lib/grpc_rest.rb +26 -21
- data/protoc-gen-rails/internal/output.go +7 -2
- data/protoc-gen-rails/internal/utils.go +1 -1
- data/protoc-gen-rails/main.go +6 -4
- data/protoc-gen-rails/main_test.go +22 -1
- data/protoc-gen-rails/testdata/base/app/controllers/my_service_controller.rb +14 -3
- data/protoc-gen-rails/testdata/base/config/routes/grpc.rb +7 -3
- data/protoc-gen-rails/testdata/no_service/.keep +0 -0
- data/protoc-gen-rails/testdata/test.proto +6 -0
- data/protoc-gen-rails/testdata/test_service.proto +14 -2
- data/spec/grpc_rest_spec.rb +117 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/test_service_pb.rb +44 -0
- data/spec/test_service_services_pb.rb +25 -0
- metadata +29 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8851853946d82493dd832a39be201f3b5b0bc5e70c7837e9be7a3db4d1d30460
|
4
|
+
data.tar.gz: a44a3d29c174df98af07cc91693127fbfc8e6a5c42b282bb7119573af53e5d28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfa0ed32f96706a4d8e6b0bfccc72d2e0d018f64fa4e5e87372bc3527c7b7521d8b1aecc48520df695dda54ac717cc5d7440319ea767c075204464d1965c668c
|
7
|
+
data.tar.gz: bbaa98db8d37ec4b91325438422fa1681fc0a35186f3c3e427cd1cb933bceaba7f806fe9cbf4269f4bdbd7315bbe1870b28aae14f4c1d59294dc974333652c4c
|
data/.github/workflows/CI.yml
CHANGED
@@ -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/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
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
|
-
|
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
|
-
|
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
|
-
*
|
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
data/lib/grpc_rest/version.rb
CHANGED
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
|
-
|
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(
|
47
|
+
params[field] = Google::Protobuf::Struct.from_hash(val)
|
42
48
|
when 'google.protobuf.Timestamp'
|
43
|
-
|
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(
|
55
|
+
params[field] = Google::Protobuf::Value.from_ruby(val)
|
46
56
|
when 'google.protobuf.ListValue'
|
47
|
-
params[field] = Google::Protobuf::ListValue.from_a(
|
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
|
-
|
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
|
-
|
110
|
-
|
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}}
|
126
|
+
{{.HttpMethod}} '{{.Path}}' => '{{.Controller}}#{{.MethodName}}'
|
122
127
|
{{end -}}
|
123
128
|
`
|
124
129
|
|
data/protoc-gen-rails/main.go
CHANGED
@@ -49,11 +49,13 @@ func main() {
|
|
49
49
|
files = append(files, fileResult)
|
50
50
|
}
|
51
51
|
}
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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: "
|
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"], "
|
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
|
-
|
3
|
-
|
4
|
-
|
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
|
@@ -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
|
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×tamp_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
|
data/spec/spec_helper.rb
ADDED
@@ -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.
|
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-
|
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
|