protocol-grpc 0.9.0 → 0.10.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: 7a4e884fa493dfbcc313a9c261b9d0cbeef027dc04b7ad99f031160f9231a8e5
4
- data.tar.gz: f0bb7c75757eef5ae129197304ebe1922b928348b65560512ef110586d49ba66
3
+ metadata.gz: 26f2d23d59b86e4dc0dda296d1ed3d455c52c9741ced1a1c62bd8b24c6d0398e
4
+ data.tar.gz: 4f4b655efdec5d5e1bde611e1904112706a3f1548ee4b5c90d8b24b5a5b2c882
5
5
  SHA512:
6
- metadata.gz: 1a9db6d101f6f41583d0b8c09fd00f7c4aa03de315e6a969484ae9c48b23a102b5c957bd47cd3c99d70c367e944d18f23e36c27436c8d8d897df7339d49bf9f7
7
- data.tar.gz: 90f1f8718e1b147cae4a6140aa2b391cb513abe89fa53708c22f5fa8f40724c36c61f35ad448e5ccbcf5437cb4e9bc4e4ee4b4276ea9f1e26399716d55c6fc5c
6
+ metadata.gz: 7f2e77bb2014620f522bf84704dceae110ea22f1c580c9e714d077a3296b1226360910dd5872a8aee6a0d45fb0b273c0a1e074a9c9c7c562f0df4619b50340b0
7
+ data.tar.gz: '039d6f8f513430285ef84722a177c91e501c91c6e10f843b87f2c10df1dbed69b8e4196d44cc73264e1e9b3e749e8c7c75ae6a4da00653973c013588fb0c1f2a'
checksums.yaml.gz.sig CHANGED
Binary file
data/design.md CHANGED
@@ -266,8 +266,14 @@ module Protocol
266
266
  # Custom header policy for gRPC
267
267
  # Extends Protocol::HTTP::Headers::POLICY with gRPC-specific headers
268
268
  HEADER_POLICY = Protocol::HTTP::Headers::POLICY.merge(
269
+ # Request headers:
270
+ "grpc-timeout" => Header::Timeout,
271
+ "grpc-encoding" => Header::Encoding,
272
+
273
+ # Response headers:
269
274
  "grpc-status" => Header::Status,
270
275
  "grpc-message" => Header::Message,
276
+
271
277
  # By default, all other headers follow standard HTTP policy
272
278
  # But gRPC allows most metadata to be sent as trailers
273
279
  ).freeze
@@ -108,9 +108,19 @@ module Protocol
108
108
  def decompress(data)
109
109
  case @encoding
110
110
  when "gzip"
111
- Zlib::Gunzip.new.inflate(data)
111
+ # Gzip format: zlib stream with gzip header (RFC 1952)
112
+ # Use MAX_WBITS + 32 to handle gzip header and CRC
113
+ inflater = Zlib::Inflate.new(Zlib::MAX_WBITS + 32)
114
+ result = inflater.inflate(data)
115
+ inflater.close
116
+ result
112
117
  when "deflate"
113
- Zlib::Inflate.inflate(data)
118
+ # Zlib format (RFC 1950) - default window bits handle zlib header
119
+ # This matches HTTP's "deflate" content-encoding
120
+ inflater = Zlib::Inflate.new
121
+ result = inflater.inflate(data)
122
+ inflater.close
123
+ result
114
124
  else
115
125
  data
116
126
  end
@@ -75,19 +75,26 @@ module Protocol
75
75
 
76
76
  protected
77
77
 
78
- # Compress data using the configured encoding.
78
+ # Compress data using the specified encoding.
79
+ # Per gRPC spec, compression is per-message and uses standard formats:
80
+ # - gzip: RFC 1952 (gzip format with headers and CRC)
81
+ # - deflate: RFC 1950 (zlib format, not raw deflate, for HTTP compatibility)
79
82
  # @parameter data [String] The data to compress
83
+ # @parameter encoding [String | Nil] The encoding to use. If `nil`, uses @encoding.
80
84
  # @returns [String] The compressed data
81
85
  # @raises [Error] If compression fails
82
86
  def compress(data)
83
87
  case @encoding
