protocol-grpc 0.1.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.
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "uri"
7
+ require_relative "header"
8
+ require_relative "status"
9
+
10
+ module Protocol
11
+ module GRPC
12
+ # @namespace
13
+ module Metadata
14
+ # Extract gRPC status from headers.
15
+ # Convenience method that handles both Header::Status instances and raw values.
16
+ # Returns Status::UNKNOWN if status is not present.
17
+ #
18
+ # Note: In Protocol::HTTP::Headers, trailers are merged into the headers
19
+ # so users just access headers["grpc-status"] regardless of whether it
20
+ # was sent as an initial header or trailer.
21
+ #
22
+ # @parameter headers [Protocol::HTTP::Headers]
23
+ # @returns [Integer] Status code (0-16)
24
+ def self.extract_status(headers)
25
+ # Ensure policy is set - setting policy clears the index (@indexed = nil)
26
+ # The index will be rebuilt automatically on next access via to_h
27
+ headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
28
+
29
+ status = headers["grpc-status"]
30
+ return Status::UNKNOWN unless status
31
+
32
+ if status.is_a?(Header::Status)
33
+ status.to_i
34
+ else
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
38
+ end
39
+ end
40
+
41
+ # Extract gRPC status message from headers.
42
+ # Convenience method that handles both Header::Message instances and raw values.
43
+ # Returns `Nil` if message is not present.
44
+ #
45
+ # @parameter headers [Protocol::HTTP::Headers]
46
+ # @returns [String | Nil] Status message
47
+ def self.extract_message(headers)
48
+ # Ensure policy is set - setting policy clears the index (@indexed = nil)
49
+ # The index will be rebuilt automatically on next access via to_h
50
+ headers.policy = Protocol::GRPC::HEADER_POLICY unless headers.policy == Protocol::GRPC::HEADER_POLICY
51
+
52
+ message = headers["grpc-message"]
53
+ return nil unless message
54
+
55
+ if message.is_a?(Header::Message)
56
+ message.decode
57
+ else
58
+ # Fallback for when header policy isn't used
59
+ message_value = message.is_a?(Array) ? message.first : message.to_s
60
+ URI.decode_www_form_component(message_value)
61
+ end
62
+ end
63
+
64
+ # Build headers with gRPC status and message
65
+ # @parameter status [Integer] gRPC status code
66
+ # @parameter message [String | Nil] Optional status message
67
+ # @parameter policy [Hash] Header policy to use
68
+ # @returns [Protocol::HTTP::Headers]
69
+ def self.build_status_headers(status: Status::OK, message: nil, policy: HEADER_POLICY)
70
+ headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
71
+ headers["grpc-status"] = Header::Status.new(status)
72
+ headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
73
+ headers
74
+ end
75
+
76
+ # Mark that trailers will follow (call after sending initial headers)
77
+ # @parameter headers [Protocol::HTTP::Headers]
78
+ # @returns [Protocol::HTTP::Headers]
79
+ def self.prepare_trailers!(headers)
80
+ headers.trailer!
81
+ headers
82
+ end
83
+
84
+ # Add status as trailers to existing headers
85
+ # @parameter headers [Protocol::HTTP::Headers]
86
+ # @parameter status [Integer] gRPC status code
87
+ # @parameter message [String | Nil] Optional status message
88
+ def self.add_status_trailer!(headers, status: Status::OK, message: nil)
89
+ headers.trailer! unless headers.trailer?
90
+ headers["grpc-status"] = Header::Status.new(status)
91
+ headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
92
+ end
93
+
94
+ # Add status as initial headers (for trailers-only responses)
95
+ # @parameter headers [Protocol::HTTP::Headers]
96
+ # @parameter status [Integer] gRPC status code
97
+ # @parameter message [String | Nil] Optional status message
98
+ def self.add_status_header!(headers, status: Status::OK, message: nil)
99
+ headers["grpc-status"] = Header::Status.new(status)
100
+ headers["grpc-message"] = Header::Message.new(Header::Message.encode(message)) if message
101
+ end
102
+
103
+ # Build a trailers-only error response (no body, status in headers)
104
+ # @parameter status [Integer] gRPC status code
105
+ # @parameter message [String | Nil] Optional status message
106
+ # @parameter policy [Hash] Header policy to use
107
+ # @returns [Protocol::HTTP::Response]
108
+ def self.build_trailers_only_response(status:, message: nil, policy: HEADER_POLICY)
109
+ headers = Protocol::HTTP::Headers.new([], nil, policy: policy)
110
+ headers["content-type"] = "application/grpc+proto"
111
+ add_status_header!(headers, status: status, message: message)
112
+
113
+ Protocol::HTTP::Response[200, headers, nil]
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "base64"
7
+ require "protocol/http"
8
+
9
+ module Protocol
10
+ module GRPC
11
+ # Provides utility methods for building and parsing gRPC-compatible HTTP requests.
12
+ module Methods
13
+ # Build gRPC path from service and method.
14
+ # @parameter service [String] e.g., "my_service.Greeter"
15
+ # @parameter method [String] e.g., "SayHello"
16
+ # @returns [String] e.g., "/my_service.Greeter/SayHello"
17
+ def self.build_path(service, method)
18
+ "/#{service}/#{method}"
19
+ end
20
+
21
+ # Parse service and method from gRPC path.
22
+ # @parameter path [String] e.g., "/my_service.Greeter/SayHello"
23
+ # @returns [Array(String, String)] [service, method]
24
+ def self.parse_path(path)
25
+ parts = path.split("/")
26
+ [parts[1], parts[2]]
27
+ end
28
+
29
+ # Build gRPC request headers.
30
+ # @parameter metadata [Hash] Custom metadata key-value pairs
31
+ # @parameter timeout [Numeric | Nil] Optional timeout in seconds
32
+ # @parameter content_type [String] Content type (default: "application/grpc+proto")
33
+ # @returns [Protocol::HTTP::Headers]
34
+ def self.build_headers(metadata: {}, timeout: nil, content_type: "application/grpc+proto")
35
+ headers = Protocol::HTTP::Headers.new
36
+ headers["content-type"] = content_type
37
+ headers["te"] = "trailers"
38
+ headers["grpc-timeout"] = format_timeout(timeout) if timeout
39
+
40
+ metadata.each do |key, value|
41
+ # Binary headers end with -bin and are base64 encoded:
42
+ headers[key] = if key.end_with?("-bin")
43
+ Base64.strict_encode64(value)
44
+ else
45
+ value.to_s
46
+ end
47
+ end
48
+
49
+ headers
50
+ end
51
+
52
+ # Extract metadata from gRPC headers.
53
+ # @parameter headers [Protocol::HTTP::Headers]
54
+ # @returns [Hash] Metadata key-value pairs
55
+ def self.extract_metadata(headers)
56
+ metadata = {}
57
+
58
+ headers.each do |key, value|
59
+ # Skip reserved headers:
60
+ next if key.start_with?("grpc-") || key == "content-type" || key == "te"
61
+
62
+ # Decode binary headers:
63
+ metadata[key] = if key.end_with?("-bin")
64
+ Base64.strict_decode64(value)
65
+ else
66
+ value
67
+ end
68
+ end
69
+
70
+ metadata
71
+ end
72
+
73
+ # Format timeout for grpc-timeout header.
74
+ # @parameter timeout [Numeric] Timeout in seconds
75
+ # @returns [String] e.g., "1000m" for 1 second
76
+ def self.format_timeout(timeout)
77
+ # gRPC timeout format: value + unit (H=hours, M=minutes, S=seconds, m=milliseconds, u=microseconds, n=nanoseconds)
78
+ if timeout >= 3600
79
+ "#{(timeout / 3600).to_i}H"
80
+ elsif timeout >= 60
81
+ "#{(timeout / 60).to_i}M"
82
+ elsif timeout >= 1
83
+ "#{timeout.to_i}S"
84
+ elsif timeout >= 0.001
85
+ "#{(timeout * 1000).to_i}m"
86
+ elsif timeout >= 0.000001
87
+ "#{(timeout * 1_000_000).to_i}u"
88
+ else
89
+ "#{(timeout * 1_000_000_000).to_i}n"
90
+ end
91
+ end
92
+
93
+ # Parse grpc-timeout header value.
94
+ # @parameter value [String] e.g., "1000m"
95
+ # @returns [Numeric | Nil] Timeout in seconds, or `Nil` if value is invalid
96
+ def self.parse_timeout(value)
97
+ return nil unless value
98
+
99
+ amount = value[0...-1].to_i
100
+ unit = value[-1]
101
+
102
+ case unit
103
+ when "H" then amount * 3600
104
+ when "M" then amount * 60
105
+ when "S" then amount
106
+ when "m" then amount / 1000.0
107
+ when "u" then amount / 1_000_000.0
108
+ when "n" then amount / 1_000_000_000.0
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require "protocol/http"
7
+ require_relative "error"
8
+ require_relative "status"
9
+ require_relative "metadata"
10
+ require_relative "header"
11
+
12
+ module Protocol
13
+ module GRPC
14
+ # Represents server middleware for handling gRPC requests.
15
+ # Implements Protocol::HTTP::Middleware interface.
16
+ # Subclasses should implement {dispatch} to handle service routing and protocol details.
17
+ class Middleware < Protocol::HTTP::Middleware
18
+ # Initialize a new middleware instance.
19
+ # @parameter app [#call | Nil] The next middleware in the chain
20
+ def initialize(app = nil)
21
+ super(app)
22
+ end
23
+
24
+ # Handle incoming HTTP request.
25
+ # @parameter request [Protocol::HTTP::Request]
26
+ # @returns [Protocol::HTTP::Response]
27
+ def call(request)
28
+ return super unless grpc_request?(request)
29
+
30
+ begin
31
+ dispatch(request)
32
+ rescue Error => error
33
+ trailers_only_error(error.status_code, error.message)
34
+ rescue StandardError => error
35
+ trailers_only_error(Status::INTERNAL, error.message)
36
+ end
37
+ end
38
+
39
+ # Dispatch the request to the service handler.
40
+ # Subclasses should implement this method to handle routing and protocol details.
41
+ # @parameter request [Protocol::HTTP::Request]
42
+ # @returns [Protocol::HTTP::Response]
43
+ # @raises [NotImplementedError] If not implemented by subclass
44
+ def dispatch(request)
45
+ raise NotImplementedError, "Subclasses must implement #dispatch"
46
+ end
47
+
48
+ protected
49
+
50
+ # Check if the request is a gRPC request.
51
+ # @parameter request [Protocol::HTTP::Request]
52
+ # @returns [Boolean] `true` if the request is a gRPC request, `false` otherwise
53
+ def grpc_request?(request)
54
+ content_type = request.headers["content-type"]
55
+ content_type&.start_with?("application/grpc")
56
+ end
57
+
58
+ # Build a trailers-only error response.
59
+ # @parameter status_code [Integer] gRPC status code
60
+ # @parameter message [String] Error message
61
+ # @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
+ )
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ module Protocol
7
+ module GRPC
8
+ # Provides gRPC status codes and their descriptions.
9
+ module Status
10
+ OK = 0
11
+ CANCELLED = 1
12
+ UNKNOWN = 2
13
+ INVALID_ARGUMENT = 3
14
+ DEADLINE_EXCEEDED = 4
15
+ NOT_FOUND = 5
16
+ ALREADY_EXISTS = 6
17
+ PERMISSION_DENIED = 7
18
+ RESOURCE_EXHAUSTED = 8
19
+ FAILED_PRECONDITION = 9
20
+ ABORTED = 10
21
+ OUT_OF_RANGE = 11
22
+ UNIMPLEMENTED = 12
23
+ INTERNAL = 13
24
+ UNAVAILABLE = 14
25
+ DATA_LOSS = 15
26
+ UNAUTHENTICATED = 16
27
+
28
+ # Status code descriptions
29
+ DESCRIPTIONS = {
30
+ OK => "OK",
31
+ CANCELLED => "Cancelled",
32
+ UNKNOWN => "Unknown",
33
+ INVALID_ARGUMENT => "Invalid Argument",
34
+ DEADLINE_EXCEEDED => "Deadline Exceeded",
35
+ NOT_FOUND => "Not Found",
36
+ ALREADY_EXISTS => "Already Exists",
37
+ PERMISSION_DENIED => "Permission Denied",
38
+ RESOURCE_EXHAUSTED => "Resource Exhausted",
39
+ FAILED_PRECONDITION => "Failed Precondition",
40
+ ABORTED => "Aborted",
41
+ OUT_OF_RANGE => "Out of Range",
42
+ UNIMPLEMENTED => "Unimplemented",
43
+ INTERNAL => "Internal",
44
+ UNAVAILABLE => "Unavailable",
45
+ DATA_LOSS => "Data Loss",
46
+ UNAUTHENTICATED => "Unauthenticated"
47
+ }.freeze
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ # @namespace
7
+ module Protocol
8
+ # @namespace
9
+ module GRPC
10
+ VERSION = "0.1.0"
11
+ end
12
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2025, by Samuel Williams.
5
+
6
+ require_relative "grpc/version"
7
+
8
+ require_relative "grpc/status"
9
+ require_relative "grpc/error"
10
+ require_relative "grpc/methods"
11
+ require_relative "grpc/header"
12
+ require_relative "grpc/metadata"
13
+ require_relative "grpc/call"
14
+ require_relative "grpc/body/readable_body"
15
+ require_relative "grpc/body/writable_body"
16
+ require_relative "grpc/interface"
17
+ require_relative "grpc/middleware"
18
+ require_relative "grpc/health_check"
19
+
20
+ module Protocol
21
+ # Protocol abstractions for gRPC, built on top of `protocol-http`.
22
+ #
23
+ # gRPC is an RPC framework that runs over HTTP/2. It uses Protocol Buffers for serialization
24
+ # and supports four types of RPC patterns:
25
+ #
26
+ # 1. **Unary RPC**: Single request, single response
27
+ # 2. **Client Streaming**: Stream of requests, single response
28
+ # 3. **Server Streaming**: Single request, stream of responses
29
+ # 4. **Bidirectional Streaming**: Stream of requests, stream of responses
30
+ #
31
+ # This gem provides **protocol-level abstractions only** - no networking, no client/server implementations.
32
+ # Those should be built on top in separate gems (e.g., `async-grpc`).
33
+ module GRPC
34
+ end
35
+ end
data/license.md ADDED
@@ -0,0 +1,21 @@
1
+ # MIT License
2
+
3
+ Copyright, 2025, by Samuel Williams.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/readme.md ADDED
@@ -0,0 +1,57 @@
1
+ # Protocol::GRPC
2
+
3
+ Provides abstractions for working with the gRPC protocol over HTTP/2.
4
+
5
+ [![Development Status](https://github.com/socketry/protocol-grpc/workflows/Test/badge.svg)](https://github.com/socketry/protocol-grpc/actions?workflow=Test)
6
+
7
+ ## Features
8
+
9
+ `protocol-grpc` provides protocol-level abstractions for building gRPC applications:
10
+
11
+ - **Protocol-level abstractions** - No networking, no client/server implementations. Focuses on gRPC protocol details.
12
+ - **Message framing** - Handles gRPC's 5-byte length-prefixed message format with compression support.
13
+ - **Status codes and error handling** - Complete gRPC status code support with error hierarchy.
14
+ - **Metadata and trailers** - Full support for gRPC metadata headers and HTTP trailers.
15
+ - **Interface definitions** - Define service contracts using `Protocol::GRPC::Interface` with PascalCase method names matching `.proto` files.
16
+ - **Middleware pattern** - Abstract base class for building gRPC server applications.
17
+ - **Call context** - Track deadlines, metadata, and request context for each RPC call.
18
+
19
+ Following the same pattern as `protocol-http`, this gem provides only protocol abstractions. Client and server implementations are built on top in separate gems (e.g., `async-grpc`).
20
+
21
+ ## Usage
22
+
23
+ Please see the [project documentation](https://socketry.github.io/protocol-grpc/) for more details.
24
+
25
+ - [Getting Started](https://socketry.github.io/protocol-grpc/guides/getting-started/index) - This guide explains how to use `protocol-grpc` for building abstract gRPC interfaces.
26
+
27
+ ## Releases
28
+
29
+ Please see the [project releases](https://socketry.github.io/protocol-grpc/releases/index) for all releases.
30
+
31
+ ### v0.1.0
32
+
33
+ - Initial design.
34
+
35
+ ## See Also
36
+
37
+ - [async-grpc](https://github.com/socketry/async-grpc) — Asynchronous gRPC client and server implementation using this interface.
38
+ - [protocol-http](https://github.com/socketry/protocol-http) — HTTP protocol abstractions that gRPC builds upon.
39
+ - [async-http](https://github.com/socketry/async-http) — Asynchronous HTTP client and server, supporting HTTP/2 which gRPC requires.
40
+
41
+ ## Contributing
42
+
43
+ We welcome contributions to this project.
44
+
45
+ 1. Fork it.
46
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
47
+ 3. Commit your changes (`git commit -am 'Add some feature'`).
48
+ 4. Push to the branch (`git push origin my-new-feature`).
49
+ 5. Create new Pull Request.
50
+
51
+ ### Developer Certificate of Origin
52
+
53
+ In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
54
+
55
+ ### Community Guidelines
56
+
57
+ This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
data/releases.md ADDED
@@ -0,0 +1,5 @@
1
+ # Releases
2
+
3
+ ## v0.1.0
4
+
5
+ - Initial design.
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protocol-grpc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: async
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: base64
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: google-protobuf
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '4.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '4.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: protocol-http
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.28'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.28'
68
+ executables: []
69
+ extensions: []
70
+ extra_rdoc_files: []
71
+ files:
72
+ - context/getting-started.md
73
+ - context/index.yaml
74
+ - design.md
75
+ - lib/protocol/grpc.rb
76
+ - lib/protocol/grpc/body/readable_body.rb
77
+ - lib/protocol/grpc/body/writable_body.rb
78
+ - lib/protocol/grpc/call.rb
79
+ - lib/protocol/grpc/error.rb
80
+ - lib/protocol/grpc/header.rb
81
+ - lib/protocol/grpc/health_check.rb
82
+ - lib/protocol/grpc/interface.rb
83
+ - lib/protocol/grpc/metadata.rb
84
+ - lib/protocol/grpc/methods.rb
85
+ - lib/protocol/grpc/middleware.rb
86
+ - lib/protocol/grpc/status.rb
87
+ - lib/protocol/grpc/version.rb
88
+ - license.md
89
+ - readme.md
90
+ - releases.md
91
+ homepage: https://github.com/socketry/protocol-grpc
92
+ licenses:
93
+ - MIT
94
+ metadata:
95
+ documentation_uri: https://socketry.github.io/protocol-grpc/
96
+ source_code_uri: https://github.com/socketry/protocol-grpc.git
97
+ rdoc_options: []
98
+ require_paths:
99
+ - lib
100
+ required_ruby_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '3.2'
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubygems_version: 3.6.9
112
+ specification_version: 4
113
+ summary: Protocol abstractions for gRPC, built on top of protocol-http.
114
+ test_files: []