async-grpc 0.7.0 → 0.9.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: 13df5d80dfc403580a0b3efa15de081d71f285901a5f082bacb7cb840b23f66f
4
- data.tar.gz: 59f811ca32b0beac56192f587be2e3184279c5d9160891b9b931b20229473033
3
+ metadata.gz: 031cde64ff322b5e4d247cec205cdbe17219d455240866df154fca3f370e675c
4
+ data.tar.gz: d926dbe26788e37bbcb219cce9224c40f5c282bb0ed84c42fcbb6118472da69f
5
5
  SHA512:
6
- metadata.gz: 2d85ab093902e64a2ea3877f788eeac19bd6ae005bf47d3ddfa52831d0fa8193255f6ae0158b7bbb6897f4f5cdd39935cf03d7b14b9139da8257ffef07e1258e
7
- data.tar.gz: ee29fe46cf81d22298c94c5cb63b17a4783e4159f6110f70efc91444d507f47aec2bf14346d7e6187b9b40bee3f18a1e38c511afc0250c3f2fadbf88790495b7
6
+ metadata.gz: 5a5cb6a72c70046df6b85f287d96f04b2ab12a807945025b5c38b924f6dcb5480b012444b42b4e857e63f1b535c5c6db9cd9c6fe6ad9f04c6f7976065cdcfbf5
7
+ data.tar.gz: dc65cc27dfcb838ce8b77f805012333edeb0813707e9d646a805c9776f9e7fae9d2dca78cd3a5dc29ceb27b99ff435cb9f84adcbeba131362aeb40ef7dc37020
checksums.yaml.gz.sig CHANGED
Binary file
@@ -126,6 +126,49 @@ Async do
126
126
  end
127
127
  ```
128
128
 
129
+ ### Instrumentation
130
+
131
+ Subclass the dispatcher and override {ruby Async::GRPC::Dispatcher#emit_completion} to record completion metrics or logs.
132
+ The hook runs once per request after the final gRPC status is assigned, including routing failures and completed streaming calls:
133
+
134
+ ``` ruby
135
+ class InstrumentedDispatcher < Async::GRPC::Dispatcher
136
+ protected
137
+
138
+ def emit_completion(call, error = nil)
139
+ Console.info(self, "Request completed!", path: call.request.path, status: call.status)
140
+ end
141
+ end
142
+ ```
143
+
144
+ A cancelled request has a status of `Protocol::GRPC::Status::CANCELLED` and no `error`. Exceptions raised by the hook are logged and ignored.
145
+
146
+ Translate expected application errors at the service boundary by raising {ruby Protocol::GRPC::Error}:
147
+
148
+ ``` ruby
149
+ class ApplicationService < Async::GRPC::Service
150
+ def find_record(input, output, call)
151
+ output.write(Record.find(input.read.id))
152
+ rescue ActiveRecord::RecordNotFound => error
153
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::NOT_FOUND, error.message)
154
+ end
155
+ end
156
+ ```
157
+
158
+ Override {ruby Async::GRPC::Dispatcher#invoke_service} when you need middleware around service execution:
159
+
160
+ ``` ruby
161
+ class ApplicationDispatcher < Async::GRPC::Dispatcher
162
+ protected
163
+
164
+ def invoke_service(service, handler_method, input, output, call)
165
+ RequestContext.for(call.request) do
166
+ super
167
+ end
168
+ end
169
+ end
170
+ ```
171
+
129
172
  ## Complete Example
130
173
 
131
174
  ``` ruby
data/context/index.yaml CHANGED
@@ -3,6 +3,8 @@
3
3
  ---
4
4
  description: Client and server implementation for gRPC using Async.
5
5
  metadata:
6
+ bug_tracker_uri: https://github.com/socketry/async-grpc/issues
7
+ changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
6
8
  documentation_uri: https://socketry.github.io/async-grpc/
7
9
  source_code_uri: https://github.com/socketry/async-grpc.git
8
10
  files:
@@ -2,6 +2,7 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require "async"
7
8
  require "async/http/client"
@@ -10,13 +11,12 @@ require "async/http/endpoint"
10
11
  require "protocol/http"
11
12
  require "protocol/grpc"