84
88
  when "gzip"
89
+ # Use GzipWriter for proper gzip format (includes headers, CRC)
85
90
  io = StringIO.new
86
91
  gz = Zlib::GzipWriter.new(io, @level)
87
92
  gz.write(data)
88
93
  gz.close
89
94
  io.string
90
95
  when "deflate"
96
+ # Use zlib format (RFC 1950) for HTTP compatibility
97
+ # This matches HTTP's "deflate" content-encoding
91
98
  Zlib::Deflate.deflate(data, @level)
92
99
  else
93
100
  data # No compression or identity
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ module Protocol
7
+ module GRPC
8
+ module Header
9
+ # The `grpc-encoding` header represents the message compression encoding.
10
+ #
11
+ # The `grpc-encoding` header specifies the compression algorithm used for the message payload.
12
+ # Common values include "identity" (no compression), "gzip", "deflate", etc.
13
+ # This header can appear in both request and response headers, but not in trailers.
14
+ class Encoding < String
15
+ # Parse an encoding from a header value.
16
+ #
17
+ # @parameter value [String] The header value to parse (e.g., "identity", "gzip").
18
+ # @returns [Encoding] A new Encoding instance.
19
+ def self.parse(value)
20
+ new(value)
21
+ end
22
+
23
+ # Coerce a value to an Encoding instance.
24
+ # Used by Protocol::HTTP::Headers when setting header values.
25
+ #
26
+ # @parameter value [Object] The value to coerce (will be converted to string).
27
+ # @returns [Encoding] A new Encoding instance.
28
+ def self.coerce(value)
29
+ new(value.to_s)
30
+ end
31
+
32
+ # Initialize the encoding header with the given value.
33
+ #
34
+ # @parameter value [String] The encoding value (e.g., "identity", "gzip").
35
+ def initialize(value)
36
+ super(value.to_s)
37
+ end
38
+
39
+ # Check if this encoding represents no compression (identity encoding).
40
+ #
41
+ # @returns [Boolean] `true` if the encoding is "identity" or empty.
42
+ def identity?
43
+ self == "identity" || self.empty?
44
+ end
45
+
46
+ # Merge another encoding value (takes the new value, as encoding should only appear once)
47
+ # @parameter value [String] The new encoding value
48
+ def <<(value)
49
+ replace(value.to_s)
50
+
51
+ return self
52
+ end
53
+
54
+ # Whether this header is acceptable in HTTP trailers.
55
+ # The `grpc-encoding` header does not appear in trailers.
56
+ # @returns [Boolean] `false`, as grpc-encoding cannot appear in trailers.
57
+ def self.trailer?
58
+ false
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require_relative "../methods"
7
+
8
+ module Protocol
9
+ module GRPC
10
+ module Header
11
+ # The `grpc-timeout` header represents the gRPC request timeout.
12
+ #
13
+ # The `grpc-timeout` header specifies how long the client is willing to wait for an RPC to complete.
14
+ # The format is: value + unit (H=hours, M=minutes, S=seconds, m=milliseconds, u=microseconds, n=nanoseconds).
15
+ # This header appears only in request headers, not in trailers.
16
+ class Timeout < String
17
+ # Parse a timeout from a header value.
18
+ #
19
+ # @parameter value [String] The header value to parse (e.g., "5S", "1000m").
20
+ # @returns [Timeout] A new Timeout instance.
21
+ def self.parse(value)
22
+ new(value)
23
+ end
24
+
25
+ # Coerce a value to a Timeout instance.
26
+ #
27
+ # If a Numeric is provided, it will be formatted as a gRPC timeout string using {Protocol::GRPC::Methods.format_timeout}.
28
+ #
29
+ # @parameter value [String | Numeric] The value to coerce.
30
+ # @returns [Timeout] A new Timeout instance.
31
+ def self.coerce(value)
32
+ if value.is_a?(Numeric)
33
+ return new(Protocol::GRPC::Methods.format_timeout(value))
34
+ else
35
+ return new(value.to_s)
36
+ end
37
+ end
38
+
39
+ # Initialize the timeout header with the given value.
40
+ #
41
+ # @parameter value [String] The timeout value in gRPC format.
42
+ def initialize(value)
43
+ super(value.to_s)
44
+ end
45
+
46
+ # Parse the timeout value to seconds.
47
+ #
48
+ # @returns [Numeric | Nil] Timeout in seconds, or `Nil` if value is invalid.
49
+ def to_seconds
50
+ Protocol::GRPC::Methods.parse_timeout(self)
51
+ end
52
+
53
+ # Merge another timeout value (takes the new value, as timeout should only appear once)
54
+ # @parameter value [String] The new timeout value
55
+ def <<(value)
56
+ replace(value.to_s)
57
+
58
+ return self
59
+ end
60
+
61
+ # Whether this header is acceptable in HTTP trailers.
62
+ # The `grpc-timeout` header is request-only and does not appear in trailers.
63
+ # @returns [Boolean] `false`, as grpc-timeout cannot appear in trailers.
64
+ def self.trailer?
65
+ false
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -8,6 +8,8 @@ require "protocol/http"
8
8
  require_relative "status"
