protocol-grpc 0.3.0 → 0.5.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: a889e5eb3f6b74e8609a27a4667b62611ab8dc0515d24d9ecf70c4fee7e25b11
4
- data.tar.gz: 56b5bc9e9d8a767db66ead55bcc97404e8092ef6a75a008d4bd893624391b808
3
+ metadata.gz: ef695986f77971ac09708cb8bc1cbb694fd402c1c6f2fcc9070b26309a95fbcb
4
+ data.tar.gz: 2ba1216276a20618b2e93034981f15522f5b6dae44a615d51796b08dc67f2dbe
5
5
  SHA512:
6
- metadata.gz: 6aab2ee9f75421070a6348c39da680c7cd320e31021b9492f48d48832ab4caf580ffa77113a792e336cd87158890d22ed12d9e4650079954b33c99c7da85e422
7
- data.tar.gz: 67c0e25c06ed8f5c8ad431233506f6133655d64a6e546d9767c74049d85b0fa6b54e37384e15e6458c429f0431779fad96c8b01e30821acd8f7952a260ccf3de
6
+ metadata.gz: a5c9661129270d4ccd3d47b84ae6d3e2058f1c7499fb9302f69a85d08dbe67ea305d72f23d2ab36f62ab728a40982b5ae7f9b1c2aaa29e09378206912b5afec7
7
+ data.tar.gz: bb0825134335b844320744dda1130e3fd9426c5eb4b8894a17cdb408c0d67b978c3a2bcb05c9a5164faa00d0ebf0903097c9533e0695aac8963068265eb3eb90
checksums.yaml.gz.sig CHANGED
Binary file
@@ -130,4 +130,3 @@ call.deadline.exceeded? # => false
130
130
  call.peer # => Protocol::HTTP::Address
131
131
  ```
132
132
 
133
-
data/design.md CHANGED
@@ -293,58 +293,22 @@ module Protocol
293
293
  message ? URI.decode_www_form_component(message) : nil
294
294
  end
295
295
 
296
- # Build headers with gRPC status and message
297
- # @parameter status [Integer] gRPC status code
298
- # @parameter message [String, nil] Optional status message
299
- # @parameter policy [Hash] Header policy to use
300
- # @returns [Protocol::HTTP::Headers]
301
- def self.build_status_headers(status: Status::OK, message: nil, policy: HEADER_POLICY)
302
- headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
303
- headers["grpc-status"] = status.to_s
304
- headers["grpc-message"] = URI.encode_www_form_component(message) if message
305
- headers
306
- end
307
-
308
- # Mark that trailers will follow (call after sending initial headers)
309
- # @parameter headers [Protocol::HTTP::Headers]
310
- # @returns [Protocol::HTTP::Headers]
311
- def self.prepare_trailers!(headers)
312
- headers.trailer!
313
- headers
314
- end
296
+ # Add gRPC status, message, and optional backtrace to headers.
297
+ # Whether these become headers or trailers is controlled by the protocol layer.
298
+ # @parameter headers [Protocol::HTTP::Headers]
299
+ # @parameter status [Integer] gRPC status code
300
+ # @parameter message [String | Nil] Optional status message
301
+ # @parameter error [Exception | Nil] Optional error object (used to extract backtrace)
302
+ def self.add_status!(headers, status: Status::OK, message: nil, error: nil)
303
+ headers["grpc-status"] = Header::Status.new(status)
304
+ headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
315
305
 
316
- # Add status as trailers to existing headers
317
- # @parameter headers [Protocol::HTTP::Headers]
318
- # @parameter status [Integer] gRPC status code
319
- # @parameter message [String, nil] Optional status message
320
- def self.add_status_trailer!(headers, status: Status::OK, message: nil)
321
- headers.trailer! unless headers.trailer?
322
- headers["grpc-status"] = status.to_s
323
- headers["grpc-message"] = URI.encode_www_form_component(message) if message
324
- end
325
-
326
- # Add status as initial headers (for trailers-only responses)
327
- # @parameter headers [Protocol::HTTP::Headers]
328
- # @parameter status [Integer] gRPC status code
329
- # @parameter message [String, nil] Optional status message
330
- def self.add_status_header!(headers, status: Status::OK, message: nil)
331
- headers["grpc-status"] = status.to_s
332
- headers["grpc-message"] = URI.encode_www_form_component(message) if message
333
- end
334
-
335
- # Build a trailers-only error response (no body, status in headers)
336
- # @parameter status [Integer] gRPC status code
337
- # @parameter message [String, nil] Optional status message
338
- # @parameter policy [Hash] Header policy to use
339
- # @returns [Protocol::HTTP::Response]
340
- def self.build_trailers_only_response(status:, message: nil, policy: HEADER_POLICY)
341
- headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
342
- headers["content-type"] = "application/grpc+proto"
343
- add_status_header!(headers, status: status, message: message)
344
-
345
- Protocol::HTTP::Response[200, headers, nil]
306
+ # Add backtrace from error if available
307
+ if error && error.backtrace && !error.backtrace.empty?
308
+ headers["backtrace"] = error.backtrace
346
309
  end
347
310
  end
311
+ end
348
312
  end
349
313
  end
350
314
  ```
