grpc 1.81.0.pre1-aarch64-linux-gnu → 1.82.0.pre1-aarch64-linux-gnu

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/src/ruby/ext/grpc/rb_byte_buffer.c +1 -0
  3. data/src/ruby/ext/grpc/rb_call.c +3 -0
  4. data/src/ruby/ext/grpc/rb_call_credentials.c +1 -0
  5. data/src/ruby/ext/grpc/rb_channel.c +1 -0
  6. data/src/ruby/ext/grpc/rb_channel_args.c +1 -0
  7. data/src/ruby/ext/grpc/rb_channel_credentials.c +1 -0
  8. data/src/ruby/ext/grpc/rb_completion_queue.c +1 -0
  9. data/src/ruby/ext/grpc/rb_compression_options.c +1 -0
  10. data/src/ruby/ext/grpc/rb_grpc_imports.generated.c +4 -4
  11. data/src/ruby/ext/grpc/rb_grpc_imports.generated.h +6 -6
  12. data/src/ruby/ext/grpc/rb_server.c +1 -0
  13. data/src/ruby/ext/grpc/rb_server_credentials.c +1 -0
  14. data/src/ruby/ext/grpc/rb_xds_channel_credentials.c +1 -0
  15. data/src/ruby/ext/grpc/rb_xds_server_credentials.c +1 -0
  16. data/src/ruby/lib/grpc/3.2/grpc_c.so +0 -0
  17. data/src/ruby/lib/grpc/3.3/grpc_c.so +0 -0
  18. data/src/ruby/lib/grpc/3.4/grpc_c.so +0 -0
  19. data/src/ruby/lib/grpc/4.0/grpc_c.so +0 -0
  20. data/src/ruby/lib/grpc/core/call_credentials.rb +86 -0
  21. data/src/ruby/lib/grpc/core/channel_credentials.rb +69 -0
  22. data/src/ruby/lib/grpc/core/credentials_helper.rb +126 -0
  23. data/src/ruby/lib/grpc/generic/client_stub.rb +86 -73
  24. data/src/ruby/lib/grpc/generic/interceptors.rb +1 -1
  25. data/src/ruby/lib/grpc/version.rb +1 -1
  26. data/src/ruby/lib/grpc.rb +25 -0
  27. data/src/ruby/spec/call_credentials_spec.rb +52 -2
  28. data/src/ruby/spec/credentials_helper_spec.rb +80 -0
  29. data/src/ruby/spec/generic/client_stub_spec.rb +27 -4
  30. data/src/ruby/spec/generic/server_interceptors_spec.rb +17 -3
  31. metadata +7 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4eefae8cfb06c186758ca20bc3ed3fbcba7c282e6dceca0a940d22a7642c5140
4
- data.tar.gz: 3bc030d495a68ad97c8fca760b507e244b8f195b5f167a63b0379d005d73dc26
3
+ metadata.gz: 498c3294c6940248f50a6a058bf0c0967694e06e856eadd5c7ede49343d5983b
4
+ data.tar.gz: 815e71d46eda970f26df975436059bab6877a234dd0d9dca5636f11463fd0330
5
5
  SHA512:
6
- metadata.gz: 4a51f83de800a40a2d3345de53a415f0048972be619e6a6c8260c6e470de8146571275fdc3cdf2518f2b2e931d53a56b821668ec1167dbd6840a2611fd0491dc
7
- data.tar.gz: 899ecba43410f77cf128f5b14a1e1d6fa2c360facda02198f612e7b73d13045ebed36196c8ecfcd525374eda97cf640b0d075eb40e37414508dbf553f665f7e4
6
+ metadata.gz: fa1ffcf6aff99564e543ab32b98513fb660b22cce4c7c16159f2887c7a063dc44ff72853dc0541dc05bbd1cbcb4a28142c6d277d52e8af647e2aa2a6b0553663
7
+ data.tar.gz: aab8a687f2fe8b7beabd09737a675b171f590b609194eea97d43ed518124146f4c00311289d283dc4d7c6d6b44030b7930c41f9eebc7a0cdacca3db9cc5b6c89
@@ -23,6 +23,7 @@
23
23
  #include <grpc/byte_buffer_reader.h>
24
24
  #include <grpc/grpc.h>
25
25
  #include <grpc/slice.h>
26
+ #include <stdbool.h>
26
27
 
27
28
  #include "rb_grpc.h"
28
29
  #include "rb_grpc_imports.generated.h"
@@ -23,8 +23,11 @@
23
23
  #include <grpc/grpc.h>
24
24
  #include <grpc/impl/codegen/compression_types.h>
25
25
  #include <grpc/support/alloc.h>
26
+ #include <stdbool.h>
26
27
 
27
28
  #include "rb_byte_buffer.h"
29
+ /* TODO(nnepal): Include grpc/grpc_security.h for pure ruby call
30
+ * credentials after rb_call_credentials gets removed */
28
31
  #include "rb_call_credentials.h"
29
32
  #include "rb_completion_queue.h"
30
33
  #include "rb_grpc.h"
@@ -26,6 +26,7 @@
26
26
  #include <grpc/support/alloc.h>
27
27
  #include <grpc/support/log.h>
28
28
  #include <ruby/thread.h>
29
+ #include <stdbool.h>
29
30
 
30
31
  #include "rb_call.h"
31
32
  #include "rb_event_thread.h"
@@ -27,6 +27,7 @@
27
27
  #include <grpc/support/log.h>
28
28
  #include <grpc/support/time.h>
29
29
  #include <ruby/thread.h>
30
+ #include <stdbool.h>
30
31
 
31
32
  #include "rb_byte_buffer.h"
32
33
  #include "rb_call.h"
@@ -23,6 +23,7 @@
23
23
  #include <grpc/grpc.h>
24
24
  #include <grpc/support/alloc.h>
25
25
  #include <grpc/support/string_util.h>
26
+ #include <stdbool.h>
26
27
 
27
28
  #include "rb_grpc.h"
28
29
  #include "rb_grpc_imports.generated.h"
@@ -24,6 +24,7 @@
24
24
  #include <grpc/grpc.h>
25
25
  #include <grpc/grpc_security.h>
26
26
  #include <grpc/support/alloc.h>
27
+ #include <stdbool.h>
27
28
  #include <string.h>
28
29
 
