async-grpc 0.6.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ea5b102766838b1d027771084f9696707e1cbf5dac9fdee9004daf4724b5a318
4
- data.tar.gz: 105255db3b068be3a28f0234e1ce77600d5780a7220a15e848eb6ad9e855ddb2
3
+ metadata.gz: 2b2db42e03f75834e2259977d953d8c1688949e3c45c3ca972f9a9a0c6ca23b9
4
+ data.tar.gz: 1f7ce2103876bcb8fe0d716595668bee4904e1d2f35d0cb5ac86b246f84fd2e0
5
5
  SHA512:
6
- metadata.gz: 7ff58e16e452b3c37ce7c4a8ded6ed7381580f1d2a8b046d644b3449b1b5d0df791f3463acb6fcfd8168d8a9c53cb094cc40988c82afded2478d9b58a5059499
7
- data.tar.gz: 7b9e4e143f778688971dbee371c1e6ebfc049ad002dd942d570e19570880336c18a36a30e681692e267d561fda48f0b1060813efe951625b76e2a1fc51dd1f49
6
+ metadata.gz: 61cfba13b45b83a390b41e2ede94ad6bf81286f3273630d8bc8336103e40d888fbb0d0c74f1f54c04c44d37a19fa493fcb0cf29e096388d855ff3b7fb626d187
7
+ data.tar.gz: 46dc51138f257f1768ddef3d81b42d1c040a53812a9970bd8bb7fee60a94a6e0d16f210069faa4c6b484f58994070f1e9ad04459685f57932fa89db40eb5bf8c
checksums.yaml.gz.sig CHANGED
Binary file
@@ -16,7 +16,7 @@ require "protocol/grpc/body/writable_body"
16
16
  require "protocol/grpc/metadata"
17
17
  require "protocol/grpc/error"
18
18
  require_relative "stub"
19
- require_relative "remote_error"
19
+ require_relative "error"
20
20
 
21
21
  module Async
22
22
  module GRPC
@@ -113,11 +113,12 @@ module Async
113
113
  # @parameter metadata [Hash] Custom metadata headers
114
114
  # @parameter timeout [Numeric | Nil] Optional timeout in seconds
115
115
  # @parameter encoding [String | Nil] Optional compression encoding
116
+ # @parameter initial [Object | Array | Nil] Optional initial message(s) for bidirectional streaming
116
117
  # @yields {|input, output| ...} Block for streaming calls
117
118
  # @returns [Object | Protocol::GRPC::Body::ReadableBody] Response message or readable body for streaming
118
119
  # @raises [ArgumentError] If method is unknown or streaming type is invalid
119
120
  # @raises [Protocol::GRPC::Error] If the gRPC call fails
120
- def invoke(service, method, request = nil, metadata: {}, timeout: nil, encoding: nil, &block)
121
+ def invoke(service, method, request = nil, metadata: {}, timeout: nil, encoding: nil, initial: nil, &block)
121
122
  rpc = service.class.lookup_rpc(method)
122
123
  raise ArgumentError, "Unknown method: #{method}" unless rpc
123
124
 
@@ -141,7 +142,7 @@ module Async
141
142
  when :client_streaming
142
143
  client_streaming_call(path, headers, request_class, response_class, encoding, &block)
143
144
  when :bidirectional
144
- bidirectional_call(path, headers, request_class, response_class, encoding, &block)
145
+ bidirectional_call(path, headers, request_class, response_class, encoding, initial: initial, &block)
145
146
  else
146
147
  raise ArgumentError, "Unknown streaming type: #{streaming}"
147
148
  end
@@ -273,14 +274,16 @@ module Async
273
274
  # @parameter request_class [Class] Request message class
274
275
  # @parameter response_class [Class] Response message class
275
276
  # @parameter encoding [String | Nil] Compression encoding
277
+ # @parameter initial [Object | Array | Nil] Optional initial message(s) to write before waiting for the response
276
278
  # @yields {|input, output| ...} Block to handle bidirectional streaming