12
13
  require "protocol/grpc/interface"
13
- require "protocol/grpc/methods"
14
14
  require "protocol/grpc/body/readable_body"
15
15
  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
@@ -123,7 +123,7 @@ module Async
123
123
  raise ArgumentError, "Unknown method: #{method}" unless rpc
124
124
 
125
125
  path = service.path(method)
126
- headers = Protocol::GRPC::Methods.build_headers(
126
+ headers = Protocol::GRPC::Metadata.build(
127
127
  metadata: metadata,
128
128
  timeout: timeout,
129
129
  content_type: "application/grpc+proto"
@@ -277,6 +277,7 @@ module Async
277
277
  # @parameter initial [Object | Array | Nil] Optional initial message(s) to write before waiting for the response
278
278
  # @yields {|input, output| ...} Block to handle bidirectional streaming
279
279
  # @returns [Protocol::GRPC::Body::ReadableBody] Readable body for streaming messages
280
+ # Without a block, `initial` is sent as the complete request stream and the caller owns the returned readable body.
280
281
  # @raises [Protocol::GRPC::Error] If the gRPC call fails
281
282
  def bidirectional_call(path, headers, request_class, response_class, encoding, initial: nil, &block)
282
283
  body = Protocol::GRPC::Body::WritableBody.new(
@@ -284,6 +285,7 @@ module Async
284
285
  message_class: request_class
285
286
  )
286
287
  Array(initial).each{|message| body.write(message)}
288
+ body.close_write unless block_given?
287
289
 
288
290
  http_request = Protocol::HTTP::Request["POST", path, headers, body]
289
291
  response = call(http_request)
@@ -298,6 +300,8 @@ module Async
298
300
  )
299
301
 
300
302
  unless block_given?
303
+ # The caller owns the readable body and its underlying response:
304
+ response = nil
301
305
  return readable_body
302
306
  end
303
307
 
@@ -328,7 +332,7 @@ module Async
328
332
  return if status == Protocol::GRPC::Status::OK
329
333
 
330
334
  message = Protocol::GRPC::Metadata.extract_message(response.headers)
331
- metadata = Protocol::GRPC::Methods.extract_metadata(response.headers)
335
+ metadata = Protocol::GRPC::Metadata.extract(response.headers)
332
336
 
333
337
  remote_error = RemoteError.for(message, metadata)
334
338
 
@@ -2,13 +2,15 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require "async"
7
- require "async/deadline"
8
+
9
+ require_relative "error"
8
10
 
9
11
  require "protocol/grpc/middleware"
10
- require "protocol/grpc/methods"
11
12
  require "protocol/grpc/call"
13
+ require "protocol/grpc/route"
12
14
  require "protocol/grpc/body/readable_body"
13
15
  require "protocol/grpc/body/writable_body"
14
16
  require "protocol/grpc/metadata"
@@ -44,113 +46,237 @@ module Async
44
46
 
45
47
  protected
46
48
 
49
+ # Called once after the final gRPC status has been assigned.
50
+ #
51
+ # Override this method to record request metrics or logs. The hook also runs for
52
+ # routing/setup failures and after streaming calls finish. A cancelled call has
53
+ # a status of `Protocol::GRPC::Status::CANCELLED` and no error.
54
+ #
55
+ # Exceptions raised by this hook are ignored.
56
+ #
57
+ # @parameter call [Protocol::GRPC::Call] The call context, including the request and the response.
58
+ # @parameter error [Exception | Nil] The error which caused the call to fail, if any.
59
+ # @returns [void]
60
+ # @example Record the final gRPC status code
61
+ # def emit_completion(call, error = nil)
62
+ # service_name, method_name = Protocol::GRPC::Route.parse(call.request.path)
63
+ # metrics.increment("grpc.request", tags: {service: service_name, method: method_name, status: call.status})
64
+ # end
65
+ def emit_completion(call, error = nil)
66
+ # Implementation-defined.
67
+ end
68
+
69
+ # Invoke a service handler.
70
+ #
71
+ # Override this method to establish request context or wrap handlers with
72
+ # application middleware. Overrides should call `super` to invoke the handler.
73
+ # Stream cleanup, trailer handling, and status assignment are performed by the
74
+ # dispatcher after this method returns or raises.
75
+ #
76
+ # This hook only surrounds service execution. Use {emit_completion} to observe
77
+ # every request, including routing/setup failures. A deadline expiring during
78
+ # service execution raises {DeadlineExceededError} through this hook before it is
79
+ # converted to `DEADLINE_EXCEEDED`.
80
+ #
81
+ # @parameter service [Async::GRPC::Service] The service containing the handler.
82
+ # @parameter handler_method [Symbol] The service method to invoke.
83
+ # @parameter input [Protocol::GRPC::Body::ReadableBody] The decoded request stream.
84
+ # @parameter output [Protocol::GRPC::Body::WritableBody] The response stream.
85
+ # @parameter call [Protocol::GRPC::Call] The call context.
86
+ # @returns [Object | Nil] An internal dispatch result with no stable application-facing contract.
87
+ # @example Wrap service execution with application middleware
88
+ # def invoke_service(service, handler_method, input, output, call)
89
+ # context = RequestContext.new(call.request)
90
+ # middleware.call(context) do
91
+ # super
92
+ # end
93
+ # end
47
94
  def invoke_service(service, handler_method, input, output, call)
95
+ service.send(handler_method, input, output, call)
96
+ end
97
+
98
+ # Map an internal dispatcher error to the gRPC status and message reported to the client.
99
+ #
100
+ # @parameter error [Exception] The error which caused the call to fail.
101
+ # @returns [Tuple(Integer, String | Nil)] The gRPC status code and message.
102
+ private def status_for(error)
103
+ case error
104
+ when DeadlineExceededError
105
+ return Protocol::GRPC::Status::DEADLINE_EXCEEDED, "Deadline exceeded!"
106
+ when Protocol::GRPC::Error
107
+ return error.status_code, error.message
108
+ else
109
+ # A `nil` message defers to the message of the error itself:
110
+ return Protocol::GRPC::Status::INTERNAL, nil
111
+ end
112
+ end
113
+
114
+ # Invoke the service with deadline enforcement and completion reporting.
115
+ #
116
+ # @parameter parent [Async::Task] The task used to enforce the call deadline.
117
+ def dispatch_to_service(service, handler_method, input, output, call, parent: Async::Task.current)
118
+ error = nil
119
+ cancellation = nil
120
+
48
121
  begin
49
- service.send(handler_method, input, output, call)
50
- ensure
51
- # Close input stream:
52
- input.close
122
+ if deadline = call.deadline
123
+ parent.with_timeout(deadline.remaining, DeadlineExceededError) do
124
+ invoke_service(service, handler_method, input, output, call)
125
+ end
126
+ else
127
+ invoke_service(service, handler_method, input, output, call)
128
+ end
129
+ rescue => error
130
+ prepare_trailers(output, call)
131
+ assign_status(call, error)
132
+ rescue Async::Stop => cancellation
133
+ prepare_trailers(output, call)
134
+
135
+ # Cancellation has a fixed status and does not require error mapping:
136
+ if headers = call.response&.headers
137
+ Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::CANCELLED)
138
+ end
53
139
 