@@ -754,7 +718,7 @@ module Protocol
754
718
  # Find handler
755
719
  handler = @services[service_name]
756
720
  unless handler
757
- return trailers_only_error(Status::UNIMPLEMENTED, "Service not found: #{service_name}")
721
+ return make_response(Status::UNIMPLEMENTED, "Service not found: #{service_name}")
758
722
  end
759
723
 
760
724
  # Determine handler method and message classes
@@ -773,17 +737,17 @@ module Protocol
773
737
  end
774
738
 
775
739
  unless handler.respond_to?(handler_method)
776
- return trailers_only_error(Status::UNIMPLEMENTED, "Method not found: #{method_name}")
740
+ return make_response(Status::UNIMPLEMENTED, "Method not found: #{method_name}")
777
741
  end
778
742
 
779
743
  # Handle the RPC
780
- begin
781
- handle_rpc(request, handler, handler_method, request_class, response_class)
782
- rescue Error => e
783
- trailers_only_error(e.status_code, e.message)
784
- rescue => e
785
- trailers_only_error(Status::INTERNAL, e.message)
786
- end
744
+ begin
745
+ handle_rpc(request, handler, handler_method, request_class, response_class)
746
+ rescue Error => error
747
+ make_response(error.status_code, error.message, error: error)
748
+ rescue => error
749
+ make_response(Status::INTERNAL, error.message, error: error)
750
+ end
787
751
  end
788
752
 
789
753
  protected
@@ -811,20 +775,22 @@ module Protocol
811
775
  handler.send(method, input, output, call)
812
776
  output.close_write unless output.closed?
813
777
 
814
- # Mark trailers and add status
815
- response_headers.trailer!
816
- Metadata.add_status_trailer!(response_headers, status: Status::OK)
817
-
818
- Protocol::HTTP::Response[200, response_headers, output]
819
- end
778
+ # Mark trailers and add status
779
+ response_headers.trailer!
780
+ Metadata.add_status!(response_headers, status: Status::OK)
820
781
 
821
- def trailers_only_error(status_code, message)
822
- Metadata.build_trailers_only_response(
823
- status: status_code,
824
- message: message,
825
- policy: HEADER_POLICY
826
- )
827
- end
782
+ Protocol::HTTP::Response[200, response_headers, output]
783
+ end
784
+
785
+ protected
786
+
787
+ def make_response(status_code, message, error: nil)
788
+ headers = Protocol::HTTP::Headers.new([], nil, policy: HEADER_POLICY)
789
+ headers["content-type"] = "application/grpc+proto"
790
+ Metadata.add_status!(headers, status: status_code, message: message, error: error)
791
+
792
+ Protocol::HTTP::Response[200, headers, nil]
793
+ end
828
794
  end
829
795
  end
830
796
  end
@@ -953,7 +919,7 @@ def handle_grpc_request(http_request)
953
919
  # Add status as trailer - these will be sent after the response body
954
920
  # Note: The user just adds them to headers; the @tail marker ensures
955
921
  # they're recognized as trailers internally