9
9
  require_relative "header/status"
10
10
  require_relative "header/message"
11
+ require_relative "header/timeout"
12
+ require_relative "header/encoding"
11
13
 
12
14
  module Protocol
13
15
  module GRPC
@@ -18,6 +20,11 @@ module Protocol
18
20
  # Custom header policy for gRPC.
19
21
  # Extends Protocol::HTTP::Headers::POLICY with gRPC-specific headers.
20
22
  HEADER_POLICY = Protocol::HTTP::Headers::POLICY.merge(
23
+ # Request headers:
24
+ "grpc-timeout" => Header::Timeout,
25
+ "grpc-encoding" => Header::Encoding,
26
+
27
+ # Response headers:
21
28
  "grpc-status" => Header::Status,
22
29
  "grpc-message" => Header::Message
23
30
  # By default, all other headers follow standard HTTP policy, but gRPC allows most metadata to be sent as trailers.
@@ -32,10 +32,14 @@ module Protocol
32
32
  # @parameter content_type [String] Content type (default: "application/grpc+proto")
33
33
  # @returns [Protocol::HTTP::Headers]
34
34
  def self.build_headers(metadata: {}, timeout: nil, content_type: "application/grpc+proto")
35
- headers = Protocol::HTTP::Headers.new
35
+ headers = Protocol::HTTP::Headers.new(policy: Protocol::GRPC::HEADER_POLICY)
36
36
  headers["content-type"] = content_type
37
37
  headers["te"] = "trailers"
38
- headers["grpc-timeout"] = format_timeout(timeout) if timeout
38
+
39
+ if timeout
40
+ # Coerced to proper format by header policy:
41
+ headers["grpc-timeout"] = timeout
42
+ end
39
43
 
40
44
  metadata.each do |key, value|
41
45
  # Binary headers end with -bin and are base64 encoded:
@@ -27,6 +27,9 @@ module Protocol
27
27
  def call(request)
28
28
  return super unless grpc_request?(request)
29
29
 
30
+ # Ensure the request headers are using the gRPC header policy:
31
+ request.headers.policy = Protocol::GRPC::HEADER_POLICY
32
+
30
33
  begin
31
34
  dispatch(request)
32
35
  rescue Error => error
@@ -7,7 +7,7 @@
7
7
  module Protocol
8
8
  # @namespace
9
9
  module GRPC
10
- VERSION = "0.9.0"
10
+ VERSION = "0.10.0"
11
11
  end
12
12
  end
13
13
 
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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
@@ -109,8 +109,10 @@ files:
109
109
  - lib/protocol/grpc/call.rb
110
110
  - lib/protocol/grpc/error.rb
111
111
  - lib/protocol/grpc/header.rb
112
+ - lib/protocol/grpc/header/encoding.rb
112
113
  - lib/protocol/grpc/header/message.rb
113
114
  - lib/protocol/grpc/header/status.rb
115
+ - lib/protocol/grpc/header/timeout.rb
114
116
  - lib/protocol/grpc/health_check.rb
115
117
  - lib/protocol/grpc/interface.rb
116
118
  - lib/protocol/grpc/metadata.rb
metadata.gz.sig CHANGED
Binary file