29
30
  #include "rb_call_credentials.h"
@@ -24,6 +24,7 @@
24
24
  #include <grpc/support/log.h>
25
25
  #include <grpc/support/time.h>
26
26
  #include <ruby/thread.h>
27
+ #include <stdbool.h>
27
28
 
28
29
  #include "rb_grpc.h"
29
30
  #include "rb_grpc_imports.generated.h"
@@ -26,6 +26,7 @@
26
26
  #include <grpc/impl/grpc_types.h>
27
27
  #include <grpc/support/alloc.h>
28
28
  #include <grpc/support/string_util.h>
29
+ #include <stdbool.h>
29
30
  #include <string.h>
30
31
 
31
32
  #include "rb_byte_buffer.h"
@@ -159,9 +159,9 @@ grpc_server_register_method_type grpc_server_register_method_import;
159
159
  grpc_server_request_registered_call_type grpc_server_request_registered_call_import;
160
160
  grpc_server_create_type grpc_server_create_import;
161
161
  grpc_server_register_completion_queue_type grpc_server_register_completion_queue_import;
162
+ grpc_server_config_fetcher_arg_vtable_type grpc_server_config_fetcher_arg_vtable_import;
162
163
  grpc_server_config_fetcher_xds_create_type grpc_server_config_fetcher_xds_create_import;
163
- grpc_server_config_fetcher_destroy_type grpc_server_config_fetcher_destroy_import;
164
- grpc_server_set_config_fetcher_type grpc_server_set_config_fetcher_import;
164
+ grpc_server_config_fetcher_unref_type grpc_server_config_fetcher_unref_import;
165
165
  grpc_server_add_http2_port_type grpc_server_add_http2_port_import;
166
166
  grpc_server_start_type grpc_server_start_import;
167
167
  grpc_server_shutdown_and_notify_type grpc_server_shutdown_and_notify_import;