277
279
  # @returns [Protocol::GRPC::Body::ReadableBody] Readable body for streaming messages
278
280
  # @raises [Protocol::GRPC::Error] If the gRPC call fails
279
- def bidirectional_call(path, headers, request_class, response_class, encoding, &block)
281
+ def bidirectional_call(path, headers, request_class, response_class, encoding, initial: nil, &block)
280
282
  body = Protocol::GRPC::Body::WritableBody.new(
281
283
  encoding: encoding,
282
284
  message_class: request_class
283
285
  )
286
+ Array(initial).each{|message| body.write(message)}
284
287
 
285
288
  http_request = Protocol::HTTP::Request["POST", path, headers, body]
286
289
  response = call(http_request)
@@ -4,7 +4,8 @@
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require "async"
7
- require "async/deadline"
7
+
8
+ require_relative "error"
8
9
 
9
10
  require "protocol/grpc/middleware"
10
11
  require "protocol/grpc/methods"
@@ -44,6 +45,25 @@ module Async
44
45
 
45
46
  protected
46
47
 
48
+ # Invoke a service handler, providing a protected extension point for middleware and instrumentation around service execution.
49
+ #
50
+ # Override this method to observe service outcomes, establish request context, or wrap handlers with application middleware. Overrides should call `super` to invoke the handler and preserve the dispatcher's stream cleanup, trailer handling, and successful status assignment.
51
+ #
52
+ # This hook only surrounds service handler execution. Errors raised earlier while routing or preparing the request do not pass through it. A call deadline that expires during service execution raises {DeadlineExceededError} through this hook before the dispatcher converts it to a `DEADLINE_EXCEEDED` response.
53
+ #
54
+ # @parameter service [Async::GRPC::Service] The service containing the handler.
55
+ # @parameter handler_method [Symbol] The service method to invoke.
56
+ # @parameter input [Protocol::GRPC::Body::ReadableBody] The decoded request stream.
57
+ # @parameter output [Protocol::GRPC::Body::WritableBody] The response stream.
58
+ # @parameter call [Protocol::GRPC::Call] The call context.
59
+ # @returns [Object | Nil] An internal dispatch result with no stable application-facing contract.
60
+ # @example Wrap service execution with application middleware
61
+ # def invoke_service(service, handler_method, input, output, call)
62
+ # context = RequestContext.new(call.request)
63
+ # middleware.call(context) do
64
+ # super
65
+ # end
66
+ # end
47
67
  def invoke_service(service, handler_method, input, output, call)
48
68
  begin
49
69
  service.send(handler_method, input, output, call)
@@ -69,15 +89,15 @@ module Async
69
89
  end
70
90
  end
71
91
 
72
- def dispatch_to_service(service, handler_method, input, output, call, deadline, parent: Async::Task.current)
73
- if deadline
74
- parent.with_timeout(deadline.remaining) do
92
+ def dispatch_to_service(service, handler_method, input, output, call, parent: Async::Task.current)
93
+ if deadline = call.deadline
94
+ parent.with_timeout(deadline.remaining, DeadlineExceededError) do
75
95
  invoke_service(service, handler_method, input, output, call)
76
96
  end
77
97
  else
78
98
  invoke_service(service, handler_method, input, output, call)
79
99
  end
80
- rescue Async::TimeoutError => error
100
+ rescue DeadlineExceededError => error
81
101
  # Close input and output streams:
82
102
  input.close
83
103
  output.close_write unless output.closed?
@@ -135,22 +155,16 @@ module Async
135
155
  # Create response object:
136
156
  response = Protocol::HTTP::Response[200, response_headers, output]
137
157
 
138
- # Parse deadline from timeout header:
139
- timeout = Protocol::GRPC::Methods.parse_timeout(request.headers["grpc-timeout"])
140
- deadline = if timeout
141
- Async::Deadline.start(timeout)
142
- end
143
-
144
- # Create call context with request and response:
145
- call = Protocol::GRPC::Call.new(request, response, deadline: deadline)
158
+ # Create call context with request, response and deadline:
159
+ call = Protocol::GRPC::Call.for(request, response)
146
160
 
