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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/async/grpc/client.rb +7 -4
- data/lib/async/grpc/dispatcher.rb +29 -15
- data/lib/async/grpc/{remote_error.rb → error.rb} +10 -2
- data/lib/async/grpc/stub.rb +5 -4
- data/lib/async/grpc/version.rb +1 -1
- data/lib/async/grpc.rb +1 -0
- data/readme.md +20 -0
- data/releases.md +4 -0
- data.tar.gz.sig +0 -0
- metadata +23 -9
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2b2db42e03f75834e2259977d953d8c1688949e3c45c3ca972f9a9a0c6ca23b9
|
|
4
|
+
data.tar.gz: 1f7ce2103876bcb8fe0d716595668bee4904e1d2f35d0cb5ac86b246f84fd2e0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 61cfba13b45b83a390b41e2ede94ad6bf81286f3273630d8bc8336103e40d888fbb0d0c74f1f54c04c44d37a19fa493fcb0cf29e096388d855ff3b7fb626d187
|
|
7
|
+
data.tar.gz: 46dc51138f257f1768ddef3d81b42d1c040a53812a9970bd8bb7fee60a94a6e0d16f210069faa4c6b484f58994070f1e9ad04459685f57932fa89db40eb5bf8c
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/lib/async/grpc/client.rb
CHANGED
|
@@ -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 "
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
#
|
|
139
|
-
|
|
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,
|
|
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
|
|
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 <
|
|
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.
|
data/lib/async/grpc/stub.rb
CHANGED
|
@@ -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
|
data/lib/async/grpc/version.rb
CHANGED
data/lib/async/grpc.rb
CHANGED
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.
|
|
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-
|
|
70
|
+
name: protocol-grpc
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
58
72
|
requirements:
|
|
59
73
|
- - "~>"
|
|
60
74
|
- !ruby/object:Gem::Version
|
|
61
|
-
version:
|
|
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:
|
|
82
|
+
version: 0.12.0
|
|
69
83
|
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: protocol-
|
|
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.
|
|
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.
|
|
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/
|
|
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.
|
|
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
|