54
- # Close output stream:
55
- output.close_write unless output.closed?
140
+ raise
141
+ else
142
+ prepare_trailers(output, call)
56
143
 
57
- # gRPC supports trailers-only responses, but only if there are no data frames. If at this point, there are data frames (which may or may not have been sent yet), we need to mark trailers:
58
- if output.count > 0
59
- call.response.headers.trailer!
144
+ # Add status (if not already set by handler):
145
+ if headers = call.response&.headers
146
+ unless headers.key?("grpc-status")
147
+ Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
148
+ end
60
149
  end
150
+ ensure
151
+ finalize_streams(input, output, call, error, cancellation)
61
152
  end
62
-
63
- # Add status (if not already set by handler):
64
- if headers = call.response&.headers
65
- # Only add OK status if grpc-status hasn't been set by the handler:
66
- unless headers.key?("grpc-status")
67
- Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::OK)
68
- end
153
+ end
154
+
155
+ # Mark response headers as trailers when data was written.
156
+ def prepare_trailers(output, call)
157
+ if output.count > 0
158
+ call.response.headers.trailer!
69
159
  end
70
160
  end
71
161
 
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
75
- invoke_service(service, handler_method, input, output, call)
76
- end
162
+ # Close the input and output streams.
163
+ def close_streams(input, output, call)
164
+ # Close input stream:
165
+ input.close
166
+ ensure
167
+ # Close output stream:
168
+ unless output.closed?
169
+ output.close_write
170
+ end
171
+ end
172
+
173
+ # Close streams and report completion while preserving an active service error or cancellation.
174
+ private def finalize_streams(input, output, call, error, cancellation)
175
+ close_streams(input, output, call)
176
+ rescue => close_error
177
+ if error || cancellation
178
+ Console.warn(self, "Error during stream cleanup!", exception: close_error)
77
179
  else
