From f0cf55e6192f8af160fc7e66a9d0248a07ecd651 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 22 Aug 2026 10:06:53 +1200 Subject: [PATCH 1/4] Add status to gRPC calls --- context/getting-started.md | 7 ++++--- guides/getting-started/readme.md | 7 ++++--- lib/protocol/grpc/call.rb | 9 +++++++++ releases.md | 1 + test/protocol/grpc/call.rb | 21 +++++++++++++++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) diff --git a/context/getting-started.md b/context/getting-started.md index 8e12834..e784d34 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -18,7 +18,7 @@ $ bundle add protocol-grpc - A {ruby Protocol::GRPC::Body::Readable} class which handles reading gRPC messages from HTTP request/response bodies with automatic framing and decoding. - A {ruby Protocol::GRPC::Body::Writable} class which handles writing gRPC messages to HTTP request/response bodies with automatic framing and encoding. - A {ruby Protocol::GRPC::Middleware} abstract base class for building gRPC server applications. - - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline tracking. + - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline and status tracking. - A {ruby Protocol::GRPC::Status} module with gRPC status code constants. - A {ruby Protocol::GRPC::Error} hierarchy for gRPC-specific error handling. @@ -132,13 +132,14 @@ end ``` ruby require "protocol/grpc/call" -call = Protocol::GRPC::Call.new(request, deadline: deadline) +call = Protocol::GRPC::Call.new(request, response, deadline: deadline) # Access request call.request # => Protocol::HTTP::Request -# Check deadline +# Check deadline and status call.deadline.exceeded? # => false +call.status # => Protocol::GRPC::Status::OK # Access peer information call.peer # => Protocol::HTTP::Address diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 8e12834..e784d34 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -18,7 +18,7 @@ $ bundle add protocol-grpc - A {ruby Protocol::GRPC::Body::Readable} class which handles reading gRPC messages from HTTP request/response bodies with automatic framing and decoding. - A {ruby Protocol::GRPC::Body::Writable} class which handles writing gRPC messages to HTTP request/response bodies with automatic framing and encoding. - A {ruby Protocol::GRPC::Middleware} abstract base class for building gRPC server applications. - - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline tracking. + - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline and status tracking. - A {ruby Protocol::GRPC::Status} module with gRPC status code constants. - A {ruby Protocol::GRPC::Error} hierarchy for gRPC-specific error handling. @@ -132,13 +132,14 @@ end ``` ruby require "protocol/grpc/call" -call = Protocol::GRPC::Call.new(request, deadline: deadline) +call = Protocol::GRPC::Call.new(request, response, deadline: deadline) # Access request call.request # => Protocol::HTTP::Request -# Check deadline +# Check deadline and status call.deadline.exceeded? # => false +call.status # => Protocol::GRPC::Status::OK # Access peer information call.peer # => Protocol::HTTP::Address diff --git a/lib/protocol/grpc/call.rb b/lib/protocol/grpc/call.rb index cbcd0d7..a3e9286 100644 --- a/lib/protocol/grpc/call.rb +++ b/lib/protocol/grpc/call.rb @@ -66,6 +66,15 @@ def time_remaining @deadline&.remaining end + # Get the current gRPC status of the call. + # Calls without a response or an assigned status report {Status::UNKNOWN}. + # @returns [Integer] The current gRPC status code. + def status + return Status::UNKNOWN unless headers = @response&.headers + + Metadata.extract_status(headers) + end + # Get peer information (client address). # @returns [String | Nil] The peer address as a string, or `Nil` if not available def peer diff --git a/releases.md b/releases.md index 3386aa5..2976ec4 100644 --- a/releases.md +++ b/releases.md @@ -3,6 +3,7 @@ ## Unreleased - **Breaking**: Removed the unused `Protocol::GRPC::Call#cancel!` and `Protocol::GRPC::Call#cancelled?` methods. + - Added `Protocol::GRPC::Call#status` to report the status assigned to the response. ## v0.13.1 diff --git a/test/protocol/grpc/call.rb b/test/protocol/grpc/call.rb index 08ef9b1..5747c85 100644 --- a/test/protocol/grpc/call.rb +++ b/test/protocol/grpc/call.rb @@ -63,6 +63,27 @@ end end + with "status" do + it "returns the status assigned to the response" do + Protocol::GRPC::Metadata.assign_status!(response.headers, status: Protocol::GRPC::Status::RESOURCE_EXHAUSTED) + call = subject.new(request, response) + + expect(call.status).to be == Protocol::GRPC::Status::RESOURCE_EXHAUSTED + end + + it "returns UNKNOWN without an assigned response status" do + call = subject.new(request, response) + + expect(call.status).to be == Protocol::GRPC::Status::UNKNOWN + end + + it "returns UNKNOWN without a response" do + call = subject.new(request) + + expect(call.status).to be == Protocol::GRPC::Status::UNKNOWN + end + end + with "deadline" do let(:deadline) {Async::Deadline.start(5.0)} From 0c4f422d863cf6ec3d22ee2e5b72191b5130ecfe Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 22 Aug 2026 10:23:48 +1200 Subject: [PATCH 2/4] Return nil when call status is unavailable --- lib/protocol/grpc/call.rb | 9 ++++----- test/protocol/grpc/call.rb | 8 ++++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/protocol/grpc/call.rb b/lib/protocol/grpc/call.rb index a3e9286..72f7a38 100644 --- a/lib/protocol/grpc/call.rb +++ b/lib/protocol/grpc/call.rb @@ -67,12 +67,11 @@ def time_remaining end # Get the current gRPC status of the call. - # Calls without a response or an assigned status report {Status::UNKNOWN}. - # @returns [Integer] The current gRPC status code. + # @returns [Integer | Nil] The current gRPC status code, if available. def status - return Status::UNKNOWN unless headers = @response&.headers - - Metadata.extract_status(headers) + if headers = @response&.headers + headers["grpc-status"]&.to_s&.to_i + end end # Get peer information (client address). diff --git a/test/protocol/grpc/call.rb b/test/protocol/grpc/call.rb index 5747c85..5518fcf 100644 --- a/test/protocol/grpc/call.rb +++ b/test/protocol/grpc/call.rb @@ -71,16 +71,16 @@ expect(call.status).to be == Protocol::GRPC::Status::RESOURCE_EXHAUSTED end - it "returns UNKNOWN without an assigned response status" do + it "returns nil without an assigned response status" do call = subject.new(request, response) - expect(call.status).to be == Protocol::GRPC::Status::UNKNOWN + expect(call.status).to be_nil end - it "returns UNKNOWN without a response" do + it "returns nil without a response" do call = subject.new(request) - expect(call.status).to be == Protocol::GRPC::Status::UNKNOWN + expect(call.status).to be_nil end end From c603f66055a57b0b06ab573e9dfd5d7be9c8a98f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 22 Aug 2026 10:24:25 +1200 Subject: [PATCH 3/4] Clarify gRPC call documentation --- context/getting-started.md | 2 +- guides/getting-started/readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/context/getting-started.md b/context/getting-started.md index e784d34..1adb5e9 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -18,7 +18,7 @@ $ bundle add protocol-grpc - A {ruby Protocol::GRPC::Body::Readable} class which handles reading gRPC messages from HTTP request/response bodies with automatic framing and decoding. - A {ruby Protocol::GRPC::Body::Writable} class which handles writing gRPC messages to HTTP request/response bodies with automatic framing and encoding. - A {ruby Protocol::GRPC::Middleware} abstract base class for building gRPC server applications. - - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline and status tracking. + - A {ruby Protocol::GRPC::Call} class which represents the request, response, metadata, and deadline for a single gRPC RPC call. - A {ruby Protocol::GRPC::Status} module with gRPC status code constants. - A {ruby Protocol::GRPC::Error} hierarchy for gRPC-specific error handling. diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index e784d34..1adb5e9 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -18,7 +18,7 @@ $ bundle add protocol-grpc - A {ruby Protocol::GRPC::Body::Readable} class which handles reading gRPC messages from HTTP request/response bodies with automatic framing and decoding. - A {ruby Protocol::GRPC::Body::Writable} class which handles writing gRPC messages to HTTP request/response bodies with automatic framing and encoding. - A {ruby Protocol::GRPC::Middleware} abstract base class for building gRPC server applications. - - A {ruby Protocol::GRPC::Call} class which represents the context of a single gRPC RPC call, including deadline and status tracking. + - A {ruby Protocol::GRPC::Call} class which represents the request, response, metadata, and deadline for a single gRPC RPC call. - A {ruby Protocol::GRPC::Status} module with gRPC status code constants. - A {ruby Protocol::GRPC::Error} hierarchy for gRPC-specific error handling. From 199a33c248d07c43b296dd1cdf324cc9f7d79d17 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 22 Aug 2026 10:26:15 +1200 Subject: [PATCH 4/4] Assume gRPC response header policy --- lib/protocol/grpc/call.rb | 2 +- test/protocol/grpc/call.rb | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/protocol/grpc/call.rb b/lib/protocol/grpc/call.rb index 72f7a38..1c73d69 100644 --- a/lib/protocol/grpc/call.rb +++ b/lib/protocol/grpc/call.rb @@ -70,7 +70,7 @@ def time_remaining # @returns [Integer | Nil] The current gRPC status code, if available. def status if headers = @response&.headers - headers["grpc-status"]&.to_s&.to_i + headers["grpc-status"]&.to_i end end diff --git a/test/protocol/grpc/call.rb b/test/protocol/grpc/call.rb index 5518fcf..a5d261f 100644 --- a/test/protocol/grpc/call.rb +++ b/test/protocol/grpc/call.rb @@ -10,7 +10,8 @@ describe Protocol::GRPC::Call do let(:headers) {Protocol::HTTP::Headers.new([["authorization", "Bearer token123"]])} let(:request) {Protocol::HTTP::Request.new("https", "localhost", "POST", "/service/method", nil, headers, nil)} - let(:response) {Protocol::HTTP::Response[200, {}, []]} + let(:response_headers) {Protocol::HTTP::Headers.new(policy: Protocol::GRPC::HEADER_POLICY)} + let(:response) {Protocol::HTTP::Response[200, response_headers, []]} with ".for" do it "creates a call with request and response" do