956
- Protocol::GRPC::Metadata.add_status_trailer!(headers, status: Protocol::GRPC::Status::OK)
922
+ Protocol::GRPC::Metadata.add_status!(headers, status: Protocol::GRPC::Status::OK)
957
923
 
958
924
  Protocol::HTTP::Response[200, headers, output]
959
925
  end
@@ -15,6 +15,11 @@ module Protocol
15
15
  # This is the standard readable body for gRPC - all gRPC responses use message framing.
16
16
  # Wraps the underlying HTTP body and transforms raw chunks into decoded gRPC messages.
17
17
  class ReadableBody < Protocol::HTTP::Body::Wrapper
18
+ # Wrap the body of a message.
19
+ #
20
+ # @parameter message [Request | Response] The message to wrap.
21
+ # @parameter options [Hash] The options to pass to the initializer.
22
+ # @returns [ReadableBody | Nil] The wrapped body or `nil` if the message has no body.
18
23
  def self.wrap(message, **options)
19
24
  if body = message.body
20
25
  message.body = self.new(body, **options)
@@ -11,8 +11,8 @@ module Protocol
11
11
  # Can be used by both client stubs and server implementations.
12
12
  class Interface
13
13
  # RPC method definition
14
- RPC = Struct.new(:request_class, :response_class, :streaming, :method, keyword_init: true) do
15
- def initialize(request_class:, response_class:, streaming: :unary, method: nil)
14
+ RPC = Struct.new(:name, :request_class, :response_class, :streaming, :method, keyword_init: true) do
15
+ def initialize(name:, request_class:, response_class:, streaming: :unary, method: nil)
16
16
  super
17
17
  end
18
18
 
@@ -40,6 +40,8 @@ module Protocol
40
40
  # @parameter streaming [Symbol] Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional)
41
41
  # @parameter method [Symbol | Nil] Optional explicit Ruby method name (snake_case). If not provided, automatically converts PascalCase to snake_case.
42
42
  def self.rpc(name, **options)
43
+ options[:name] = name
44
+
43
45
  # Ensure snake_case method name is always available
44
46
  options[:method] ||= pascal_case_to_snake_case(name.to_s).to_sym
45
47
 
@@ -75,57 +75,23 @@ module Protocol
75
75
  end
76
76
  end
77
77
 
78
- # Build headers with gRPC status and message
79
- # @parameter status [Integer] gRPC status code
80
- # @parameter message [String | Nil] Optional status message
81
- # @parameter policy [Hash] Header policy to use
82
- # @returns [Protocol::HTTP::Headers]
83
- def self.build_status_headers(status: Status::OK, message: nil, policy: HEADER_POLICY)
84
- headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
85
- headers["grpc-status"] = Header::Status.new(status)
86
- headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
87
- headers
88
- end
89
-
90
- # Mark that trailers will follow (call after sending initial headers)
91
- # @parameter headers [Protocol::HTTP::Headers]
92
- # @returns [Protocol::HTTP::Headers]
93
- def self.prepare_trailers!(headers)
94
- headers.trailer!
95
- headers
96
- end
97
-
98
- # Add status as trailers to existing headers
99
- # @parameter headers [Protocol::HTTP::Headers]
100
- # @parameter status [Integer] gRPC status code
101
- # @parameter message [String | Nil] Optional status message
102
- def self.add_status_trailer!(headers, status: Status::OK, message: nil)
103
- headers.trailer! unless headers.trailer?
104
- headers["grpc-status"] = Header::Status.new(status)
105
- headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
106
- end
107
-
108
- # Add status as initial headers (for trailers-only responses)
78
+ # Add gRPC status, message, and optional backtrace to headers.
79
+ # Whether these become headers or trailers is controlled by the protocol layer.
109
80
  # @parameter headers [Protocol::HTTP::Headers]
110
81
  # @parameter status [Integer] gRPC status code
111
82
  # @parameter message [String | Nil] Optional status message
112
- def self.add_status_header!(headers, status: Status::OK, message: nil)
83
+ # @parameter error [Exception | Nil] Optional error object (used to extract backtrace)
84
+ def self.add_status!(headers, status: Status::OK, message: nil, error: nil)
113
85
  headers["grpc-status"] = Header::Status.new(status)