78
- invoke_service(service, handler_method, input, output, call)
180
+ error = close_error
181
+ assign_status(call, error)
182
+ raise
79
183
  end
80
- rescue Async::TimeoutError => error
81
- # Close input and output streams:
82
- input.close
83
- output.close_write unless output.closed?
84
-
85
- # Set DEADLINE_EXCEEDED status in trailers:
184
+ ensure
185
+ # Cancellation raises `Async::Stop`, not `StandardError`:
186
+ report_completion(call, error)
187
+ end
188
+
189
+ # Assign the status for the given error to the response.
190
+ def assign_status(call, error)
86
191
  if headers = call.response&.headers
87
- Protocol::GRPC::Metadata.assign_status!(headers, status: Protocol::GRPC::Status::DEADLINE_EXCEEDED, message: "Deadline exceeded!", error: error)
192
+ begin
193
+ status, message = status_for(error)
194
+ rescue => internal_error
195
+ Console.warn(self, "Error during status mapping!", exception: internal_error)
196
+
197
+ status = Protocol::GRPC::Status::INTERNAL
198
+ message = nil
199
+ end
200
+
201
+ Protocol::GRPC::Metadata.assign_status!(headers, status: status, message: message, error: error)
88
202
  end
89
203
  end
90
204
 
205
+ # Report completion without allowing instrumentation to affect the request.
206
+ private def report_completion(call, error = nil)
207
+ emit_completion(call, error)
208
+ rescue => error
209
+ Console.warn(self, "Error during emit completion!", exception: error)
210
+ end
211
+
91
212
  # Dispatch the request to the appropriate service.
92
213
  # @parameter request [Protocol::HTTP::Request] The HTTP request
93
214
  # @returns [Protocol::HTTP::Response] The HTTP response
94
- # @raises [Protocol::GRPC::Error] If service or method is not found
95
215
  def dispatch(request)
96
- # Parse service and method from path:
97
- service_name, method_name = Protocol::GRPC::Methods.parse_path(request.path)
98
-
99
- # Find service:
100
- service = @services[service_name]
101
- unless service
102
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
103
- end
104
-
105
- # Verify service name matches:
106
- unless service_name == service.service_name
107
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
108
- end
109
-
110
- # Get RPC descriptions from the service:
111
- rpc_descriptor = service.rpc_descriptions[method_name]
112
- unless rpc_descriptor
113
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
114
- end
115
-
116
- handler_method = rpc_descriptor.method
117
- request_class = rpc_descriptor.request_class
118
- response_class = rpc_descriptor.response_class
119
-
120
- # Verify handler method exists:
121
- unless service.respond_to?(handler_method, true)
122
- raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
123
- end
124
-
125
- # Create protocol-level objects for gRPC handling:
126
- encoding = request.headers["grpc-encoding"]
127
- input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
128
- output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
129
-
130
216
  # Create response headers:
217
+ encoding = request.headers["grpc-encoding"]
131
218
  response_headers = Protocol::HTTP::Headers.new([], nil, policy: Protocol::GRPC::HEADER_POLICY)
132
219
  response_headers["content-type"] = "application/grpc+proto"
133
220
  response_headers["grpc-encoding"] = encoding if encoding
134
221
 
