gruf 2.7.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
data/gruf.gemspec CHANGED
@@ -15,7 +15,7 @@
15
15
  # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
16
16
  # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
17
17
  #
18
- $:.push File.expand_path("../lib", __FILE__)
18
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
19
19
  require 'gruf/version'
20
20
 
21
21
  Gem::Specification.new do |spec|
@@ -30,19 +30,37 @@ Gem::Specification.new do |spec|
30
30
  spec.homepage = 'https://github.com/bigcommerce/gruf'
31
31
 
32
32
  spec.files = Dir['README.md', 'CHANGELOG.md', 'CODE_OF_CONDUCT.md', 'lib/**/*', 'gruf.gemspec']
33
- spec.executables << 'gruf'
33
+ spec.executables << 'gruf'
34
34
  spec.require_paths = ['lib']
35
35
 
36
- spec.required_ruby_version = '~> 2.4'
36
+ spec.required_ruby_version = '>= 2.6', '< 3.1'
37
37
 
38
38
  spec.add_development_dependency 'bundler', '~> 1.11'
39
- spec.add_development_dependency 'rake', '~> 10.0'
40
- spec.add_development_dependency 'pry', '~> 0.11'
39
+ spec.add_development_dependency 'bundler-audit', '>= 0.6'
40
+ # rubocop:disable Gemspec/RubyVersionGlobalsUsage
41
+ spec.add_development_dependency(
42
+ 'factory_bot',
43
+ (Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.5') ? '>= 6.1' : '~> 5.2')
44
+ )
45
+ # rubocop:enable Gemspec/RubyVersionGlobalsUsage
46
+ spec.add_development_dependency 'ffaker', '>= 2.15'
47
+ spec.add_development_dependency 'pry', '~> 0.12'
48
+ spec.add_development_dependency 'pry-byebug', '>= 3.9'
49
+ spec.add_development_dependency 'rake', '>= 10.0'
50
+ spec.add_development_dependency 'rspec', '>= 3.8'
51
+ spec.add_development_dependency 'rspec_junit_formatter', '>= 0.4'
52
+ spec.add_development_dependency 'rubocop', '>= 1.0'
53
+ spec.add_development_dependency 'rubocop-performance', '>= 0.0.1'
54
+ spec.add_development_dependency 'rubocop-rspec', '>= 2.0'
55
+ spec.add_development_dependency 'rubocop-thread_safety', '>= 0.3'
56
+ spec.add_development_dependency 'simplecov', '>= 0.16'
41
57
 
42
- spec.add_runtime_dependency 'grpc', '~> 1.10'
43
- spec.add_runtime_dependency 'grpc-tools', '~> 1.10'
44
58
  spec.add_runtime_dependency 'activesupport', '> 4'
45
-
46
59
  spec.add_runtime_dependency 'concurrent-ruby', '> 1'
60
+ spec.add_runtime_dependency 'e2mmap', '~> 0.1'
61
+ spec.add_runtime_dependency 'grpc', '~> 1.10'
62
+ spec.add_runtime_dependency 'grpc-tools', '~> 1.10'
63
+ spec.add_runtime_dependency 'json', '>= 2.3'
47
64
  spec.add_runtime_dependency 'slop', '~> 4.6'
65
+ spec.add_runtime_dependency 'thwait', '~> 0.1'
48
66
  end
@@ -38,7 +38,7 @@ module Gruf
38
38
  @services = services || Gruf.services
39
39
  @hook_executor = hook_executor || Gruf::Hooks::Executor.new(hooks: Gruf.hooks&.prepare)
40
40
  @server = server || Gruf::Server.new(Gruf.server_options)
41
- @logger = logger || Gruf.logger || ::Logger.new(STDERR)
41
+ @logger = logger || Gruf.logger || ::Logger.new($stderr)
42
42
  end
43
43
 
44
44
  ##
@@ -67,7 +67,23 @@ module Gruf
67
67
  # Setup options for CLI execution and configure Gruf based on inputs
68
68
  #
69
69
  def setup!
70
- opts = Slop.parse(@args) do |o|
70
+ opts = parse_options
71
+
72
+ Gruf.server_binding_url = opts[:host] if opts[:host]
73
+ if opts.suppress_default_interceptors?
74
+ Gruf.interceptors.remove(Gruf::Interceptors::ActiveRecord::ConnectionReset)
75
+ Gruf.interceptors.remove(Gruf::Interceptors::Instrumentation::OutputMetadataTimer)
76
+ end
77
+ Gruf.backtrace_on_error = true if opts.backtrace_on_error?
78
+ end
79
+
80
+ ##
81
+ # Parse all CLI arguments into an options result
82
+ #
83
+ # @return [Slop::Result]
84
+ #
85
+ def parse_options
86
+ ::Slop.parse(@args) do |o|
71
87
  o.null '-h', '--help', 'Display help message' do
72
88
  puts o
73
89
  exit(0)
@@ -80,10 +96,6 @@ module Gruf
80
96
  exit(0)
81
97
  end
82
98
  end
83
-
84
- Gruf.server_binding_url = opts[:host] if opts[:host]
85
- Gruf.use_default_interceptors = false if opts.suppress_default_interceptors?
86
- Gruf.backtrace_on_error = true if opts.backtrace_on_error?
87
99
  end
88
100
  end
89
101
  end
data/lib/gruf/client.rb CHANGED
@@ -60,9 +60,10 @@ module Gruf
60
60
  @opts = options || {}
61
61
  @opts[:password] = @opts.fetch(:password, '').to_s
62
62
  @opts[:hostname] = @opts.fetch(:hostname, Gruf.default_client_host)
63
+ @opts[:channel_credentials] = @opts.fetch(:channel_credentials, Gruf.default_channel_credentials)
63
64
  @error_factory = Gruf::Client::ErrorFactory.new
64
- client_options[:timeout] = client_options[:timeout].to_i if client_options.key?(:timeout)
65
- client = "#{service}::Stub".constantize.new(@opts[:hostname], build_ssl_credentials, client_options)
65
+ client_options[:timeout] = parse_timeout(client_options[:timeout]) if client_options.key?(:timeout)
66
+ client = "#{service}::Stub".constantize.new(@opts[:hostname], build_ssl_credentials, **client_options)
66
67
  super(client)
67
68
  end
68
69
 
@@ -190,6 +191,8 @@ module Gruf
190
191
  #
191
192
  # :nocov:
192
193
  def build_ssl_credentials
194
+ return opts[:channel_credentials] if opts[:channel_credentials]
195
+
193
196
  cert = nil
194
197
  if opts[:ssl_certificate_file]
195
198
  cert = File.read(opts[:ssl_certificate_file]).to_s.strip
@@ -213,5 +216,27 @@ module Gruf
213
216
  Gruf::Serializers::Errors::Json
214
217
  end
215
218
  end
219
+
220
+ ##
221
+ # Handle various timeout values and prevent improper value setting
222
+ #
223
+ # @see GRPC::Core::TimeConsts#from_relative_time
224
+ # @param [mixed] timeout
225
+ # @return [Float]
226
+ # @return [GRPC::Core::TimeSpec]
227
+ #
228
+ def parse_timeout(timeout)
229
+ if timeout.nil?
230
+ GRPC::Core::TimeConsts::ZERO
231
+ elsif timeout.is_a?(GRPC::Core::TimeSpec)
232
+ timeout
233
+ elsif timeout.is_a?(Numeric)
234
+ timeout
235
+ elsif timeout.respond_to?(:to_f)
236
+ timeout.to_f
237
+ else
238
+ raise ArgumentError, 'timeout is not a valid value: does not respond to to_f'
239
+ end
240
+ end
216
241
  end
217
242
  end
@@ -33,6 +33,7 @@ module Gruf
33
33
  #
34
34
  def initialize(error)
35
35
  @error = error
36
+ super
36
37
  end
37
38
  end
38
39
 
@@ -57,6 +58,7 @@ module Gruf
57
58
  class FailedPrecondition < Error; end
58
59
  class Internal < Error; end
59
60
  class PermissionDenied < Error; end
61
+ class ResourceExhausted < Error; end
60
62
  class Unauthenticated < Error; end
61
63
  class Unavailable < Error; end
62
64
  class Unimplemented < Error; end
@@ -26,6 +26,7 @@ module Gruf
26
26
  server_options: {},
27
27
  interceptors: nil,
28
28
  hooks: nil,
29
+ default_channel_credentials: nil,
29
30
  default_client_host: '',
30
31
  use_ssl: false,
31
32
  ssl_crt_file: '',
@@ -97,7 +98,7 @@ module Gruf
97
98
  #
98
99
  def reset
99
100
  VALID_CONFIG_KEYS.each do |k, v|
100
- send((k.to_s + '='), v)
101
+ send("#{k}=", v)
101
102
  end
102
103
  self.interceptors = Gruf::Interceptors::Registry.new
103
104
  self.hooks = Gruf::Hooks::Registry.new
@@ -106,7 +107,7 @@ module Gruf
106
107
  self.logger = Rails.logger
107
108
  else
108
109
  require 'logger'
109
- self.logger = ::Logger.new(STDOUT)
110
+ self.logger = ::Logger.new($stdout)
110
111
  end
111
112
  self.grpc_logger = logger if grpc_logger.nil?
112
113
  self.ssl_crt_file = "#{root_path}config/ssl/#{environment}.crt"
@@ -65,7 +65,9 @@ module Gruf
65
65
  def self.bind(service)
66
66
  service_class = service.name.constantize
67
67
  Gruf.services << service_class
68
+ # rubocop:disable ThreadSafety/InstanceVariableInClassMethod
68
69
  @bound_service = service_class
70
+ # rubocop:enable ThreadSafety/InstanceVariableInClassMethod
69
71
  ServiceBinder.new(service_class).bind!(self)
70
72
  end
71
73
 
@@ -58,7 +58,7 @@ module Gruf
58
58
  # @param [Class] service The class of the service being executed against
59
59
  # @param [GRPC::RpcDesc] rpc_desc The RPC descriptor of the call
60
60
  # @param [GRPC::ActiveCall] active_call The restricted view of the call
61
- # @param [Object] message The protobuf message (or messages) of the request
61
+ # @param [Object|Google::Protobuf::MessageExts] message The protobuf message (or messages) of the request
62
62
  #
63
63
  def initialize(method_key:, service:, rpc_desc:, active_call:, message:)
64
64
  @method_key = method_key
@@ -99,21 +99,23 @@ module Gruf
99
99
  # @return [String] The parsed service method name
100
100
  #
101
101
  def method_name
102
- "#{service_key}.#{method_key}"
102
+ "#{service_key}.#{@method_key}"
103
103
  end
104
104
 
105
105
  ##
106
106
  # Return all messages for this request, properly handling different request types
107
107
  #
108
- # @return Enumerable<Object> All messages for this request
108
+ # @return [Enumerable<Object>] All messages for this request
109
109
  #
110
110
  def messages
111
111
  if client_streamer?
112
- message.call { |msg| yield msg }
112
+ # rubocop:disable Style/ExplicitBlockArgument
113
+ @message.call { |msg| yield msg }
114
+ # rubocop:enable Style/ExplicitBlockArgument
113
115
  elsif bidi_streamer?
114
- message
116
+ @message
115
117
  else
116
- [message]
118
+ [@message]
117
119
  end
118
120
  end
119
121
  end
@@ -38,7 +38,7 @@ module Gruf
38
38
  ##
39
39
  # Bind all methods on the service to the passed controller
40
40
  #
41
- # @param [Gruf::Controllers::Base] controller
41
+ # @param [Class<Gruf::Controllers::Base>] controller
42
42
  #
43
43
  def bind!(controller)
44
44
  rpc_methods.each { |name, desc| bind_method(controller, name, desc) }
data/lib/gruf/error.rb CHANGED
@@ -126,7 +126,7 @@ module Gruf
126
126
  # @return [Hash] The newly set metadata
127
127
  #
128
128
  def metadata=(metadata)
129
- @metadata = metadata.map { |k, str| [k, str.to_s] }.to_h
129
+ @metadata = metadata.transform_values(&:to_s)
130
130
  end
131
131
 
132
132
  ##
@@ -39,7 +39,7 @@ module Gruf
39
39
  @hooks.each do |hook|
40
40
  next unless hook.respond_to?(name)
41
41
 
42
- hook.send(name, arguments)
42
+ hook.send(name, **arguments)
43
43
  end
44
44
  end
45
45
  end
@@ -43,7 +43,7 @@ module Gruf
43
43
  # @param [Hash] metadata A hash of outgoing metadata
44
44
  # @return [Object] The response message
45
45
  #
46
- def request_response(request: nil, call: nil, method: nil, metadata: nil)
46
+ def request_response(request: nil, call: nil, method: nil, metadata: nil, &block)
47
47
  rc = Gruf::Outbound::RequestContext.new(
48
48
  type: :request_response,
49
49
  requests: [request],
@@ -51,9 +51,7 @@ module Gruf
51
51
  method: method,
52
52
  metadata: metadata
53
53
  )
54
- call(request_context: rc) do
55
- yield
56
- end
54
+ call(request_context: rc, &block)
57
55
  end
58
56
 
59
57
  ##
@@ -65,7 +63,7 @@ module Gruf
65
63
  # @param [Hash] metadata A hash of outgoing metadata
66
64
  # @return [Object] The response message
67
65
  #
68
- def client_streamer(requests: nil, call: nil, method: nil, metadata: nil)
66
+ def client_streamer(requests: nil, call: nil, method: nil, metadata: nil, &block)
69
67
  rc = Gruf::Outbound::RequestContext.new(
70
68
  type: :client_streamer,
71
69
  requests: requests,
@@ -73,9 +71,7 @@ module Gruf
73
71
  method: method,
74
72
  metadata: metadata
75
73
  )
76
- call(request_context: rc) do
77
- yield
78
- end
74
+ call(request_context: rc, &block)
79
75
  end
80
76
 
81
77
  ##
@@ -87,7 +83,7 @@ module Gruf
87
83
  # @param [Hash] metadata A hash of outgoing metadata
88
84
  # @return [Object] The response message
89
85
  #
90
- def server_streamer(request: nil, call: nil, method: nil, metadata: nil)
86
+ def server_streamer(request: nil, call: nil, method: nil, metadata: nil, &block)
91
87
  rc = Gruf::Outbound::RequestContext.new(
92
88
  type: :server_streamer,
93
89
  requests: [request],
@@ -95,9 +91,7 @@ module Gruf
95
91
  method: method,
96
92
  metadata: metadata
97
93
  )
98
- call(request_context: rc) do
99
- yield
100
- end
94
+ call(request_context: rc, &block)
101
95
  end
102
96
 
103
97
  ##
@@ -108,7 +102,7 @@ module Gruf
108
102
  # @param [Method] method The method being called
109
103
  # @param [Hash] metadata A hash of outgoing metadata
110
104
  #
111
- def bidi_streamer(requests: nil, call: nil, method: nil, metadata: nil)
105
+ def bidi_streamer(requests: nil, call: nil, method: nil, metadata: nil, &block)
112
106
  rc = Gruf::Outbound::RequestContext.new(
113
107
  type: :bidi_streamer,
114
108
  requests: requests,
@@ -116,9 +110,7 @@ module Gruf
116
110
  method: method,
117
111
  metadata: metadata
118
112
  )
119
- call(request_context: rc) do
120
- yield
121
- end
113
+ call(request_context: rc, &block)
122
114
  end
123
115
  end
124
116
  end
@@ -28,14 +28,14 @@ module Gruf
28
28
  #
29
29
  # @param [Array<Gruf::Interceptors::ServerInterceptor>] interceptors
30
30
  #
31
- def initialize(interceptors = [])
32
- @interceptors = interceptors
31
+ def initialize(interceptors = nil)
32
+ @interceptors = interceptors || []
33
33
  end
34
34
 
35
35
  ##
36
36
  # Intercept the given request and run interceptors in a FIFO execution order
37
37
  #
38
- def intercept!
38
+ def intercept!(&block)
39
39
  return yield if @interceptors.none?
40
40
 
41
41
  i = @interceptors.pop
@@ -45,7 +45,7 @@ module Gruf
45
45
 
46
46
  i.call do
47
47
  if @interceptors.any?
48
- intercept! { yield }
48
+ intercept!(&block)
49
49
  else
50
50
  yield
51
51
  end
@@ -27,12 +27,10 @@ module Gruf
27
27
  ##
28
28
  # Handle the instrumented response. Note: this will only instrument timings of _successful_ responses.
29
29
  #
30
- def call
30
+ def call(&block)
31
31
  return unless active_call.respond_to?(:output_metadata)
32
32
 
33
- result = Gruf::Interceptors::Timer.time do
34
- yield
35
- end
33
+ result = Gruf::Interceptors::Timer.time(&block)
36
34
  output_metadata.update(metadata_key => result.elapsed.to_s)
37
35
 
38
36
  raise result.message unless result.successful?
@@ -28,9 +28,11 @@ module Gruf
28
28
  # Format the parameters into a loggable string. Must be implemented in every derivative class
29
29
  #
30
30
  # @param [Hash] _payload The incoming request payload
31
+ # @param [Gruf::Controllers::Request] request The current controller request
32
+ # @param [Gruf::Interceptors::Timer::Result] result The timed result of the response
31
33
  # @return [String] The formatted string
32
34
  #
33
- def format(_payload)
35
+ def format(_payload, request:, result:)
34
36
  raise NotImplementedError
35
37
  end
36
38
  end
@@ -30,9 +30,11 @@ module Gruf
30
30
  # Format the request into a JSON-friendly payload
31
31
  #
32
32
  # @param [Hash] payload The incoming request payload
33
+ # @param [Gruf::Controllers::Request] request The current controller request
34
+ # @param [Gruf::Interceptors::Timer::Result] result The timed result of the response
33
35
  # @return [String] The JSON representation of the payload
34
36
  #
35
- def format(payload)
37
+ def format(payload, request:, result:)
36
38
  payload.merge(format: 'json').to_json
37
39
  end
38
40
  end
@@ -28,9 +28,11 @@ module Gruf
28
28
  # Format the request by only outputting the message body and params (if set to log params)
29
29
  #
30
30
  # @param [Hash] payload The incoming request payload
31
+ # @param [Gruf::Controllers::Request] request The current controller request
32
+ # @param [Gruf::Interceptors::Timer::Result] result The timed result of the response
31
33
  # @return [String] The formatted string
32
34
  #
33
- def format(payload)
35
+ def format(payload, request:, result:)
34
36
  time = payload.fetch(:duration, 0)
35
37
  grpc_status = payload.fetch(:grpc_status, 'GRPC::Ok')
36
38
  route_key = payload.fetch(:method, 'unknown')
@@ -61,12 +61,10 @@ module Gruf
61
61
  #
62
62
  # @return [String]
63
63
  #
64
- def call
65
- return yield if options.fetch(:ignore_methods, []).include?(request.method_name)
64
+ def call(&block)
65
+ return yield if options.fetch(:ignore_methods, [])&.include?(request.method_name)
66
66
 
67
- result = Gruf::Interceptors::Timer.time do
68
- yield
69
- end
67
+ result = Gruf::Interceptors::Timer.time(&block)
70
68
 
71
69
  # Fetch log level options and merge with default...
72
70
  log_level_map = LOG_LEVEL_MAP.merge(options.fetch(:log_levels, {}))
@@ -100,7 +98,7 @@ module Gruf
100
98
  payload[:time] = Time.now.to_s
101
99
  payload[:host] = Socket.gethostname
102
100
 
103
- logger.send(type, formatter.format(payload))
101
+ logger.send(type, formatter.format(payload, request: request, result: result))
104
102
 
105
103
  raise result.message unless result.successful?
106
104
 
@@ -113,7 +111,7 @@ module Gruf
113
111
  # @return [::Gruf::Logger]
114
112
  #
115
113
  def logger
116
- @logger ||= options.fetch(:logger, ::Gruf.logger)
114
+ @logger ||= (options.fetch(:logger, nil) || Gruf.logger)
117
115
  end
118
116
 
119
117
  ##
@@ -164,26 +162,26 @@ module Gruf
164
162
  end
165
163
 
166
164
  ##
167
- # Redact any blacklisted params and return an updated hash
165
+ # Redact any blocklisted params and return an updated hash
168
166
  #
169
167
  # @param [Hash] params The hash of parameters to sanitize
170
168
  # @return [Hash] The sanitized params in hash form
171
169
  #
172
170
  def sanitize(params = {})
173
- blacklists = options.fetch(:blacklist, []).map(&:to_s)
174
- redacted_string = options.fetch(:redacted_string, 'REDACTED')
175
- blacklists.each do |blacklist|
176
- parts = blacklist.split('.').map(&:to_sym)
171
+ blocklists = (options.fetch(:blocklist, []) || []).map(&:to_s)
172
+ redacted_string = options.fetch(:redacted_string, nil) || 'REDACTED'
173
+ blocklists.each do |blocklist|
174
+ parts = blocklist.split('.').map(&:to_sym)
177
175
  redact!(parts, 0, params, redacted_string)
178
176
  end
179
177
  params
180
178
  end
181
179
 
182
180
  ##
183
- # Helper method to recursively redact based on the black list
181
+ # Helper method to recursively redact based on the blocklist
184
182
  #
185
- # @param [Array] parts The blacklist. ex. 'data.schema' -> [:data, :schema]
186
- # @param [Integer] idx The current index of the blacklist
183
+ # @param [Array] parts The blocklist. ex. 'data.schema' -> [:data, :schema]
184
+ # @param [Integer] idx The current index of the blocklist
187
185
  # @param [Hash] params The hash of parameters to sanitize
188
186
  # @param [String] redacted_string The custom redact string
189
187
  #