@@ -448,9 +448,9 @@ void grpc_rb_load_imports(HMODULE library) {
448
448
  grpc_server_request_registered_call_import = (grpc_server_request_registered_call_type) GetProcAddress(library, "grpc_server_request_registered_call");
449
449
  grpc_server_create_import = (grpc_server_create_type) GetProcAddress(library, "grpc_server_create");
450
450
  grpc_server_register_completion_queue_import = (grpc_server_register_completion_queue_type) GetProcAddress(library, "grpc_server_register_completion_queue");
451
+ grpc_server_config_fetcher_arg_vtable_import = (grpc_server_config_fetcher_arg_vtable_type) GetProcAddress(library, "grpc_server_config_fetcher_arg_vtable");
451
452
  grpc_server_config_fetcher_xds_create_import = (grpc_server_config_fetcher_xds_create_type) GetProcAddress(library, "grpc_server_config_fetcher_xds_create");
452
- grpc_server_config_fetcher_destroy_import = (grpc_server_config_fetcher_destroy_type) GetProcAddress(library, "grpc_server_config_fetcher_destroy");
453
- grpc_server_set_config_fetcher_import = (grpc_server_set_config_fetcher_type) GetProcAddress(library, "grpc_server_set_config_fetcher");
453
+ grpc_server_config_fetcher_unref_import = (grpc_server_config_fetcher_unref_type) GetProcAddress(library, "grpc_server_config_fetcher_unref");
454
454
  grpc_server_add_http2_port_import = (grpc_server_add_http2_port_type) GetProcAddress(library, "grpc_server_add_http2_port");
455
455
  grpc_server_start_import = (grpc_server_start_type) GetProcAddress(library, "grpc_server_start");
456
456
  grpc_server_shutdown_and_notify_import = (grpc_server_shutdown_and_notify_type) GetProcAddress(library, "grpc_server_shutdown_and_notify");
@@ -453,15 +453,15 @@ extern grpc_server_create_type grpc_server_create_import;
453
453
  typedef void(*grpc_server_register_completion_queue_type)(grpc_server* server, grpc_completion_queue* cq, void* reserved);
454
454
  extern grpc_server_register_completion_queue_type grpc_server_register_completion_queue_import;
455
455
  #define grpc_server_register_completion_queue grpc_server_register_completion_queue_import
456
+ typedef const grpc_arg_pointer_vtable*(*grpc_server_config_fetcher_arg_vtable_type)(void);
457
+ extern grpc_server_config_fetcher_arg_vtable_type grpc_server_config_fetcher_arg_vtable_import;
458
+ #define grpc_server_config_fetcher_arg_vtable grpc_server_config_fetcher_arg_vtable_import
456
459
  typedef grpc_server_config_fetcher*(*grpc_server_config_fetcher_xds_create_type)(grpc_server_xds_status_notifier notifier, const grpc_channel_args* args);
457
460
  extern grpc_server_config_fetcher_xds_create_type grpc_server_config_fetcher_xds_create_import;
458
461
  #define grpc_server_config_fetcher_xds_create grpc_server_config_fetcher_xds_create_import
459
- typedef void(*grpc_server_config_fetcher_destroy_type)(grpc_server_config_fetcher* config_fetcher);
460
- extern grpc_server_config_fetcher_destroy_type grpc_server_config_fetcher_destroy_import;
461
- #define grpc_server_config_fetcher_destroy grpc_server_config_fetcher_destroy_import
462
- typedef void(*grpc_server_set_config_fetcher_type)(grpc_server* server, grpc_server_config_fetcher* config_fetcher);
463
- extern grpc_server_set_config_fetcher_type grpc_server_set_config_fetcher_import;
464
- #define grpc_server_set_config_fetcher grpc_server_set_config_fetcher_import
462
+ typedef void(*grpc_server_config_fetcher_unref_type)(grpc_server_config_fetcher* config_fetcher);
463
+ extern grpc_server_config_fetcher_unref_type grpc_server_config_fetcher_unref_import;
464
+ #define grpc_server_config_fetcher_unref grpc_server_config_fetcher_unref_import
465
465
  typedef int(*grpc_server_add_http2_port_type)(grpc_server* server, const char* addr, grpc_server_credentials* creds);
466
466
  extern grpc_server_add_http2_port_type grpc_server_add_http2_port_import;
467
467
  #define grpc_server_add_http2_port grpc_server_add_http2_port_import
@@ -25,6 +25,7 @@
25
25
  #include <grpc/grpc_security.h>
26
26
  #include <grpc/support/atm.h>
27
27
  #include <grpc/support/log.h>
28
+ #include <stdbool.h>
28
29
 
29
30
  #include "rb_byte_buffer.h"
30
31
  #include "rb_call.h"
@@ -23,6 +23,7 @@
23
23
  #include <grpc/credentials.h>
24
24
  #include <grpc/grpc.h>
25
25
  #include <grpc/grpc_security.h>
26
+ #include <stdbool.h>
26
27
 
27
28
  #include "rb_grpc.h"
28
29
  #include "rb_grpc_imports.generated.h"
@@ -24,6 +24,7 @@
24
24
  #include <grpc/grpc.h>
25
25
  #include <grpc/grpc_security.h>
26
26
  #include <grpc/support/alloc.h>
27
+ #include <stdbool.h>
27
28
  #include <string.h>
28
29
 
29
30
  #include "rb_call_credentials.h"
@@ -23,6 +23,7 @@
23
23
  #include <grpc/credentials.h>
24
24
  #include <grpc/grpc.h>
25
25
  #include <grpc/grpc_security.h>
26
+ #include <stdbool.h>
26
27
 
27
28
  #include "rb_grpc.h"
28
29
  #include "rb_grpc_imports.generated.h"
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 gRPC authors.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module GRPC
18
+ module Core
19
+ # CallCredentials represents per-call credentials.
20
+ class CallCredentials
21
+ attr_reader :auth_proc
22
+
23
+ def initialize(auth_proc = nil, &block)
24
+ @auth_proc = auth_proc || block
25
+ fail TypeError, 'Argument to CallCredentials#new must be a proc' unless @auth_proc.is_a?(Proc)
26
+ end
27
+
28
+ def get_metadata(context)
29
+ @auth_proc.call(context)
30
+ end
31
+
32
+ def composite?
33
+ false
34
+ end
35
+
36
+ def compose(*others)
37
+ return self if others.empty?
38
+ valid_others = validate_credentials_list!(others)
39
+ CompositeCallCredentials.new([self] + valid_others)
40
+ end
41
+
42
+ private
43
+
44
+ def validate_credentials_list!(list)
45
+ list.flatten.each do |o|
46
+ fail TypeError, "Argument to compose must be a CallCredentials, got #{o.class}" \
47
+ unless o.is_a?(CallCredentials)
48
+ end
49
+ end
50
+ end
51
+
52
+ class CompositeCallCredentials < CallCredentials
53
+ def initialize(*creds)
54
+ flat_creds = creds.flatten.flat_map do |c|
55
+ c.composite? ? c.creds : c
56
+ end
57
+ @creds = flat_creds.uniq
58
+ super(proc { |context| get_metadata(context) })
59
+ end
60
+
61
+ def composite?
62
+ true
63
+ end
64
+
65
+ def get_metadata(context)
66
+ @creds.each_with_object({}) do |c, metadata|
67
+ creds_metadata = c.get_metadata(context)
68
+ next unless creds_metadata
69
+ metadata.merge!(
70
+ creds_metadata.is_a?(Hash) ? creds_metadata : fail(TypeError, "Call credentials must return Hash or nil, got #{creds_metadata.class}")
71
+ )
72
+ end
73
+ end
74
+
75
+ def compose(*others)
76
+ return self if others.empty?
77
+ valid_others = validate_credentials_list!(others)
78
+ CompositeCallCredentials.new(@creds + valid_others)
79
+ end
80
+
81
+ protected
82
+
83
+ attr_reader :creds
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 gRPC authors.
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ module GRPC
18
+ module Core
19
+ # Provides shared #compose logic for channel credential classes.
20
+ # @api private
21
+ module ChannelCredentialsComposable
22
+ def compose(*others)
23
+ return self if others.empty?
24
+ flat_others = others.flatten
25
+
26
+ flat_others.each do |o|
27
+ fail TypeError, "Argument to compose must be a CallCredentials, got #{o.class}" \
28
+ unless o.is_a?(CallCredentials)
29
+ end
30
+
31
+ call_creds = flat_others.size == 1 ? flat_others.first : CompositeCallCredentials.new(flat_others)
32
+ CompositeChannelCredentials.new(self, call_creds)
33
+ end
34
+ end
35
+
36
+ class ChannelCredentials
37
+ prepend ChannelCredentialsComposable
38
+ end
39
+
40
+ class XdsChannelCredentials
41
+ prepend ChannelCredentialsComposable
42
+ end
43
+
44
+ class CompositeChannelCredentials
45
+ attr_reader :channel_credentials, :call_credentials
46
+
47
+ def initialize(channel_creds, call_creds)
48
+ @channel_credentials = channel_creds
49
+ @call_credentials = call_creds
50
+ end
51
+
52
+ def compose(*others)
53
+ return self if others.empty?
54
+ flat_others = others.flatten
55
+
56
+ flat_others.each do |o|
57
+ fail TypeError, "Argument to compose must be a CallCredentials, got #{o.class}" \
58
+ unless o.is_a?(CallCredentials)
59
+ end
60
+
61
+ if @call_credentials
62
+ CompositeChannelCredentials.new(@channel_credentials, @call_credentials.compose(*flat_others))
63
+ else
64
+ CompositeChannelCredentials.new(@channel_credentials, CompositeCallCredentials.new(flat_others))
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,126 @@
1
+ # Copyright 2026 gRPC authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module GRPC
16
+ module Core
17
+ # Utility methods for resolving and applying call credentials to metadata.
18
+ # @api private
19
+ module CallCredentialsHelper
20
+ VALID_HEADER_KEY_PATTERN = /\A[a-z0-9\-_.]+\z/
21
+
22
+ # Composes channel and per-call credentials when both present, otherwise
23
+ # returns whichever is available.
24
+ def self.resolve(channel_call_creds, call_credentials)
25
+ return channel_call_creds.compose(call_credentials) if channel_call_creds && call_credentials
26
+ channel_call_creds || call_credentials
27
+ end
28
+
29
+ # Returns true if +key+ is a valid gRPC metadata header key.
30
+ def self.valid_header_key?(key)
31
+ !key.nil? && !key.empty? && VALID_HEADER_KEY_PATTERN.match?(key)
32
+ end
33
+
34
+ # Parses an RPC method path into [service_url, method_name].
35
+ # e.g. '/echo.EchoServer/Echo' on 'foo.test.google.fr' gives
36
+ # ['https://foo.test.google.fr/echo.EchoServer', 'Echo'].
37
+ def self.parse_method_info(ssl_target, method)
38
+ return ["https://#{ssl_target}", nil] if method.nil? || method.empty?
39
+ last_slash = method.rindex('/')
40
+ service_path = last_slash&.positive? ? method[0, last_slash] : ''
41
+ [
42
+ "https://#{ssl_target}#{service_path}",
43
+ last_slash ? method[(last_slash + 1)..] : nil
44
+ ]
45
+ end
46
+
47
+ # Merges +creds_metadata+ into +metadata+, validating all keys.
48
+ # Raises GRPC::Unavailable on invalid type or illegal header keys.
49
+ def self.merge_creds_metadata!(creds_metadata, metadata)
50
+ return if creds_metadata.nil?
51
+ unless creds_metadata.is_a?(Hash)
52
+ fail GRPC::Unavailable, "Call credentials must return Hash or nil, got #{creds_metadata.class}"
53
+ end
54
+ creds_metadata.each do |key, value|
55
+ key_str = key.to_s
56
+ unless valid_header_key?(key_str)
57
+ fail GRPC::Unavailable, "Illegal metadata: '#{key_str}' is an invalid header key"
58
+ end
59
+ metadata[key_str] = value.is_a?(Array) ? value.map(&:to_s) : value.to_s
60
+ end
61
+ end
62
+
63
+ # Calls +credentials.get_metadata+ and merges the result into +metadata+.
64
+ # No-op when +credentials+ is nil or the channel is insecure.
65
+ # Raises GRPC::Unavailable on failure.
66
+ def self.apply(credentials, metadata, ssl_target, channel_creds, method = nil)
67
+ return unless credentials
68
+ return if channel_creds == :this_channel_is_insecure
69
+ service_url, method_name = parse_method_info(ssl_target, method)
70
+ context = { service_url: service_url, jwt_aud_uri: service_url, method_name: method_name }
71
+ begin
72
+ merge_creds_metadata!(credentials.get_metadata(context), metadata)
73
+ rescue GRPC::BadStatus
74
+ raise
75
+ rescue StandardError => e
76
+ fail GRPC::Unavailable, "Call credentials failed: #{e.message}"
77
+ end
78
+ end
79
+ end
80
+
81
+ # Prepended into ClientStub when the pure Ruby credentials path is active.
82
+ # Handles CompositeChannelCredentials splitting and applies credentials via
83
+ # metadata injection instead of the C extension set_credentials! path.
84
+ # @api private
85
+ module CompositeCredentialsHandler
86
+ def initialize(host, creds,
87
+ channel_override: nil,
88
+ timeout: nil,
89
+ propagate_mask: nil,
90
+ channel_args: {},
91
+ interceptors: [])
92
+ if creds.is_a?(Core::CompositeChannelCredentials)
93
+ pure_call_creds = creds.call_credentials
94
+ creds = creds.channel_credentials
95
+ super(host, creds,
96
+ channel_override: channel_override,
97
+ timeout: timeout,
98
+ propagate_mask: propagate_mask,
99
+ channel_args: channel_args,
100
+ interceptors: interceptors)
101
+ @call_creds = pure_call_creds
102
+ else
103
+ super
104
+ end
105
+ end
106
+
107
+ # credentials is intentionally overridden to nil in super to skip set_credentials!
108
+ def new_active_call(method, marshal, unmarshal,
109
+ deadline: nil,
110
+ parent: nil,
111
+ credentials: nil) # rubocop:disable Lint/UnusedMethodArgument
112
+ super(method, marshal, unmarshal,
113
+ deadline: deadline,
114
+ parent: parent,
115
+ credentials: nil)
116
+ end
117
+
118
+ private
119
+
120
+ def resolve_call_metadata(metadata, credentials, method)
121
+ resolved = CallCredentialsHelper.resolve(@call_creds, credentials)
122
+ metadata.dup.tap { |m| CallCredentialsHelper.apply(resolved, m, @host, @channel_creds, method) }
123
+ end
124
+ end
125
+ end
126
+ end
@@ -79,10 +79,9 @@ module GRPC
79
79
  # when present, this is the default timeout used for calls
80
80
  #
81
81
  # @param host [String] the host the stub connects to
82
- # @param creds [Core::ChannelCredentials|Symbol] the channel credentials, or
83
- # :this_channel_is_insecure, which explicitly indicates that the client
84
- # should be created with an insecure connection. Note: this argument is
85
- # ignored if the channel_override argument is provided.
82
+ # @param creds [Core::ChannelCredentials|Core::CallCredentials|Symbol]
83
+ # ChannelCredentials, CallCredentials (creates default SSL channel), or
84
+ # :this_channel_is_insecure. Ignored if channel_override is provided.
86
85
  # @param channel_override [Core::Channel] a pre-created channel
87
86
  # @param timeout [Number] the default timeout to use in requests
88
87
  # @param propagate_mask [Number] A bitwise combination of flags in
@@ -101,7 +100,18 @@ module GRPC
101
100
  propagate_mask: nil,
102
101
  channel_args: {},
103
102
  interceptors: [])