147
161
  if rpc_descriptor.streaming?
148
162
  Async do |task|
149
- dispatch_to_service(service, handler_method, input, output, call, deadline, parent: task)
163
+ dispatch_to_service(service, handler_method, input, output, call, parent: task)
150
164
  end
151
165
  else
152
166
  # Unary call:
153
- dispatch_to_service(service, handler_method, input, output, call, deadline)
167
+ dispatch_to_service(service, handler_method, input, output, call)
154
168
  end
155
169
 
156
170
  return response
@@ -1,14 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  module Async
7
7
  module GRPC
8
+ # Represents a generic async gRPC error.
9
+ class Error < StandardError
10
+ end
11
+
12
+ # Raised when a gRPC call exceeds its deadline.
13
+ class DeadlineExceededError < Error
14
+ end
15
+
8
16
  # Represents an error that originated from a remote gRPC server.
9
17
  # Used as the `cause` of {Protocol::GRPC::Error} when the client receives a non-OK status.
10
18
  # The message and optional backtrace are extracted from response metadata.
11
- class RemoteError < StandardError
19
+ class RemoteError < Error
12
20
  # Create a RemoteError from server response metadata.
13
21
  # @parameter message [String | Nil] The error message from `grpc-message` header.
14
22
  # @parameter metadata [Hash] Response metadata (extracted from gRPC headers). If it contains a `"backtrace"` key (array of strings), it is set on the error and removed from the hash.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2025, by Samuel Williams.
4
+ # Copyright, 2025-2026, by Samuel Williams.
5
5
 
6
6
  require "protocol/grpc/interface"
7
7
 
@@ -36,7 +36,7 @@ module Async
36
36
  # Uses snake_case method names (Ruby convention).
37
37
  # @parameter method_name [Symbol] The method name to call (snake_case)
38
38
  # @parameter args [Array] Positional arguments (first is the request message)
39
- # @parameter options [Hash] Keyword arguments (metadata, timeout, encoding)
39
+ # @parameter options [Hash] Keyword arguments (metadata, timeout, encoding, initial)
40
40
  # @yields {|input, output| ...} Block for streaming calls
41
41
  # @returns [Object | Protocol::GRPC::Body::ReadableBody] Response message or readable body
42
42
  # @raises [NoMethodError] If the method is not found
@@ -47,13 +47,14 @@ module Async
47
47
  # Extract request from args (first positional argument):
48
48
  request = args.first
49
49
 
50
- # Extract metadata, timeout, encoding from options:
50
+ # Extract metadata, timeout, encoding and initial messages from options:
51
51
  metadata = options.delete(:metadata) || {}
52
52
  timeout = options.delete(:timeout)
53
53
  encoding = options.delete(:encoding)
54
+ initial = options.delete(:initial)
54
55
 
55
56
  # Delegate to client.invoke with PascalCase method name (for interface lookup):
56
- @client.invoke(@interface, interface_method_name, request, metadata: metadata, timeout: timeout, encoding: encoding, &block)
57
+ @client.invoke(@interface, interface_method_name, request, metadata: metadata, timeout: timeout, encoding: encoding, initial: initial, &block)
57
58
  else
58
59
  super
59
60
  end
@@ -7,7 +7,7 @@
7
7
  module Async
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.6.0"
10
+ VERSION = "0.8.0"
11
11
  end
12
12
  end
13
13
 
data/lib/async/grpc.rb CHANGED
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require_relative "grpc/version"
7
+ require_relative "grpc/error"
7
8
  require_relative "grpc/client"
8
9
  require_relative "grpc/service"
9
10
  require_relative "grpc/stub"