135
- # Create response object:
136
- response = Protocol::HTTP::Response[200, response_headers, output]
222
+ # Assign the body after routing so setup failures are trailers-only:
223
+ response = Protocol::HTTP::Response[200, response_headers, nil]
224
+ call = nil
137
225
 
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)
226
+ begin
227
+ # Create the call context:
228
+ call = Protocol::GRPC::Call.for(request, response)
229
+
230
+ # Extract the routing information from the request path:
231
+ service_name, method_name = Protocol::GRPC::Route.parse(request.path)
232
+
233
+ # Find service:
234
+ service = @services[service_name]
235
+ unless service
236
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service not found: #{service_name}")
237
+ end
238
+
239
+ # Verify service name matches:
240
+ unless service_name == service.service_name
241
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Service name mismatch: expected #{service.service_name}, got #{service_name}")
242
+ end
243
+
244
+ # Get RPC descriptions from the service:
245
+ rpc_descriptor = service.rpc_descriptions[method_name]
246
+ unless rpc_descriptor
247
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Method not found: #{method_name}")
248
+ end
249
+
250
+ handler_method = rpc_descriptor.method
251
+ request_class = rpc_descriptor.request_class
252
+ response_class = rpc_descriptor.response_class
253
+
254
+ # Verify handler method exists:
255
+ unless service.respond_to?(handler_method, true)
256
+ raise Protocol::GRPC::Error.new(Protocol::GRPC::Status::UNIMPLEMENTED, "Handler method not implemented: #{handler_method}")
257
+ end
258
+
259
+ # Create protocol-level objects for gRPC handling:
260
+ input = Protocol::GRPC::Body::ReadableBody.new(request.body, message_class: request_class, encoding: encoding)
261
+ output = Protocol::GRPC::Body::WritableBody.new(message_class: response_class, encoding: encoding)
262
+ response.body = output
263
+ rescue => error
264
+ # Routing/setup failures. {Protocol::GRPC::Call.for} can fail before the call context exists:
265
+ call ||= Protocol::GRPC::Call.new(request, response)
266
+
267
+ assign_status(call, error)
268
+ report_completion(call, error)
269
+
270
+ return response
142
271
  end
143
272
 
144
- # Create call context with request and response:
145
- call = Protocol::GRPC::Call.new(request, response, deadline: deadline)
146
-
147
273
  if rpc_descriptor.streaming?
148
274
  Async do |task|
149
- dispatch_to_service(service, handler_method, input, output, call, deadline, parent: task)
275
+ dispatch_to_service(service, handler_method, input, output, call, parent: task)
150
276
  end
151
277
  else
152
278
  # Unary call:
153
- dispatch_to_service(service, handler_method, input, output, call, deadline)
279
+ dispatch_to_service(service, handler_method, input, output, call)
154
280
  end
155
281
 
156
282
  return response
@@ -2,13 +2,22 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025-2026, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  module Async
7
8
  module GRPC
9
+ # Represents a generic async gRPC error.
10
+ class Error < StandardError
11
+ end
12
+
13
+ # Raised when a gRPC call exceeds its deadline.
14
+ class DeadlineExceededError < Error
15
+ end
16
+
8
17
  # Represents an error that originated from a remote gRPC server.
9
18
  # Used as the `cause` of {Protocol::GRPC::Error} when the client receives a non-OK status.
10
19
  # The message and optional backtrace are extracted from response metadata.
11
- class RemoteError < StandardError
20
+ class RemoteError < Error
12
21
  # Create a RemoteError from server response metadata.
13
22
  # @parameter message [String | Nil] The error message from `grpc-message` header.
14
23
  # @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.
@@ -7,7 +7,7 @@
7
7
  module Async
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.7.0"
10
+ VERSION = "0.9.0"
11
11
  end
12
12
  end
13
13
 
data/lib/async/grpc.rb CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2025, by Samuel Williams.
5
+ # Copyright, 2026, by Alex Watt.
5
6
 
6
7
  require_relative "grpc/version"
8
+ require_relative "grpc/error"
7
9
  require_relative "grpc/client"
8
10
  require_relative "grpc/service"
9
11
  require_relative "grpc/stub"
data/license.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # MIT License
2
2
 
3
3
  Copyright, 2025-2026, by Samuel Williams.
4
+ Copyright, 2026, by Alex Watt.
4
5
 