104
- @ch = ClientStub.setup_channel(channel_override, host, creds,
103
+ # Split credentials: CallCredentials alone create a default secure channel.
104
+ # CompositeChannelCredentials splitting is handled by Core::CompositeCredentialsHandler
105
+ # module when the pure Ruby toggle is enabled.
106
+ if creds.is_a?(Core::CallCredentials)
107
+ @call_creds = creds
108
+ @channel_creds = Core::ChannelCredentials.new
109
+ else
110
+ @call_creds = nil
111
+ @channel_creds = creds
112
+ end
113
+
114
+ @ch = ClientStub.setup_channel(channel_override, host, @channel_creds,
105
115
  channel_args.dup)
106
116
  alt_host = channel_args[Core::Channel::SSL_TARGET]
107
117
  @host = alt_host.nil? ? host : alt_host
@@ -154,30 +164,19 @@ module GRPC
154
164
  credentials: nil,
155
165
  metadata: {})
156
166
  c = new_active_call(method, marshal, unmarshal,
157
- deadline: deadline,
158
- parent: parent,
167
+ deadline: deadline, parent: parent,
159
168
  credentials: credentials)
160
- interception_context = @interceptors.build_context
169
+
161
170
  intercept_args = {
162
171
  method: method,
163
172
  request: req,
164
173
  call: c.interceptable,
165
174
  metadata: metadata
166
175
  }