data/readme.md CHANGED
@@ -24,6 +24,10 @@ Please see the [project documentation](https://socketry.github.io/async-grpc/) f
24
24
 
25
25
  Please see the [project releases](https://socketry.github.io/async-grpc/releases/index) for all releases.
26
26
 
27
+ ### v0.8.0
28
+
29
+ - Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
30
+
27
31
  ### v0.6.0
28
32
 
29
33
  - Ensure `grpc-status` (and related metadata) is sent as a trailer, if data frames are written.
@@ -72,6 +76,22 @@ We welcome contributions to this project.
72
76
  4. Push to the branch (`git push origin my-new-feature`).
73
77
  5. Create new Pull Request.
74
78
 
79
+ ### Running Tests
80
+
81
+ To run the test suite:
82
+
83
+ ``` shell
84
+ bundle exec sus
85
+ ```
86
+
87
+ ### Making Releases
88
+
89
+ To make a new release:
90
+
91
+ ``` shell
92
+ bundle exec bake gem:release:patch # or minor or major
93
+ ```
94
+
75
95
  ### Developer Certificate of Origin
76
96
 
77
97
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.8.0
4
+
5
+ - Added `Async::GRPC::Error` as the base class for async gRPC errors, including `RemoteError` and the new `DeadlineExceededError` used to distinguish call deadline expiry from unrelated `Async::TimeoutError` failures.
6
+
3
7
  ## v0.6.0
4
8
 
5
9
  - Ensure `grpc-status` (and related metadata) is sent as a trailer, if data frames are written.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -38,6 +38,20 @@ cert_chain:
38
38
  -----END CERTIFICATE-----
39
39
  date: 1980-01-02 00:00:00.000000000 Z
40
40
  dependencies:
41
+ - !ruby/object:Gem::Dependency
42
+ name: async
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 2.38.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 2.38.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: async-http
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -53,33 +67,33 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
- name: protocol-http
70
+ name: protocol-grpc
57
71
  requirement: !ruby/object:Gem::Requirement
58
72
  requirements:
59
73
  - - "~>"
60
74
  - !ruby/object:Gem::Version
61
- version: '0.60'
75
+ version: 0.12.0
62
76
  type: :runtime
63
77
  prerelease: false
64
78
  version_requirements: !ruby/object:Gem::Requirement
65
79
  requirements:
66
80
  - - "~>"
67
81
  - !ruby/object:Gem::Version
68
- version: '0.60'
82
+ version: 0.12.0
69
83
  - !ruby/object:Gem::Dependency
70
- name: protocol-grpc
84
+ name: protocol-http
71
85
  requirement: !ruby/object:Gem::Requirement
72
86
  requirements:
73
87
  - - "~>"
74
88
  - !ruby/object:Gem::Version
75
- version: 0.11.0
89
+ version: '0.60'
76
90
  type: :runtime
77
91
  prerelease: false
78
92
  version_requirements: !ruby/object:Gem::Requirement
79
93
  requirements:
80
94
  - - "~>"
81
95
  - !ruby/object:Gem::Version
82
- version: 0.11.0
96
+ version: '0.60'
83
97
  executables: []
84
98
  extensions: []
85
99
  extra_rdoc_files: []
@@ -90,7 +104,7 @@ files:
90
104
  - lib/async/grpc.rb
91
105
  - lib/async/grpc/client.rb
92
106
  - lib/async/grpc/dispatcher.rb
93
- - lib/async/grpc/remote_error.rb
107
+ - lib/async/grpc/error.rb
94
108
  - lib/async/grpc/service.rb
95
109
  - lib/async/grpc/stub.rb
96
110
  - lib/async/grpc/version.rb
@@ -118,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
118
132
  - !ruby/object:Gem::Version
119
133
  version: '0'
120
134
  requirements: []
121
- rubygems_version: 4.0.3
135
+ rubygems_version: 4.0.10
122
136
  specification_version: 4
123
137
  summary: Client and server implementation for gRPC using Async.
124
138
  test_files: []
metadata.gz.sig CHANGED
Binary file