5
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
7
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -24,6 +24,14 @@ 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.9.0
28
+
29
+ - Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
30
+
31
+ ### v0.8.0
32
+
33
+ - 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.
34
+
27
35
  ### v0.6.0
28
36
 
29
37
  - Ensure `grpc-status` (and related metadata) is sent as a trailer, if data frames are written.
@@ -66,26 +74,26 @@ Please see the [project releases](https://socketry.github.io/async-grpc/releases
66
74
 
67
75
  We welcome contributions to this project.
68
76
 
69
- 1. Fork it.
77
+ 1. Fork the repository.
70
78
  2. Create your feature branch (`git checkout -b my-new-feature`).
71
- 3. Commit your changes (`git commit -am 'Add some feature'`).
79
+ 3. Commit your changes (`git commit -am 'Add some feature.'`).
72
80
  4. Push to the branch (`git push origin my-new-feature`).
73
- 5. Create new Pull Request.
81
+ 5. Create a new pull request.
74
82
 
75
83
  ### Running Tests
76
84
 
77
85
  To run the test suite:
78
86
 
79
- ``` shell
80
- bundle exec sus
87
+ ``` bash
88
+ $ bundle exec sus
81
89
  ```
82
90
 
83
91
  ### Making Releases
84
92
 
85
93
  To make a new release:
86
94
 
87
- ``` shell
88
- bundle exec bake gem:release:patch # or minor or major
95
+ ``` bash
96
+ $ bundle exec bake gem:release:patch # or minor or major
89
97
  ```
90
98
 
91
99
  ### Developer Certificate of Origin
data/releases.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Releases
2
2
 
3
+ ## v0.9.0
4
+
5
+ - Added `Async::GRPC::Dispatcher#emit_completion` for once-per-request completion instrumentation, including routing failures, cancellations, and the final gRPC status.
6
+
7
+ ## v0.8.0
8
+
9
+ - 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.
10
+
3
11
  ## v0.6.0
4
12
 
5
13
  - 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,10 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
+ - Alex Watt
8
9
  bindir: bin
9
10
  cert_chain:
10
11
  - |
@@ -38,6 +39,20 @@ cert_chain:
38
39
  -----END CERTIFICATE-----
39
40
  date: 1980-01-02 00:00:00.000000000 Z
40
41
  dependencies:
42
+ - !ruby/object:Gem::Dependency
43
+ name: async
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 2.38.0
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: 2.38.0
41
56
  - !ruby/object:Gem::Dependency
42
57
  name: async-http
43
58
  requirement: !ruby/object:Gem::Requirement
@@ -58,14 +73,14 @@ dependencies:
58
73
  requirements:
59
74
  - - "~>"
60
75
  - !ruby/object:Gem::Version
61
- version: 0.11.0
76
+ version: 0.14.0
62
77
  type: :runtime
63
78
  prerelease: false
64
79
  version_requirements: !ruby/object:Gem::Requirement
65
80
  requirements:
66
81
  - - "~>"
67
82
  - !ruby/object:Gem::Version
68
- version: 0.11.0
83
+ version: 0.14.0
69
84
  - !ruby/object:Gem::Dependency
70
85
  name: protocol-http
71
86
  requirement: !ruby/object:Gem::Requirement
@@ -90,7 +105,7 @@ files:
90
105
  - lib/async/grpc.rb
91
106
  - lib/async/grpc/client.rb
92
107
  - lib/async/grpc/dispatcher.rb
93
- - lib/async/grpc/remote_error.rb
108
+ - lib/async/grpc/error.rb
94
109
  - lib/async/grpc/service.rb
95
110
  - lib/async/grpc/stub.rb
96
111
  - lib/async/grpc/version.rb
@@ -102,6 +117,8 @@ homepage: https://github.com/socketry/async-grpc
102
117
  licenses:
103
118
  - MIT
104
119
  metadata:
120
+ bug_tracker_uri: https://github.com/socketry/async-grpc/issues
121
+ changelog_uri: https://github.com/socketry/async-grpc/blob/main/releases.md
105
122
  documentation_uri: https://socketry.github.io/async-grpc/
106
123
  source_code_uri: https://github.com/socketry/async-grpc.git
107
124
  rdoc_options: []
metadata.gz.sig CHANGED
Binary file