167
- if return_op
168
- # return the operation view of the active_call; define #execute as a
169
- # new method for this instance that invokes #request_response.
170
- c.merge_metadata_to_send(metadata)
171
- op = c.operation
172
- op.define_singleton_method(:execute) do
173
- interception_context.intercept!(:request_response, intercept_args) do
174
- c.request_response(req, metadata: metadata)
175
- end
176
- end
177
- op
178
- else
179
- interception_context.intercept!(:request_response, intercept_args) do
180
- c.request_response(req, metadata: metadata)
176
+
177
+ handle_return_op(c, return_op, metadata, credentials, method) do
178
+ execute_with_interceptors(:request_response, intercept_args, credentials, method, c) do |resolved_md|
179
+ c.request_response(req, metadata: resolved_md)
181
180
  end
182
181
  end
183
182
  end
@@ -231,30 +230,19 @@ module GRPC
231
230
  credentials: nil,
232
231
  metadata: {})
233
232
  c = new_active_call(method, marshal, unmarshal,
234
- deadline: deadline,
235
- parent: parent,
233
+ deadline: deadline, parent: parent,
236
234
  credentials: credentials)
237
- interception_context = @interceptors.build_context
235
+
238
236
  intercept_args = {
239
237
  method: method,
240
238
  requests: requests,
241
239
  call: c.interceptable,
242
240
  metadata: metadata
243
241
  }
244
- if return_op
245
- # return the operation view of the active_call; define #execute as a
246
- # new method for this instance that invokes #client_streamer.
247
- c.merge_metadata_to_send(metadata)
248
- op = c.operation
249
- op.define_singleton_method(:execute) do
250
- interception_context.intercept!(:client_streamer, intercept_args) do
251
- c.client_streamer(requests)
252
- end
253
- end
254
- op
255
- else
256
- interception_context.intercept!(:client_streamer, intercept_args) do
257
- c.client_streamer(requests, metadata: metadata)
242
+
243
+ handle_return_op(c, return_op, metadata, credentials, method) do
244
+ execute_with_interceptors(:client_streamer, intercept_args, credentials, method, c) do |resolved_md|
245
+ c.client_streamer(requests, metadata: resolved_md)
258
246
  end
259
247
  end
260
248
  end
@@ -323,30 +311,19 @@ module GRPC
323
311
  metadata: {},
324
312
  &blk)
325
313
  c = new_active_call(method, marshal, unmarshal,
326
- deadline: deadline,
327
- parent: parent,
314
+ deadline: deadline, parent: parent,
328
315
  credentials: credentials)
329
- interception_context = @interceptors.build_context
316
+
330
317
  intercept_args = {
331
318
  method: method,
332
319
  request: req,
333
320
  call: c.interceptable,
334
321
  metadata: metadata
335
322
  }
336
- if return_op
337
- # return the operation view of the active_call; define #execute
338
- # as a new method for this instance that invokes #server_streamer
339
- c.merge_metadata_to_send(metadata)
340
- op = c.operation
341
- op.define_singleton_method(:execute) do
342
- interception_context.intercept!(:server_streamer, intercept_args) do
343
- c.server_streamer(req, &blk)
344
- end
345
- end
346
- op
347
- else
348
- interception_context.intercept!(:server_streamer, intercept_args) do
349
- c.server_streamer(req, metadata: metadata, &blk)
323
+
324
+ handle_return_op(c, return_op, metadata, credentials, method) do
325
+ execute_with_interceptors(:server_streamer, intercept_args, credentials, method, c) do |resolved_md|
326
+ c.server_streamer(req, metadata: resolved_md, &blk)
350
327
  end
351
328
  end
352
329
  end
@@ -445,30 +422,19 @@ module GRPC
445
422
  metadata: {},
446
423
  &blk)
447
424
  c = new_active_call(method, marshal, unmarshal,
448
- deadline: deadline,
449
- parent: parent,
425
+ deadline: deadline, parent: parent,
450
426
  credentials: credentials)
451
- interception_context = @interceptors.build_context
427
+
452
428
  intercept_args = {
453
429
  method: method,
454
430
  requests: requests,
455
431
  call: c.interceptable,
456
432
  metadata: metadata
457
433
  }
458
- if return_op
459
- # return the operation view of the active_call; define #execute
460
- # as a new method for this instance that invokes #bidi_streamer
461
- c.merge_metadata_to_send(metadata)
462
- op = c.operation
463
- op.define_singleton_method(:execute) do
464
- interception_context.intercept!(:bidi_streamer, intercept_args) do
465
- c.bidi_streamer(requests, &blk)
466
- end
467
- end
468
- op
469
- else
470
- interception_context.intercept!(:bidi_streamer, intercept_args) do
471
- c.bidi_streamer(requests, metadata: metadata, &blk)
434
+
435
+ handle_return_op(c, return_op, metadata, credentials, method) do
436
+ execute_with_interceptors(:bidi_streamer, intercept_args, credentials, method, c) do |resolved_md|
437
+ c.bidi_streamer(requests, metadata: resolved_md, &blk)
472
438
  end
473
439
  end
474
440
  end
@@ -480,6 +446,7 @@ module GRPC
480
446
  # @param method [string] the method being called.
481
447
  # @param marshal [Function] f(obj)->string that marshals requests
482
448
  # @param unmarshal [Function] f(string)->obj that unmarshals responses
449
+ # @param deadline [Time] (optional) the time the request should complete
483
450
  # @param parent [Grpc::Call] a parent call, available when calls are
484
451
  # made from server
485
452
  # @param credentials [Core::CallCredentials] credentials to use when making
@@ -499,5 +466,51 @@ module GRPC
499
466
  ActiveCall.new(call, marshal, unmarshal, deadline,
500
467
  started: false)
501
468
  end
