protocol-grpc 0.2.0 → 0.4.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: 1918529ed154432a768c783d4677b6912017fb3acd4054fd392c79fbdc97c9b3
4
- data.tar.gz: f076c43f3f1a6e429f0b057eb5ce512f4405fb350b88dfbfdddf2a476ccbf949
3
+ metadata.gz: 301c1d74991d960be7af4b50850ffa24aab9e4c2c7aedc97759518027aacfe18
4
+ data.tar.gz: ac2fe4b32aa211ef8dbf4329a8677ae09deced47ab21816c0b6fb361d346dcd2
5
5
  SHA512:
6
- metadata.gz: 593456adb014c6caa239365a9652253838d7f0930fd2d898c052914a8535a3e34aa40905f951e01d7e2c2b77d27e49c1445f113b3b88f53d54e42fd572dc4501
7
- data.tar.gz: b14442b758e562affbc13011a2e5d982d40d0cd742d844921fb8e5efe63cecc5fa7fc633656f8fd7ef13b0f1acde18ceea9f0864aa8adf3bf571ee07558a17db
6
+ metadata.gz: 2467aaa36d36c6d011ee4913984defc1974d008bf25081777863d83b4c3c7c7825556ae571a99cc74858a7eece0c99cf253953230bbb7220244082a2c32bd95f
7
+ data.tar.gz: 222fe2052157790f288d72f6f6feff1ad42d507ab188e2bce23cbf0f2e9fa63d7eb0966f774935e5ae0ad3e5a497170f8b126f9dfa4d10ccf53b7f385d7b8437
checksums.yaml.gz.sig ADDED
@@ -0,0 +1,4 @@
1
+ P��*/�Z�Y�6��ox����p�y�9[��?G���V��seW 7�<�5�g��r�F��)�XX.z 4�
2
+ ��f3
3
+ /b�\B�e�w�P�!�E���g���
4
+ mh4���Uhbr�%ё�>jͪ1��[���_�,�Fȃ��{�t_z� c��m�����u�͈��'�3` �Q4����:��=������1�!B���P��Cٝ�4�j��.’��H��?�on��ђ��rrW�L
@@ -129,4 +129,3 @@ call.deadline.exceeded? # => false
129
129
  # Access peer information
130
130
  call.peer # => Protocol::HTTP::Address
131
131
  ```
132
-
data/design.md CHANGED
@@ -779,10 +779,10 @@ module Protocol
779
779
  # Handle the RPC
780
780
  begin
781
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)
782
+ rescue Error => error
783
+ trailers_only_error(e.status_code, error.message)
784
+ rescue => error
785
+ trailers_only_error(Status::INTERNAL, error.message)
786
786
  end
787
787
  end
788
788
 
@@ -4,6 +4,7 @@
4
4
  # Copyright, 2025, by Samuel Williams.
5
5
 
6
6
  require "protocol/http"
7
+ require "protocol/http/body/wrapper"
7
8
  require "zlib"
8
9
 
9
10
  module Protocol
@@ -12,56 +13,41 @@ module Protocol
12
13
  module Body
13
14
  # Represents a readable body for gRPC messages with length-prefixed framing.
14
15
  # This is the standard readable body for gRPC - all gRPC responses use message framing.
15
- class ReadableBody
16
+ # Wraps the underlying HTTP body and transforms raw chunks into decoded gRPC messages.
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.
23
+ def self.wrap(message, **options)
24
+ if body = message.body
25
+ message.body = self.new(body, **options)
26
+ end
27
+
28
+ return message.body
29
+ end
30
+
16
31
  # Initialize a new readable body for gRPC messages.
17
32
  # @parameter body [Protocol::HTTP::Body::Readable] The underlying HTTP body
18
33
  # @parameter message_class [Class | Nil] Protobuf message class with .decode method.
19
34
  # If `nil`, returns raw binary data (useful for channel adapters)
20
35
  # @parameter encoding [String | Nil] Compression encoding (from grpc-encoding header)
21
36
  def initialize(body, message_class: nil, encoding: nil)
22
- @body = body
37
+ super(body)
23
38
  @message_class = message_class
24
39
  @encoding = encoding
25
40
  @buffer = String.new.force_encoding(Encoding::BINARY)
26
- @closed = false
27
41
  end
28
42
 
29
- # @attribute [Protocol::HTTP::Body::Readable] The underlying HTTP body.
30
- attr_reader :body
31
-
32
43
  # @attribute [String | Nil] The compression encoding.
33
44
  attr_reader :encoding
34
45
 
35
- # Close the input body.
36
- # @parameter error [Exception | Nil] Optional error that caused the close
37
- # @returns [Nil]
38
- def close(error = nil)
39
- @closed = true
40
-
41
- if @body
42
- @body.close(error)
43
- @body = nil
44
- end
45
-
46
- nil
47
- end
48
-
49
- # Check if the stream has been closed.
50
- # @returns [Boolean] `true` if the stream is closed, `false` otherwise
51
- def closed?
52
- @closed or @body.nil?
53
- end
54
-
55
- # Check if there are any input chunks remaining.
56
- # @returns [Boolean] `true` if the body is empty, `false` otherwise
57
- def empty?
58
- @body.nil?
59
- end
60
-
61
46
  # Read the next gRPC message.
47
+ # Overrides Wrapper#read to transform raw HTTP body chunks into decoded gRPC messages.
62
48
  # @returns [Object | String | Nil] Decoded message, raw binary, or `Nil` if stream ended
63
49
  def read
64
- return nil if closed?
50
+ return nil if @body.nil? || @body.empty?
65
51
 
66
52
  # Read 5-byte prefix: 1 byte compression flag + 4 bytes length
67
53
  prefix = read_exactly(5)
@@ -87,24 +73,6 @@ module Protocol
87
73
  end
88
74
  end
89
75
 
90
- # Enumerate all messages until finished, then invoke {close}.
91
- # @yields {|message| ...} The block to call with each message.
92
- def each
93
- return to_enum unless block_given?
94
-
95
- error = nil
96
- begin
97
- while (message = read)
98
- yield message
99
- end
100
- rescue StandardError => e
101
- error = e
102
- raise
103
- ensure
104
- close(error)
105
- end
106
- end
107
-
108
76
  private
109
77
 
110
78
  # Read exactly n bytes from the underlying body.
@@ -113,28 +81,18 @@ module Protocol
113
81
  def read_exactly(n)
114
82
  # Fill buffer until we have enough data:
115
83
  while @buffer.bytesize < n
116
- return nil if closed?
84
+ return nil if @body.nil? || @body.empty?
117
85
 
118
86
  # Read chunk from underlying body:
119
87
  chunk = @body.read
120
88
 
121
89
  if chunk.nil?
122
90
  # End of stream:
123
- if @body && !@closed
124
- @body.close
125
- @closed = true
126
- end
127
91
  return nil
128
92
  end
129
93
 
130
94
  # Append to buffer:
131
95
  @buffer << chunk.force_encoding(Encoding::BINARY)
132
-
133
- # Check if body is empty and close if needed:
134
- if @body.empty?
135
- @body.close
136
- @closed = true
137
- end
138
96
  end
139
97
 
140
98
  # Extract the required data:
@@ -12,9 +12,11 @@ module Protocol
12
12
  class Call
13
13
  # Initialize a new RPC call context.
14
14
  # @parameter request [Protocol::HTTP::Request] The HTTP request
15
+ # @parameter response [Protocol::HTTP::Response | Nil] The HTTP response (for setting metadata and trailers)
15
16
  # @parameter deadline [Async::Deadline | Nil] Deadline for the call
16
- def initialize(request, deadline: nil)
17
+ def initialize(request, response = nil, deadline: nil)
17
18
  @request = request
19
+ @response = response
18
20
  @deadline = deadline
19
21
  @cancelled = false
20
22
  end
@@ -22,6 +24,9 @@ module Protocol
22
24
  # @attribute [Protocol::HTTP::Request] The underlying HTTP request.
23
25
  attr_reader :request
24
26
 
27
+ # @attribute [Protocol::HTTP::Response | Nil] The HTTP response.
28
+ attr_reader :response
29
+
25
30
  # @attribute [Async::Deadline | Nil] The deadline for this call.
26
31
  attr_reader :deadline
27
32
 
@@ -20,9 +20,9 @@ module Protocol
20
20
  class Status
21
21
  # Initialize the status header with the given value.
22
22
  #
23
- # @parameter value [String, Integer] The status code as a string or integer.
23
+ # @parameter value [String, Integer, Array] The status code as a string, integer, or array (takes first element).
24
24
  def initialize(value)
25
- @value = value.is_a?(String) ? value.to_i : value.to_i
25
+ @value = normalize_value(value)
26
26
  end
27
27
 
28
28
  # Get the status code as an integer.
@@ -40,12 +40,24 @@ module Protocol
40
40
  end
41
41
 
42
42
  # Merge another status value (takes the new value, as status should only appear once)
43
- # @parameter value [String, Integer] The new status code
43
+ # @parameter value [String, Integer, Array] The new status code
44
44
  def <<(value)
45
- @value = value.is_a?(String) ? value.to_i : value.to_i
45
+ @value = normalize_value(value)
46
46
  self
47
47
  end
48
48
 
49
+ private
50
+
51
+ # Normalize a value to an integer status code.
52
+ # Handles arrays (from external clients), strings, and integers.
53
+ # @parameter value [String, Integer, Array] The raw value
54
+ # @returns [Integer] The normalized status code
55
+ def normalize_value(value)
56
+ # Handle Array case (may occur with external clients)
57
+ actual_value = value.is_a?(Array) ? value.flatten.compact.first : value
58
+ actual_value.to_i
59
+ end
60
+
49
61
  # Whether this header is acceptable in HTTP trailers.
50
62
  # The `grpc-status` header can appear in trailers as per the gRPC specification.
51
63
  # @returns [Boolean] `true`, as grpc-status can appear in trailers.
@@ -11,10 +11,17 @@ 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
+
19
+ # Check if this RPC is a streaming RPC (server, client, or bidirectional).
20
+ # Server-side handlers for streaming RPCs are expected to block until all messages are sent.
21
+ # @returns [Boolean] `true` if streaming, `false` if unary
22
+ def streaming?
23
+ streaming != :unary
24
+ end
18
25
  end
19
26
 
20
27
  # Hook called when a subclass is created.
@@ -33,6 +40,8 @@ module Protocol
33
40
  # @parameter streaming [Symbol] Streaming type (:unary, :server_streaming, :client_streaming, :bidirectional)
34
41
  # @parameter method [Symbol | Nil] Optional explicit Ruby method name (snake_case). If not provided, automatically converts PascalCase to snake_case.
35
42
  def self.rpc(name, **options)
43
+ options[:name] = name
44
+
36
45
  # Ensure snake_case method name is always available
37
46
  options[:method] ||= pascal_case_to_snake_case(name.to_s).to_sym
38
47
 
@@ -104,4 +113,4 @@ module Protocol
104
113
  end
105
114
  end
106
115
  end
107
- end
116
+ end
@@ -33,8 +33,22 @@ module Protocol
33
33
  status.to_i
34
34
  else
35
35
  # Fallback for when header policy isn't used
36
- status_value = status.is_a?(Array) ? status.first : status.to_s
37
- status_value.to_i
36
+ # Handle Array case (may occur with external clients)
37
+ status_value = if status.is_a?(Array)
38
+ # Flatten and take first non-nil value, recursively handle nested arrays
39
+ flattened = status.flatten.compact.first
40
+ # If still an array, take first element
41
+ flattened.is_a?(Array) ? flattened.first : flattened
42
+ else
43
+ status
44
+ end
45
+
46
+ # Convert to string then integer to handle various types
47
+ # Handle case where status_value might still be an array somehow
48
+ if status_value.is_a?(Array)
49
+ status_value = status_value.first
50
+ end
51
+ status_value.to_s.to_i
38
52
  end
39
53
  end
40
54
 
@@ -7,6 +7,6 @@
7
7
  module Protocol
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.2.0"
10
+ VERSION = "0.4.0"
11
11
  end
12
12
  end
data/readme.md CHANGED
@@ -28,9 +28,19 @@ 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
+
35
+ ### v0.3.0
36
+
37
+ - **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
38
+ - **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
39
+ - Added `RPC#streaming?` method to check if an RPC is streaming.
40
+
31
41
  ### v0.2.0
32
42
 
33
- - `RCP#method` is always defined (snake case).
43
+ - `RPC#method` is always defined (snake case).
34
44
 
35
45
  ### v0.1.0
36
46
 
data/releases.md CHANGED
@@ -1,8 +1,18 @@
1
1
  # Releases
2
2
 
3
+ ## v0.4.0
4
+
5
+ - Add `RPC#name`.
6
+
7
+ ## v0.3.0
8
+
9
+ - **Breaking**: `Protocol::GRPC::Call` now takes a `response` object parameter instead of separate `response_headers`.
10
+ - **Breaking**: Removed `Call#response_headers` method. Use `call.response.headers` directly.
11
+ - Added `RPC#streaming?` method to check if an RPC is streaming.
12
+
3
13
  ## v0.2.0
4
14
 
5
- - `RCP#method` is always defined (snake case).
15
+ - `RPC#method` is always defined (snake case).
6
16
 
7
17
  ## v0.1.0
8
18
 
data.tar.gz.sig ADDED
Binary file
metadata CHANGED
@@ -1,12 +1,41 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protocol-grpc
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  bindir: bin
9
- cert_chain: []
9
+ cert_chain:
10
+ - |
11
+ -----BEGIN CERTIFICATE-----
12
+ MIIE2DCCA0CgAwIBAgIBATANBgkqhkiG9w0BAQsFADBhMRgwFgYDVQQDDA9zYW11
13
+ ZWwud2lsbGlhbXMxHTAbBgoJkiaJk/IsZAEZFg1vcmlvbnRyYW5zZmVyMRIwEAYK
14
+ CZImiZPyLGQBGRYCY28xEjAQBgoJkiaJk/IsZAEZFgJuejAeFw0yMjA4MDYwNDUz
15
+ MjRaFw0zMjA4MDMwNDUzMjRaMGExGDAWBgNVBAMMD3NhbXVlbC53aWxsaWFtczEd
16
+ MBsGCgmSJomT8ixkARkWDW9yaW9udHJhbnNmZXIxEjAQBgoJkiaJk/IsZAEZFgJj
17
+ bzESMBAGCgmSJomT8ixkARkWAm56MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIB
18
+ igKCAYEAomvSopQXQ24+9DBB6I6jxRI2auu3VVb4nOjmmHq7XWM4u3HL+pni63X2
19
+ 9qZdoq9xt7H+RPbwL28LDpDNflYQXoOhoVhQ37Pjn9YDjl8/4/9xa9+NUpl9XDIW
20
+ sGkaOY0eqsQm1pEWkHJr3zn/fxoKPZPfaJOglovdxf7dgsHz67Xgd/ka+Wo1YqoE
21
+ e5AUKRwUuvaUaumAKgPH+4E4oiLXI4T1Ff5Q7xxv6yXvHuYtlMHhYfgNn8iiW8WN
22
+ XibYXPNP7NtieSQqwR/xM6IRSoyXKuS+ZNGDPUUGk8RoiV/xvVN4LrVm9upSc0ss
23
+ RZ6qwOQmXCo/lLcDUxJAgG95cPw//sI00tZan75VgsGzSWAOdjQpFM0l4dxvKwHn
24
+ tUeT3ZsAgt0JnGqNm2Bkz81kG4A2hSyFZTFA8vZGhp+hz+8Q573tAR89y9YJBdYM
25
+ zp0FM4zwMNEUwgfRzv1tEVVUEXmoFCyhzonUUw4nE4CFu/sE3ffhjKcXcY//qiSW
26
+ xm4erY3XAgMBAAGjgZowgZcwCQYDVR0TBAIwADALBgNVHQ8EBAMCBLAwHQYDVR0O
27
+ BBYEFO9t7XWuFf2SKLmuijgqR4sGDlRsMC4GA1UdEQQnMCWBI3NhbXVlbC53aWxs
28
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MC4GA1UdEgQnMCWBI3NhbXVlbC53aWxs
29
+ aWFtc0BvcmlvbnRyYW5zZmVyLmNvLm56MA0GCSqGSIb3DQEBCwUAA4IBgQB5sxkE
30
+ cBsSYwK6fYpM+hA5B5yZY2+L0Z+27jF1pWGgbhPH8/FjjBLVn+VFok3CDpRqwXCl
31
+ xCO40JEkKdznNy2avOMra6PFiQyOE74kCtv7P+Fdc+FhgqI5lMon6tt9rNeXmnW/
32
+ c1NaMRdxy999hmRGzUSFjozcCwxpy/LwabxtdXwXgSay4mQ32EDjqR1TixS1+smp
33
+ 8C/NCWgpIfzpHGJsjvmH2wAfKtTTqB9CVKLCWEnCHyCaRVuKkrKjqhYCdmMBqCws
34
+ JkxfQWC+jBVeG9ZtPhQgZpfhvh+6hMhraUYRQ6XGyvBqEUe+yo6DKIT3MtGE2+CP
35
+ eX9i9ZWBydWb8/rvmwmX2kkcBbX0hZS1rcR593hGc61JR6lvkGYQ2MYskBveyaxt
36
+ Q2K9NVun/S785AP05vKkXZEFYxqG6EW012U4oLcFl5MySFajYXRYbuUpH6AY+HP8
37
+ voD0MPg1DssDLKwXyt1eKD/+Fq0bFWhwVM/1XiAXL7lyYUyOq24KHgQ2Csg=
38
+ -----END CERTIFICATE-----
10
39
  date: 1980-01-02 00:00:00.000000000 Z
11
40
  dependencies:
12
41
  - !ruby/object:Gem::Dependency
metadata.gz.sig ADDED
Binary file