async-grpc-xds 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.
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "google/protobuf/duration_pb"
7
+ require "google/protobuf/wrappers_pb"
8
+
9
+ require "envoy/config/core/v3/health_check_pb"
10
+
11
+ module Async
12
+ module GRPC
13
+ module XDS
14
+ # Builds Envoy HTTP active health checks.
15
+ module HTTPHealthCheck
16
+ extend self
17
+
18
+ # Build an HTTP active health check.
19
+ # @parameter path [String] The request path used to check each endpoint.
20
+ # @parameter interval [Numeric] The interval between checks in seconds.
21
+ # @parameter timeout [Numeric] The check timeout in seconds.
22
+ # @parameter unhealthy_threshold [Integer] The consecutive failures required to mark an endpoint unhealthy.
23
+ # @parameter healthy_threshold [Integer] The consecutive successes required to mark an endpoint healthy.
24
+ # @returns [Envoy::Config::Core::V3::HealthCheck] The generated health check.
25
+ def build(path, interval: 1, timeout: 1, unhealthy_threshold: 2, healthy_threshold: 1)
26
+ Envoy::Config::Core::V3::HealthCheck.new(
27
+ timeout: duration(timeout),
28
+ interval: duration(interval),
29
+ unhealthy_threshold: Google::Protobuf::UInt32Value.new(value: Integer(unhealthy_threshold)),
30
+ healthy_threshold: Google::Protobuf::UInt32Value.new(value: Integer(healthy_threshold)),
31
+ http_health_check: Envoy::Config::Core::V3::HealthCheck::HttpHealthCheck.new(path: path.to_s)
32
+ )
33
+ end
34
+
35
+ private
36
+
37
+ def duration(value)
38
+ seconds = value.to_i
39
+
40
+ Google::Protobuf::Duration.new(
41
+ seconds: seconds,
42
+ nanos: ((value.to_f - seconds) * 1_000_000_000).to_i
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -16,16 +16,22 @@ module Async
16
16
  class Server
17
17
  # Initialize an xDS server.
18
18
  # @parameter control_plane [ControlPlane] The control plane to serve.
19
+ # @parameter services [Array(Class)] The discovery service classes to register.
19
20
  # @parameter options [Hash] Default options forwarded to `Async::HTTP::Server`.
20
- def initialize(control_plane = ControlPlane.new, **options)
21
+ def initialize(control_plane = ControlPlane.new, services: [Service], **options)
21
22
  @control_plane = control_plane
22
23
  @dispatcher = Async::GRPC::Dispatcher.new
23
- @dispatcher.register(Service.new(@control_plane))
24
+ @services = services.map do |service|
25
+ service.new(@control_plane).tap do |instance|
26
+ @dispatcher.register(instance)
27
+ end
28
+ end
24
29
  @options = options
25
30
  end
26
31
 
27
32
  attr :control_plane
28
33
  attr :dispatcher
34
+ attr :services
29
35
 
30
36
  # Run the xDS server on an endpoint.
31
37
  # @parameter endpoint [Async::HTTP::Endpoint] The endpoint to bind.
@@ -3,29 +3,21 @@
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2026, by Samuel Williams.
5
5
 
6
- require "async"
7
- require "async/queue"
8
- require "async/grpc/service"
9
- require "protocol/grpc/error"
10
- require "protocol/grpc/status"
11
- require "set"
12
-
13
6
  require "envoy/service/discovery/v3/aggregated_discovery_service"
14
7
 
15
- require_relative "control_plane"
8
+ require_relative "discovery_service"
16
9
 
17
10
  module Async
18
11
  module GRPC
19
12
  module XDS
20
13
  # Serves Envoy Aggregated Discovery Service requests from a {ControlPlane}.
21
- class Service < Async::GRPC::Service
14
+ class Service < DiscoveryService
22
15
  SERVICE_NAME = "envoy.service.discovery.v3.AggregatedDiscoveryService"
23
16
 
24
17
  # Initialize an Aggregated Discovery Service.
25
18
  # @parameter control_plane [ControlPlane] The control plane that provides resources.
26
19
  def initialize(control_plane)
27
- super(Envoy::Service::Discovery::V3::AggregatedDiscoveryService, SERVICE_NAME)
28
- @control_plane = control_plane
20
+ super(Envoy::Service::Discovery::V3::AggregatedDiscoveryService, SERVICE_NAME, control_plane)
29
21
  end
30
22
 
31
23
  # Serve a state-of-the-world Aggregated Discovery Service stream.
@@ -34,25 +26,7 @@ module Async
34
26
  # @parameter call [Object] The gRPC call context.
35
27
  # @asynchronous
36
28
  def stream_aggregated_resources(input, output, call)
37
- stream = Stream.new(@control_plane, output)
38
- @control_plane.register_stream(stream)
39
-
40
- reader = Async do
41
- input.each do |request|
42
- stream.request(request)
43
- end
44
- end
45
-
46
- writer = Async do
47
- stream.run
48
- end
49
-
50
- reader.wait
51
- ensure
52
- stream&.close
53
- reader&.stop
54
- writer&.stop
55
- @control_plane.remove_stream(stream) if stream
29
+ stream_resources(input, output)
56
30
  end
57
31
 
58
32
  # Reject a delta Aggregated Discovery Service stream, which is not supported.
@@ -61,79 +35,7 @@ module Async
61
35
  # @parameter call [Object] The gRPC call context.
62
36
  # @raises [Protocol::GRPC::Error] Always raised because delta xDS is not implemented.
63
37
  def delta_aggregated_resources(input, output, call)
64
- raise Protocol::GRPC::Error.new(
65
- Protocol::GRPC::Status::UNIMPLEMENTED,
66
- "Delta xDS is not implemented."
67
- )
68
- end
69
-
70
- # Represents one ADS stream and its subscribed resources.
71
- class Stream
72
- # Initialize an ADS stream.
73
- # @parameter control_plane [ControlPlane] The control plane that provides resources.
74
- # @parameter output [Interface(:write)] The discovery response stream.
75
- def initialize(control_plane, output)
76
- @control_plane = control_plane
77
- @output = output
78
- @subscriptions = Hash.new{|hash, type_url| hash[type_url] = Set.new}
79
- @versions = {}
80
- @queue = Async::Queue.new
81
- @closed = false
82
- end
83
-
84
- # Process a discovery request and update the stream's subscriptions.
85
- # @parameter request [Envoy::Service::Discovery::V3::DiscoveryRequest] The discovery request.
86
- def request(request)
87
- return if request.type_url.nil? || request.type_url.empty?
88
-
89
- if request.error_detail
90
- Console.warn(self, "Received xDS NACK.", type_url: request.type_url, error_detail: request.error_detail)
91
- return
92
- end
93
-
94
- if request.resource_names.any?
95
- @subscriptions[request.type_url].merge(request.resource_names)
96
- else
97
- @subscriptions[request.type_url]
98
- end
99
-
100
- @queue << request.type_url
101
- end
102
-
103
- # Schedule a resource type for delivery after it changes.
104
- # @parameter type_url [String] The changed xDS resource type URL.
105
- def changed(type_url)
106
- @queue << type_url unless @closed
107
- end
108
-
109
- # Deliver scheduled resource updates until the stream closes.
110
- # @asynchronous
111
- def run
112
- until @closed
113
- type_url = @queue.dequeue
114
- flush(type_url)
115
- end
116
- end
117
-
118
- # Deliver the latest resource version for a subscribed type.
119
- # @parameter type_url [String] The xDS resource type URL.
120
- def flush(type_url)
121
- names = @subscriptions[type_url]
122
- return unless names
123
-
124
- version = @control_plane.version(type_url)
125
- return if @versions[type_url] == version
126
-
127
- response = @control_plane.response(type_url, names)
128
- @output.write(response)
129
- @versions[type_url] = version
130
- end
131
-
132
- # Close the stream and stop waiting for changes.
133
- def close
134
- @closed = true
135
- @queue.close
136
- end
38
+ delta_resources
137
39
  end
138
40
  end
139
41
  end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Released under the MIT License.
4
+ # Copyright, 2026, by Samuel Williams.
5
+
6
+ require "async/queue"
7
+ require "protocol/grpc/error"
8
+ require "protocol/grpc/status"
9
+ require "set"
10
+
11
+ module Async
12
+ module GRPC
13
+ module XDS
14
+ # Represents one discovery stream and its subscribed resources.
15
+ class Stream
16
+ # Initialize a discovery stream.
17
+ # @parameter control_plane [ControlPlane] The control plane that provides resources.
18
+ # @parameter output [Interface(:write)] The discovery response stream.
19
+ # @parameter resource_type [String | Nil] The fixed resource type, or `nil` for aggregated discovery.
20
+ def initialize(control_plane, output, resource_type: nil)
21
+ @control_plane = control_plane
22
+ @output = output
23
+ @resource_type = resource_type
24
+ @subscriptions = {}
25
+ @versions = {}
26
+ @queue = Async::Queue.new
27
+ @closed = false
28
+ end
29
+
30
+ # Process a discovery request and update the stream's subscriptions.
31
+ # @parameter request [Envoy::Service::Discovery::V3::DiscoveryRequest] The discovery request.
32
+ def request(request)
33
+ type_url = request.type_url
34
+
35
+ if @resource_type
36
+ if type_url.nil? || type_url.empty?
37
+ type_url = @resource_type
38
+ elsif type_url != @resource_type
39
+ raise Protocol::GRPC::Error.new(
40
+ Protocol::GRPC::Status::INVALID_ARGUMENT,
41
+ "Expected resource type #{@resource_type.inspect}, but received #{type_url.inspect}."
42
+ )
43
+ end
44
+ elsif type_url.nil? || type_url.empty?
45
+ return
46
+ end
47
+
48
+ if request.error_detail
49
+ Console.warn(self, "Received xDS NACK.", type_url: type_url, error_detail: request.error_detail)
50
+ return
51
+ end
52
+
53
+ names = Set.new(request.resource_names)
54
+ if @subscriptions[type_url] != names
55
+ @subscriptions[type_url] = names
56
+ @versions.delete(type_url)
57
+ end
58
+
59
+ @queue << type_url
60
+ end
61
+
62
+ # Schedule a resource type for delivery after it changes.
63
+ # @parameter type_url [String] The changed xDS resource type URL.
64
+ def changed(type_url)
65
+ return if @resource_type && type_url != @resource_type
66
+ return unless @subscriptions.key?(type_url)
67
+
68
+ @queue << type_url unless @closed
69
+ end
70
+
71
+ # Deliver scheduled resource updates until the stream closes.
72
+ # @asynchronous
73
+ def run
74
+ until @closed
75
+ type_url = @queue.dequeue
76
+ flush(type_url)
77
+ end
78
+ end
79
+
80
+ # Deliver the latest resource version for a subscribed type.
81
+ # @parameter type_url [String] The xDS resource type URL.
82
+ def flush(type_url)
83
+ names = @subscriptions[type_url]
84
+ return unless names
85
+
86
+ version = @control_plane.version(type_url)
87
+ return if @versions[type_url] == version
88
+
89
+ response = @control_plane.response(type_url, names)
90
+ @output.write(response)
91
+ @versions[type_url] = version
92
+ end
93
+
94
+ # Close the stream and stop waiting for changes.
95
+ def close
96
+ @closed = true
97
+ @queue.close
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -9,7 +9,7 @@ module Async
9
9
  module GRPC
10
10
  # @namespace
11
11
  module XDS
12
- VERSION = "0.2.0"
12
+ VERSION = "0.4.0"
13
13
  end
14
14
  end
15
15
  end
@@ -13,11 +13,17 @@ require_relative "xds/health_checker"
13
13
  require_relative "xds/load_balancer"
14
14
  require_relative "xds/context"
15
15
  require_relative "xds/client"
16
+ require_relative "xds/config_source"
16
17
  require_relative "xds/cluster"
17
18
  require_relative "xds/endpoint"
19
+ require_relative "xds/http_health_check"
18
20
  require_relative "xds/client_side_weighted_round_robin"
19
21
  require_relative "xds/control_plane"
22
+ require_relative "xds/stream"
23
+ require_relative "xds/discovery_service"
20
24
  require_relative "xds/service"
25
+ require_relative "xds/cluster_discovery_service"
26
+ require_relative "xds/endpoint_discovery_service"
21
27
  require_relative "xds/server"
22
28
 
23
29
  module Async
data/proto/readme.md CHANGED
@@ -25,45 +25,29 @@ These files come from [envoyproxy/data-plane-api](https://github.com/envoyproxy/
25
25
  To update these files, run:
26
26
 
27
27
  ```bash
28
- ./xds/update_protos.sh
28
+ bundle exec bake update_protos
29
29
  ```
30
30
 
31
- Or manually:
32
-
33
- ```bash
34
- # Clone envoy data-plane-api
35
- git clone --depth 1 https://github.com/envoyproxy/data-plane-api.git /tmp/envoy-api
36
-
37
- # Copy needed files
38
- cp -r /tmp/envoy-api/envoy proto/
39
- cp -r /tmp/envoy-api/google proto/
40
-
41
- # Cleanup
42
- rm -rf /tmp/envoy-api
43
- ```
31
+ This fetches the pinned upstream revisions defined in `bake.rb`, updates the vendored definitions, and regenerates the checked-in Ruby classes.
44
32
 
45
33
  ## Generating Ruby Code
46
34
 
47
35
  After updating proto files, generate Ruby classes:
48
36
 
49
37
  ```bash
50
- bundle exec bake async:grpc:xds:generate_protos
38
+ bundle exec bake generate_protos
51
39
  ```
52
40
 
53
41
  ## Version
54
42
 
55
- These files are from the latest `main` branch of:
43
+ The source repositories and revisions are defined by `PROTOBUF_SOURCES` in `bake.rb`. They include:
44
+
56
45
  - `envoyproxy/data-plane-api` - Envoy API definitions
57
46
  - `protocolbuffers/protobuf` - Google protobuf well-known types
58
47
  - `googleapis/api-common-protos` - Google RPC status
59
-
60
- To lock to a specific version, modify `xds/update_protos.sh` to check out specific tags:
61
-
62
- ```bash
63
- cd /tmp/envoy-api
64
- git checkout v1.30.0 # Use specific Envoy version
65
- # Then copy files
66
- ```
48
+ - `cncf/xds` - xDS API definitions
49
+ - `cncf/udpa` - UDPA annotations
50
+ - `envoyproxy/protoc-gen-validate` - Validation annotations
67
51
 
68
52
  ## Note on Dependencies
69
53
 
data/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  xDS support for `async-grpc` clients.
4
4
 
5
- This gem contains the experimental xDS implementation extracted from `async-grpc`, including Envoy xDS protobuf definitions, ADS discovery, CDS/EDS resource handling, and client-side load balancing.
5
+ This gem contains the experimental xDS implementation extracted from `async-grpc`, including Envoy xDS protobuf definitions, aggregated and resource-specific discovery, CDS/EDS resource handling, and client-side load balancing.
6
6
 
7
7
  [![Development Status](https://github.com/socketry/async-grpc-xds/workflows/Test/badge.svg)](https://github.com/socketry/async-grpc-xds/actions?workflow=Test)
8
8
 
@@ -10,22 +10,29 @@ This gem contains the experimental xDS implementation extracted from `async-grpc
10
10
 
11
11
  Please see the [project documentation](https://socketry.github.io/async-grpc-xds/) for more details.
12
12
 
13
- ## Status
14
-
15
- This is an early implementation focused on ADS with CDS and EDS. LDS/RDS, full routing semantics, NACK handling, locality weighting, and delta xDS are not complete yet.
13
+ - [Getting Started](https://socketry.github.io/async-grpc-xds/guides/getting-started/index) - This guide explains how to use `async-grpc-xds` to publish CDS and EDS resources to Envoy, or to discover gRPC backends from Ruby using xDS.
16
14
 
17
15
  ## Testing
18
16
 
19
- The `xds/` directory contains a Docker Compose integration environment with a Go xDS control plane and Ruby gRPC backends.
17
+ The `integration/` directory contains Docker Compose environments for testing the Ruby xDS client against a Go control plane and the Ruby control plane against Envoy.
20
18
 
21
19
  ``` bash
22
- docker compose -f xds/docker-compose.yaml up --build --exit-code-from tests
20
+ bundle exec bake test:integration
23
21
  ```
24
22
 
25
23
  ## Releases
26
24
 
27
25
  Please see the [project releases](https://socketry.github.io/async-grpc-xds/releases/index) for all releases.
28
26
 
27
+ ### v0.4.0
28
+
29
+ - Add dedicated Cluster Discovery Service and Endpoint Discovery Service implementations, allowing resource-specific xDS streams without claiming ADS.
30
+ - Allow generated clusters to use a dedicated EDS configuration source instead of ADS.
31
+
32
+ ### v0.3.0
33
+
34
+ - Add Envoy HTTP active health-check resources and attach health checks to generated clusters.
35
+
29
36
  ### v0.2.0
30
37
 
31
38
  - Add ORCA load reporting messages, service interface, and client-side weighted-round-robin resources.
data/releases.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Releases
2
2
 
3
+ ## v0.4.0
4
+
5
+ - Add dedicated Cluster Discovery Service and Endpoint Discovery Service implementations, allowing resource-specific xDS streams without claiming ADS.
6
+ - Allow generated clusters to use a dedicated EDS configuration source instead of ADS.
7
+
8
+ ## v0.3.0
9
+
10
+ - Add Envoy HTTP active health-check resources and attach health checks to generated clusters.
11
+
3
12
  ## v0.2.0
4
13
 
5
14
  - Add ORCA load reporting messages, service interface, and client-side weighted-round-robin resources.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: async-grpc-xds
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
@@ -126,23 +126,29 @@ executables: []
126
126
  extensions: []
127
127
  extra_rdoc_files: []
128
128
  files:
129
- - fixtures/async/grpc/test_interface.rb
130
- - fixtures/async/grpc/test_message.rb
129
+ - context/getting-started.md
130
+ - context/index.yaml
131
131
  - lib/async/grpc/xds.rb
132
132
  - lib/async/grpc/xds/ads_stream.rb
133
133
  - lib/async/grpc/xds/client.rb
134
134
  - lib/async/grpc/xds/client_side_weighted_round_robin.rb
135
135
  - lib/async/grpc/xds/cluster.rb
136
+ - lib/async/grpc/xds/cluster_discovery_service.rb
137
+ - lib/async/grpc/xds/config_source.rb
136
138
  - lib/async/grpc/xds/context.rb
137
139
  - lib/async/grpc/xds/control_plane.rb
138
140
  - lib/async/grpc/xds/discovery_client.rb
141
+ - lib/async/grpc/xds/discovery_service.rb
139
142
  - lib/async/grpc/xds/endpoint.rb
143
+ - lib/async/grpc/xds/endpoint_discovery_service.rb
140
144
  - lib/async/grpc/xds/health_checker.rb
145
+ - lib/async/grpc/xds/http_health_check.rb
141
146
  - lib/async/grpc/xds/load_balancer.rb
142
147
  - lib/async/grpc/xds/resource_cache.rb
143
148
  - lib/async/grpc/xds/resources.rb
144
149
  - lib/async/grpc/xds/server.rb
145
150
  - lib/async/grpc/xds/service.rb
151
+ - lib/async/grpc/xds/stream.rb
146
152
  - lib/async/grpc/xds/version.rb
147
153
  - lib/envoy.rb
148
154
  - lib/envoy/annotations/deprecation_pb.rb
@@ -365,21 +371,12 @@ files:
365
371
  - proto/xds/type/v3/typed_struct.proto
366
372
  - readme.md
367
373
  - releases.md
368
- - xds/Dockerfile.backend
369
- - xds/Dockerfile.control-plane
370
- - xds/backend_server.rb
371
- - xds/docker-compose.yaml
372
- - xds/go.mod
373
- - xds/go.sum
374
- - xds/readme.md
375
- - xds/test/async/grpc/xds/client.rb
376
- - xds/test/async/grpc/xds/control_plane.rb
377
- - xds/test_server.go
378
- - xds/update_protos.sh
379
374
  homepage: https://github.com/socketry/async-grpc-xds
380
375
  licenses:
381
376
  - MIT
382
377
  metadata:
378
+ bug_tracker_uri: https://github.com/socketry/async-grpc-xds/issues
379
+ changelog_uri: https://github.com/socketry/async-grpc-xds/blob/main/releases.md
383
380
  documentation_uri: https://socketry.github.io/async-grpc-xds/
384
381
  source_code_uri: https://github.com/socketry/async-grpc-xds.git
385
382
  rdoc_options: []
metadata.gz.sig CHANGED
Binary file
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Released under the MIT License.
4
- # Copyright, 2026, by Samuel Williams.
5
-
6
- require "protocol/grpc/interface"
7
- require_relative "test_message"
8
-
9
- module Async
10
- module GRPC
11
- module Fixtures
12
- # Test interface for unit tests
13
- # RPC names use PascalCase to match .proto files
14
- class TestInterface < Protocol::GRPC::Interface
15
- rpc :UnaryCall, request_class: Protocol::GRPC::Fixtures::TestMessage,
16
- response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :unary
17
- rpc :ServerStreamingCall, request_class: Protocol::GRPC::Fixtures::TestMessage,
18
- response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :server_streaming
19
- rpc :SayHello, request_class: Protocol::GRPC::Fixtures::TestMessage,
20
- response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :unary
21
- rpc :BidirectionalCall, request_class: Protocol::GRPC::Fixtures::TestMessage,
22
- response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :bidirectional
23
- rpc :ClientStreamingCall, request_class: Protocol::GRPC::Fixtures::TestMessage,
24
- response_class: Protocol::GRPC::Fixtures::TestMessage, streaming: :client_streaming
25
- rpc :SlowCall
26
- end
27
-
28
- # Test service implementation
29
- # Method names use snake_case (Ruby convention)
30
- class TestService < Async::GRPC::Service
31
- def unary_call(input, output, _call)
32
- request = input.read
33
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Response: #{request.value}")
34
- output.write(response)
35
- end
36
-
37
- def server_streaming_call(input, output, _call)
38
- request = input.read
39
- 3.times do |i|
40
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Response #{i}: #{request.value}")
41
- output.write(response)
42
- end
43
- end
44
-
45
- def say_hello(input, output, _call)
46
- request = input.read
47
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Hello, #{request.value}!")
48
- output.write(response)
49
- end
50
-
51
- def bidirectional_call(input, output, _call)
52
- # Read all input messages and echo them back with a prefix
53
- input.each do |request|
54
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Echo: #{request.value}")
55
- output.write(response)
56
- end
57
- output.close_write
58
- end
59
-
60
- def client_streaming_call(input, output, _call)
61
- # Read all input messages and return a summary
62
- values = []
63
- input.each do |request|
64
- values << request.value
65
- end
66
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Received: #{values.join(', ')}")
67
- output.write(response)
68
- end
69
-
70
- def slow_call(input, output, _call)
71
- request = input.read
72
- sleep 1 # Simulate a slow operation
73
- response = Protocol::GRPC::Fixtures::TestMessage.new(value: "Slow response: #{request.value}")
74
- output.write(response)
75
- end
76
- end
77
- end
78
- end
79
- end
@@ -1,56 +0,0 @@
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 Fixtures
9
- # Simple test message for unit tests.
10
- # Provides a basic protobuf-like message structure.
11
- class TestMessage
12
- # @attribute [String | Nil] The message value.
13
- attr_accessor :value
14
-
15
- # Initialize a new test message.
16
- # @parameter value [String | Nil] The message value
17
- def initialize(value: nil)
18
- @value = value
19
- end
20
-
21
- # Serialize the message to a binary string.
22
- # @returns [String] Binary representation of the message
23
- def to_proto
24
- # Simple serialization: length-prefixed value
25
- value_data = (@value || "").dup.force_encoding(Encoding::BINARY)
26
- [value_data.bytesize].pack("N") + value_data
27
- end
28
-
29
- alias encode to_proto
30
-
31
- # Deserialize a binary string into a message.
32
- # @parameter data [String] Binary representation of the message
33
- # @returns [TestMessage] Deserialized message instance
34
- def self.decode(data)
35
- # Simple binary format: first 4 bytes are length, rest is value
36
- length = data[0...4].unpack1("N")
37
- value = data[4...(4 + length)].dup.force_encoding(Encoding::UTF_8)
38
- new(value: value)
39
- end
40
-
41
- # Check equality with another message.
42
- # @parameter other [Object] The object to compare
43
- # @returns [Boolean] `true` if values are equal
44
- def ==(other)
45
- other.is_a?(TestMessage) && @value == other.value
46
- end
47
-
48
- # Get a string representation of the message.
49
- # @returns [String] String representation
50
- def inspect
51
- "#<#{self.class.name} value=#{@value.inspect}>"
52
- end
53
- end
54
- end
55
- end
56
- end