469
+
470
+ # Runs the interceptor chain and resolves metadata/credentials exactly once
471
+ # at the end of the chain.
472
+ def execute_with_interceptors(rpc_type, intercept_args, credentials, method, call)
473
+ interception_context = @interceptors.build_context
474
+ call_started = false
475
+ already_finished = !call.status.nil?
476
+ begin
477
+ interception_context.intercept!(rpc_type, intercept_args) do
478
+ resolved_md = resolve_call_metadata(intercept_args[:metadata], credentials, method)
479
+ call_started = true
480
+ yield(resolved_md)
481
+ end
482
+ rescue StandardError => e
483
+ call.op_is_done unless call_started || already_finished
484
+ raise e
485
+ end
486
+ end
487
+
488
+ # Handles return_op lifecycle branching
489
+ def handle_return_op(call, return_op, metadata, credentials, method, &block)
490
+ if return_op
491
+ stub = self
492
+ op = call.operation
493
+ op.define_singleton_method(:execute) do
494
+ block.call
495
+ end
496
+ op.define_singleton_method(:start_call) do |op_metadata = {}|
497
+ resolved_md = stub.send(:resolve_call_metadata, metadata, credentials, method)
498
+ call.merge_metadata_to_send(resolved_md.merge(op_metadata))
499
+ call.send_initial_metadata
500
+ rescue StandardError => e
501
+ call.op_is_done unless call.metadata_sent
502
+ raise e
503
+ end
504
+ op
505
+ else
506
+ yield
507
+ end
508
+ end
509
+
510
+ def resolve_call_metadata(metadata, _credentials, _method)
511
+ metadata
512
+ end
513
+
514
+ private :resolve_call_metadata, :execute_with_interceptors, :handle_return_op
502
515
  end
503
516
  end
@@ -169,7 +169,7 @@ module GRPC
169
169
  def intercept!(type, args = {})
170
170
  return yield if @interceptors.none?
171
171
 
172
- i = @interceptors.pop
172
+ i = @interceptors.shift
173
173
  return yield unless i
174
174
 
175
175
  i.send(type, **args) do
@@ -14,5 +14,5 @@
14
14
 
15
15
  # GRPC contains the General RPC module.
16
16
  module GRPC
17
- VERSION = '1.81.0.pre1'
17
+ VERSION = '1.82.0.pre1'
18
18
  end
data/src/ruby/lib/grpc.rb CHANGED
@@ -22,6 +22,25 @@ require_relative 'grpc/notifier'
22
22
  require_relative 'grpc/version'
23
23
  require_relative 'grpc/core/status_codes'
24
24
  require_relative 'grpc/core/time_consts'
25
+
26
+ # Feature toggle: set GRPC_EXPERIMENTS=pure_ruby_call_credentials to enable.
27
+ # Default (false): uses C extension path for backward compatibility.
28
+ # NOTE: must be set before `require 'grpc'` — evaluated once at load time.
29
+ module GRPC
30
+ PURE_RUBY_CALL_CREDENTIALS_ENABLED =
31
+ ENV.fetch('GRPC_EXPERIMENTS', '')
32
+ .split(',')
33
+ .map(&:strip)
34
+ .include?('pure_ruby_call_credentials')
35
+ .freeze
36
+ end
37
+
38
+ if GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
39
+ require_relative 'grpc/core/call_credentials'
40
+ require_relative 'grpc/core/channel_credentials'
41
+ require_relative 'grpc/core/credentials_helper'
42
+ end
43
+
25
44
  require_relative 'grpc/generic/active_call'
26
45
  require_relative 'grpc/generic/client_stub'
27
46
  require_relative 'grpc/generic/service'
@@ -35,3 +54,9 @@ begin
35
54
  ensure
36
55
  file.close
37
56
  end
57
+
58
+ # Prepend CompositeCredentialsHandler if pure Ruby credentials are enabled.
59
+ # This must happen after all credential classes are loaded.
60
+ if GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
61
+ GRPC::ClientStub.class_eval { prepend GRPC::Core::CompositeCredentialsHandler }
62
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Copyright 2015 gRPC authors.
2
4
  #
3
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,20 +18,35 @@ require 'spec_helper'
16
18
 
17
19
  describe GRPC::Core::CallCredentials do
18
20
  CallCredentials = GRPC::Core::CallCredentials
19
-
20
21
  let(:auth_proc) { proc { { 'plugin_key' => 'plugin_value' } } }
22
+ let(:auth_proc2) { proc { { 'plugin_key2' => 'plugin_value2' } } }
21
23
 
22
24
  describe '#new' do
23
25
  it 'can successfully create a CallCredentials from a proc' do
24
26
  expect { CallCredentials.new(auth_proc) }.not_to raise_error
25
27
  end
28
+
29
+ context 'pure Ruby path only' do
30
+ before do
31
+ skip 'pure Ruby path only: set GRPC_EXPERIMENTS=pure_ruby_call_credentials' \
32
+ unless GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
33
+ end
34
+
35
+ it 'can successfully create a CallCredentials from a block' do
36
+ expect { CallCredentials.new { { 'foo' => 'bar' } } }.not_to raise_error
37
+ end
38
+ end
39
+
40
+ it 'fails if initialized without a proc or block' do
41
+ expect { CallCredentials.new('not a proc') }.to raise_error(TypeError)
42
+ end
26
43
  end
27
44
 
28
45
  describe '#compose' do
29
46
  it 'can compose with another CallCredentials' do
30
47
  creds1 = CallCredentials.new(auth_proc)
31
48
  creds2 = CallCredentials.new(auth_proc)
32
- expect { creds1.compose creds2 }.not_to raise_error
49
+ expect { creds1.compose(creds2) }.not_to raise_error
33
50
  end
34
51
 
35
52
  it 'can compose with multiple CallCredentials' do
@@ -38,5 +55,38 @@ describe GRPC::Core::CallCredentials do
38
55
  creds3 = CallCredentials.new(auth_proc)
39
56
  expect { creds1.compose(creds2, creds3) }.not_to raise_error
40
57
  end