114
86
  headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
115
- end
116
-
117
- # Build a trailers-only error response (no body, status in headers)
118
- # @parameter status [Integer] gRPC status code
119
- # @parameter message [String | Nil] Optional status message
120
- # @parameter policy [Hash] Header policy to use
121
- # @returns [Protocol::HTTP::Response]
122
- def self.build_trailers_only_response(status:, message: nil, policy: HEADER_POLICY)
123
- headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
124
- headers["content-type"] = "application/grpc+proto"
125
- add_status_header!(headers, status: status, message: message)
126
87
 
127
- Protocol::HTTP::Response[200, headers, nil]
88
+ # Add backtrace from error if available
89
+ if error && error.backtrace && !error.backtrace.empty?
90
+ # Assign backtrace array directly - Split header will handle it
91
+ headers["backtrace"] = error.backtrace
92
+ end
128
93
  end
94
+
129
95
  end
130
96
  end
131
97
  end
@@ -30,9 +30,9 @@ module Protocol
30
30
  begin
31
31
  dispatch(request)
32
32
  rescue Error => error
33
- trailers_only_error(error.status_code, error.message)
33
+ make_response(error.status_code, error.message, error: error)
34
34
  rescue StandardError => error
35
- trailers_only_error(Status::INTERNAL, error.message)
35
+ make_response(Status::INTERNAL, error.message, error: error)
36
36
  end
37
37
  end
38
38
 
@@ -45,7 +45,7 @@ module Protocol
45
45
  raise NotImplementedError, "Subclasses must implement #dispatch"
46
46
  end
47
47
 
48
- protected
48
+ protected
49
49
 
50
50
  # Check if the request is a gRPC request.
51
51
  # @parameter request [Protocol::HTTP::Request]
@@ -55,16 +55,17 @@ module Protocol
55
55
  content_type&.start_with?("application/grpc")
56
56
  end
57
57
 
58
- # Build a trailers-only error response.
58
+ # Make a gRPC error response with status and optional message.
59
59
  # @parameter status_code [Integer] gRPC status code
60
60
  # @parameter message [String] Error message
61
+ # @parameter error [Exception] Optional error object (used to extract backtrace)
61
62
  # @returns [Protocol::HTTP::Response]
62
- def trailers_only_error(status_code, message)
63
- Metadata.build_trailers_only_response(
64
- status: status_code,
65
- message: message,
66
- policy: HEADER_POLICY
67
- )
63
+ def make_response(status_code, message, error: nil)
64
+ headers = Protocol::HTTP::Headers.new([], nil, policy: HEADER_POLICY)
65
+ headers["content-type"] = "application/grpc+proto"
66
+ Metadata.add_status!(headers, status: status_code, message: message, error: error)
67
+
68
+ Protocol::HTTP::Response[200, headers, nil]
68
69
  end
69
70
  end
70
71
  end
@@ -7,7 +7,7 @@
7
7
  module Protocol
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.3.0"
10
+ VERSION = "0.5.0"
11
11
  end
12
12
  end
13
13
 
data/readme.md CHANGED
@@ -28,6 +28,10 @@ Please see the [project documentation](https://socketry.github.io/protocol-grpc/
28
28
 
29
29
  Please see the [project releases](https://socketry.github.io/protocol-grpc/releases/index) for all releases.
30
30
 
31
+ ### v0.4.0
32
+
33
+ - Add `RPC#name`.
34
+
31
35
  ### v0.3.0
32
36
 
33
37
  - **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
data/releases.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Releases
2
2
 
3
+ ## v0.4.0
4
+
5
+ - Add `RPC#name`.
6
+
3
7
  ## v0.3.0
4
8
 
5
9
  - **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -86,14 +86,14 @@ dependencies:
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0.28'
89
+ version: '0.56'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0.28'
96
+ version: '0.56'
97
97
  executables: []
98
98
  extensions: []
99
99
  extra_rdoc_files: []
metadata.gz.sig CHANGED
Binary file