gruf 2.7.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
  #