58
+
59
+ context 'pure Ruby path only' do
60
+ before do
61
+ skip 'pure Ruby path only: set GRPC_EXPERIMENTS=pure_ruby_call_credentials' \
62
+ unless GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
63
+ end
64
+
65
+ it 'returns a CompositeCallCredentials with merged metadata' do
66
+ creds1 = CallCredentials.new(auth_proc)
67
+ creds2 = CallCredentials.new(auth_proc2)
68
+ composite = creds1.compose(creds2)
69
+ expect(composite).to be_a(GRPC::Core::CompositeCallCredentials)
70
+ expect(composite.get_metadata(nil))
71
+ .to eq({ 'plugin_key' => 'plugin_value', 'plugin_key2' => 'plugin_value2' })
72
+ end
73
+
74
+ it 'returns a CompositeCallCredentials when composing multiple' do
75
+ creds1 = CallCredentials.new(auth_proc)
76
+ creds2 = CallCredentials.new(auth_proc2)
77
+ creds3 = CallCredentials.new(proc { { 'plugin_key3' => 'plugin_value3' } })
78
+ composite = creds1.compose(creds2, creds3)
79
+ expect(composite).to be_a(GRPC::Core::CompositeCallCredentials)
80
+ expect(composite.get_metadata(nil))
81
+ .to eq({ 'plugin_key' => 'plugin_value',
82
+ 'plugin_key2' => 'plugin_value2',
83
+ 'plugin_key3' => 'plugin_value3' })
84
+ end
85
+
86
+ it 'fails if composed with non-CallCredentials' do
87
+ creds1 = CallCredentials.new(auth_proc)
88
+ expect { creds1.compose('not a cred') }.to raise_error(TypeError)
89
+ end
90
+ end
41
91
  end
42
92
  end
@@ -0,0 +1,80 @@
1
+ # Copyright 2015 gRPC authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'spec_helper'
16
+ require 'grpc/core/credentials_helper'
17
+
18
+ describe GRPC::Core::CallCredentialsHelper do
19
+ before(:each) do
20
+ skip 'pure Ruby path only: set GRPC_EXPERIMENTS=pure_ruby_call_credentials' \
21
+ unless GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
22
+ end
23
+
24
+ describe '.resolve' do
25
+ it 'returns nil if both are nil' do
26
+ expect(GRPC::Core::CallCredentialsHelper.resolve(nil, nil)).to eq(nil)
27
+ end
28
+
29
+ it 'returns channel creds if call creds are nil' do
30
+ creds = double('creds')
31
+ expect(GRPC::Core::CallCredentialsHelper.resolve(creds, nil)).to eq(creds)
32
+ end
33
+
34
+ it 'returns call creds if channel creds are nil' do
35
+ creds = double('creds')
36
+ expect(GRPC::Core::CallCredentialsHelper.resolve(nil, creds)).to eq(creds)
37
+ end
38
+
39
+ it 'composes if both are present' do
40
+ creds1 = double('creds1')
41
+ creds2 = double('creds2')
42
+ expect(creds1).to receive(:compose).with(creds2).and_return('composite')
43
+ expect(GRPC::Core::CallCredentialsHelper.resolve(creds1, creds2)).to eq('composite')
44
+ end
45
+ end
46
+
47
+ describe '.apply' do
48
+ let(:metadata) { {} }
49
+ let(:creds) { double('creds') }
50
+
51
+ it 'does nothing if creds are nil' do
52
+ GRPC::Core::CallCredentialsHelper.apply(nil, metadata, 'host', 'channel_creds')
53
+ expect(metadata).to eq({})
54
+ end
55
+
56
+ it 'does nothing if channel is insecure' do
57
+ GRPC::Core::CallCredentialsHelper.apply(creds, metadata, 'host', :this_channel_is_insecure)
58
+ expect(metadata).to eq({})
59
+ end
60
+
61
+ it 'applies metadata if valid' do
62
+ expect(creds).to receive(:get_metadata).and_return({ 'foo' => 'bar' })
63
+ GRPC::Core::CallCredentialsHelper.apply(creds, metadata, 'host', 'secure_channel_creds')
64
+ expect(metadata).to eq({ 'foo' => 'bar' })
65
+ end
66
+
67
+ it 'handles exceptions' do
68
+ expect(creds).to receive(:get_metadata).and_raise('error')
69
+ expect do
70
+ GRPC::Core::CallCredentialsHelper.apply(creds, metadata, 'host', 'secure_channel_creds')
71
+ end.to raise_error(GRPC::BadStatus)
72
+ end
73
+
74
+ it 'converts keys and values to strings' do
75
+ expect(creds).to receive(:get_metadata).and_return({ foo: :bar })
76
+ GRPC::Core::CallCredentialsHelper.apply(creds, metadata, 'host', 'secure_channel_creds')
77
+ expect(metadata).to eq({ 'foo' => 'bar' })
78
+ end
79
+ end
80
+ end
@@ -157,6 +157,29 @@ describe 'ClientStub' do # rubocop:disable Metrics/BlockLength
157
157
  end
158
158
  expect(&blk).to_not raise_error
159
159
  end
160
+
161
+ it 'creates secure channel when only CallCredentials provided' do
162
+ call_creds = GRPC::Core::CallCredentials.new(proc { {} })
163
+ stub = GRPC::ClientStub.new(fake_host, call_creds)
164
+ # Verify the internal channel credentials are SSL (not insecure)
165
+ expect(stub.instance_variable_get(:@channel_creds)).to be_a(GRPC::Core::ChannelCredentials)
166
+ expect(stub.instance_variable_get(:@call_creds)).to eq(call_creds)
167
+ end
168
+
169
+ context 'with CompositeChannelCredentials (pure Ruby path)' do
170
+ it 'splits CompositeChannelCredentials into channel + call creds' do
171
+ skip 'CompositeChannelCredentials requires pure Ruby toggle ON' \
172
+ unless GRPC::PURE_RUBY_CALL_CREDENTIALS_ENABLED
173
+ require 'grpc/core/call_credentials'
174
+ require 'grpc/core/channel_credentials'
175
+ chan_creds = GRPC::Core::ChannelCredentials.new
176
+ call_creds = GRPC::Core::CallCredentials.new(proc { {} })
177
+ composite = chan_creds.compose(call_creds)
178
+ stub = GRPC::ClientStub.new(fake_host, composite)
179
+ expect(stub.instance_variable_get(:@channel_creds)).to eq(chan_creds)
180
+ expect(stub.instance_variable_get(:@call_creds)).to eq(call_creds)
181
+ end
182
+ end
160
183
  end
161
184
 
162
185
  describe '#request_response', request_response: true do
@@ -301,7 +324,7 @@ describe 'ClientStub' do # rubocop:disable Metrics/BlockLength
301
324
  describe 'via a call operation' do
302
325
  after(:each) do
303
326
  # make sure op.wait doesn't freeze, even if there's a bad status
304
- @op.wait
327
+ @op&.wait
305
328
  end
306
329
  def get_response(stub, run_start_call_first: false, credentials: nil)
307
330
  @op = stub.request_response(@method, @sent_msg, noop, noop,
@@ -403,7 +426,7 @@ describe 'ClientStub' do # rubocop:disable Metrics/BlockLength
403
426
  describe 'via a call operation' do
404
427
  after(:each) do
405
428
  # make sure op.wait doesn't freeze, even if there's a bad status
406
- @op.wait
429
+ @op&.wait
407
430
  end
408
431
  def get_response(stub, run_start_call_first: false)
409
432
  @op = stub.client_streamer(@method, @sent_msgs, noop, noop,
@@ -522,7 +545,7 @@ describe 'ClientStub' do # rubocop:disable Metrics/BlockLength
522
545
 
523
546
  describe 'via a call operation' do
524
547
  after(:each) do
525
- @op.wait # make sure wait doesn't freeze
548
+ @op&.wait
526
549
  end
527
550
  def get_responses(stub, run_start_call_first: false, unmarshal: noop)
528
551
  @op = stub.server_streamer(@method, @sent_msg, noop, unmarshal,
@@ -841,7 +864,7 @@ describe 'ClientStub' do # rubocop:disable Metrics/BlockLength
841
864
 
842
865
  describe 'via a call operation' do
843
866
  after(:each) do
844
- @op.wait # make sure wait doesn't freeze
867
+ @op&.wait
845
868
  end
846
869
  def get_responses(stub, run_start_call_first: false, deadline: nil,
847
870
  marshal_proc: noop)
@@ -84,7 +84,7 @@ describe 'Server Interceptors' do
84
84
  run_services_on_server(@server, services: [service]) do
85
85
  stub = build_insecure_stub(EchoStub)
86
86
  expect_any_instance_of(GRPC::ActiveCall).to(
87
- receive(:client_streamer).with(requests)
87
+ receive(:client_streamer).with(requests, metadata: client_metadata)
88
88
  .once.and_call_original
89
89
  )
90
90
  op = stub.a_client_streaming_rpc(requests, client_call_opts)
@@ -122,7 +122,7 @@ describe 'Server Interceptors' do
122
122
  run_services_on_server(@server, services: [service]) do
123
123
  stub = build_insecure_stub(EchoStub)
124
124
  expect_any_instance_of(GRPC::ActiveCall).to(
125
- receive(:server_streamer).with(request)
125
+ receive(:server_streamer).with(request, metadata: client_metadata)
126
126
  .once.and_call_original
127
127
  )
128
128
  op = stub.a_server_streaming_rpc(request, client_call_opts)
@@ -162,7 +162,7 @@ describe 'Server Interceptors' do
162
162
  run_services_on_server(@server, services: [service]) do
163
163
  stub = build_insecure_stub(EchoStub)
164
164
  expect_any_instance_of(GRPC::ActiveCall).to(
165
- receive(:bidi_streamer).with(requests)
165
+ receive(:bidi_streamer).with(requests, metadata: client_metadata)
166
166
  .once.and_call_original
167
167
  )
168
168
  op = stub.a_bidi_rpc(requests, client_call_opts)
@@ -203,6 +203,20 @@ describe 'Server Interceptors' do
203
203
  expect(stub.an_rpc(request)).to be_a(EchoMsg)
204
204
  end
205
205
  end
206
+
207
+ it 'should be invoked in FIFO order', server: true do
208
+ expect(interceptor).to receive(:request_response).ordered
209
+ .once.and_call_original
210
+ expect(interceptor2).to receive(:request_response).ordered
211
+ .once.and_call_original
212
+ expect(interceptor3).to receive(:request_response).ordered
213
+ .once.and_call_original
214
+
215
+ run_services_on_server(@server, services: [service]) do
216
+ stub = build_insecure_stub(EchoStub)
217
+ expect(stub.an_rpc(request)).to be_a(EchoMsg)
218
+ end
219
+ end
206
220
  end
207
221
 
208
222
  context 'when an interceptor is not added' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.81.0.pre1
4
+ version: 1.82.0.pre1
5
5
  platform: aarch64-linux-gnu
6
6
  authors:
7
7
  - gRPC Authors
@@ -271,6 +271,9 @@ files:
271
271
  - src/ruby/lib/grpc/3.3/grpc_c.so
272
272
  - src/ruby/lib/grpc/3.4/grpc_c.so
273
273
  - src/ruby/lib/grpc/4.0/grpc_c.so
274
+ - src/ruby/lib/grpc/core/call_credentials.rb
275
+ - src/ruby/lib/grpc/core/channel_credentials.rb
276
+ - src/ruby/lib/grpc/core/credentials_helper.rb
274
277
  - src/ruby/lib/grpc/core/status_codes.rb
275
278
  - src/ruby/lib/grpc/core/time_consts.rb
276
279
  - src/ruby/lib/grpc/errors.rb
@@ -312,6 +315,7 @@ files:
312
315
  - src/ruby/spec/client_server_spec.rb
313
316
  - src/ruby/spec/compression_options_spec.rb
314
317
  - src/ruby/spec/core_spec.rb
318
+ - src/ruby/spec/credentials_helper_spec.rb
315
319
  - src/ruby/spec/debug_message_spec.rb
316
320
  - src/ruby/spec/error_sanity_spec.rb
317
321
  - src/ruby/spec/errors_spec.rb
@@ -371,7 +375,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
371
375
  - !ruby/object:Gem::Version
372
376
  version: 3.3.22
373
377
  requirements: []
374
- rubygems_version: 4.0.11
378
+ rubygems_version: 4.0.14
375
379
  specification_version: 4
376
380
  summary: GRPC system in Ruby
377
381
  test_files:
@@ -387,6 +391,7 @@ test_files:
387
391
  - src/ruby/spec/client_server_spec.rb
388
392
  - src/ruby/spec/compression_options_spec.rb
389
393
  - src/ruby/spec/core_spec.rb
394
+ - src/ruby/spec/credentials_helper_spec.rb
390
395
  - src/ruby/spec/debug_message_spec.rb
391
396
  - src/ruby/spec/error_sanity_spec.rb
392
397
  - src/ruby/spec/errors_spec.rb