service_mesh 0.4.4 → 0.6.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: 0d1c22ba40201e6531617371be60f030c23976f3a31d06a536114a388c59a9f0
4
- data.tar.gz: c3e7fd9aedbc639cde6d3727e028799fa51d3222a0006826655084e9f6cb36ea
3
+ metadata.gz: 643fe078e4376b1f4d19e3e45e0dbbe7e8b61e620e96591f67c7b0ed18865a50
4
+ data.tar.gz: 57c88b02dd056895234d1536b16f00973202cdec9b6600f7ab31621452e52d93
5
5
  SHA512:
6
- metadata.gz: d9ebe5643e51f2aebb1ddb7896344b7db28ec0bced10a6cb759db597ffafea1c558dd7612daa9297d4fb4d36c413349c3b085a692770ea0c60742a892d1beffa
7
- data.tar.gz: a5d57ce5d40a07b3ce478ba55dead8115c62c7f6bb678115e90fce513ef27128e851d33f71dd408d318fae5411a1eb53b086d1b539b867bec225934ed7558071
6
+ metadata.gz: 56f15b80a1db1ad69e0e95105e8ed221b873b4ae762ff85296e576ad7cb1625b22209d5bf13aad4e85b903a2144b08bd9f6bdfef0169b49860f6c3350ebcdc4d
7
+ data.tar.gz: 7be31a262f4c7cf55b5a2f7661c69baf7922e2261a5924a59537bbb3e0c0c3141eb0247977aca65941ed033a5eeb6dd66f5a900978e980d4a28d66cff6cc3aff
data/README.md CHANGED
@@ -23,8 +23,12 @@ Requires Ruby 3.3 or newer. The gem has no runtime dependencies.
23
23
  raises `KindMismatch`.
24
24
  - `Message#payload` is always `Encoding::BINARY`.
25
25
  - `Target#same_channel?` compares segments and kind and ignores metadata.
26
- - The configuration keys the specification defines: `DEPLOYMENT_GROUP_KEY`,
27
- `CONSUMER_GROUP_KEY`, and the value `CONSUMER_GROUP_NONE`.
26
+ - The configuration keys the specification defines. `DEPLOYMENT_GROUP_KEY` is runtime
27
+ configuration. `CONSUMER_GROUP_KEY`, with the value `CONSUMER_GROUP_NONE`, is `Endpoint` and
28
+ `Subscriber` metadata.
29
+ - The reserved metadata prefix `RESERVED_PREFIX` (`Mesh-`).
30
+ - The metadata keys the specification defines: `HANDLER_ERROR_KEY`, `TIMEOUT_KEY`, `DEADLINE_KEY`,
31
+ `DELIVERY_ATTEMPT_KEY`, and `MESSAGE_ID_KEY`.
28
32
  - The errors defined by the specification as `ServiceMesh::Error`: `ServiceMesh::KindMismatch`,
29
33
  `ServiceMesh::InvalidTarget`, and `ServiceMesh::NoDeploymentGroup`.
30
34
  - A conformance suite a transport runs against its own `Client` and
@@ -72,6 +76,18 @@ RSpec.describe MyTransport do
72
76
  end
73
77
  ```
74
78
 
79
+ A transport that needs its own settings for the suite to pass supplies any of
80
+ four optional lets, each defaulting to an empty Hash:
81
+
82
+ ```ruby
83
+ let(:endpoint_metadata) { {...} } # merged under the metadata of every Endpoint the suite builds
84
+ let(:subscriber_metadata) { {...} } # merged under the metadata of every Subscriber the suite builds
85
+ let(:request_options) { {...} } # the options of every request the suite makes
86
+ let(:publish_options) { {...} } # the options of every publish the suite makes
87
+ ```
88
+
89
+ Metadata an example sets itself wins over `endpoint_metadata` and `subscriber_metadata`.
90
+
75
91
  The suite covers:
76
92
 
77
93
  - kind checks
@@ -16,6 +16,12 @@ require_relative "../service_mesh"
16
16
  # let(:client_config) { {...transport keys...} }
17
17
  # let(:route_target) { ServiceMesh::Target.new(segments: %w[test echo], kind: :route) }
18
18
  # let(:topic_target) { ServiceMesh::Target.new(segments: %w[test event], kind: :topic) }
19
+ #
20
+ # # Optional, each defaulting to an empty Hash.
21
+ # let(:endpoint_metadata) { {...} } # merged under each Endpoint's metadata
22
+ # let(:subscriber_metadata) { {...} } # merged under each Subscriber's metadata
23
+ # let(:request_options) { {...} } # options for every request
24
+ # let(:publish_options) { {...} } # options for every publish
19
25
  # end
20
26
  # end
21
27
  #
@@ -25,13 +31,25 @@ require_relative "../service_mesh"
25
31
  RSpec.shared_examples "a service mesh transport" do
26
32
  let(:service_map) { ServiceMesh::ServiceMap.new }
27
33
  let(:wait) { 3 }
34
+ let(:endpoint_metadata) { {} }
35
+ let(:subscriber_metadata) { {} }
36
+ let(:request_options) { {} }
37
+ let(:publish_options) { {} }
28
38
 
29
39
  def endpoint(target, handler, metadata: {})
30
- ServiceMesh::Endpoint.new(target: target, handler: handler, metadata: metadata)
40
+ ServiceMesh::Endpoint.new(target: target, handler: handler, metadata: endpoint_metadata.merge(metadata))
31
41
  end
32
42
 
33
43
  def subscriber(target, handler, metadata: {})
34
- ServiceMesh::Subscriber.new(target: target, handler: handler, metadata: metadata)
44
+ ServiceMesh::Subscriber.new(target: target, handler: handler, metadata: subscriber_metadata.merge(metadata))
45
+ end
46
+
47
+ def request(msg, via: client)
48
+ via.request(msg, request_options)
49
+ end
50
+
51
+ def publish(msg, via: client)
52
+ via.publish(msg, publish_options)
35
53
  end
36
54
 
37
55
  def message(target, metadata: {}, payload: "")
@@ -72,11 +90,11 @@ RSpec.shared_examples "a service mesh transport" do
72
90
 
73
91
  describe "kind checks" do
74
92
  it "rejects a request to a topic" do
75
- expect { client.request(message(topic_target)) }.to raise_error(ServiceMesh::KindMismatch)
93
+ expect { request(message(topic_target)) }.to raise_error(ServiceMesh::KindMismatch)
76
94
  end
77
95
 
78
96
  it "rejects a publish to a route" do
79
- expect { client.publish(message(route_target)) }.to raise_error(ServiceMesh::KindMismatch)
97
+ expect { publish(message(route_target)) }.to raise_error(ServiceMesh::KindMismatch)
80
98
  end
81
99
 
82
100
  it "rejects an endpoint on a topic" do
@@ -107,7 +125,7 @@ RSpec.shared_examples "a service mesh transport" do
107
125
  message(topic_target, metadata: {"Reply-Key" => "reply-value"}, payload: m.payload.upcase)
108
126
  })])
109
127
 
110
- reply = client.request(message(route_target, metadata: {"Request-Key" => "request-value"}, payload: "hello"))
128
+ reply = request(message(route_target, metadata: {"Request-Key" => "request-value"}, payload: "hello"))
111
129
 
112
130
  expect(seen.target.same_channel?(route_target)).to be(true)
113
131
  expect(seen.metadata["Request-Key"]).to eq("request-value")
@@ -119,7 +137,7 @@ RSpec.shared_examples "a service mesh transport" do
119
137
 
120
138
  it "carries an empty payload with no metadata" do
121
139
  serve(endpoints: [endpoint(route_target, ->(m) { m })])
122
- reply = client.request(message(route_target))
140
+ reply = request(message(route_target))
123
141
  expect([reply.payload, reply.metadata]).to eq(["", {}])
124
142
  end
125
143
  end
@@ -129,7 +147,7 @@ RSpec.shared_examples "a service mesh transport" do
129
147
  got = Queue.new
130
148
  serve(subscribers: [subscriber(topic_target, ->(m) { got << m })])
131
149
 
132
- client.publish(message(topic_target, metadata: {"Event-Id" => "42"}, payload: "created"))
150
+ publish(message(topic_target, metadata: {"Event-Id" => "42"}, payload: "created"))
133
151
 
134
152
  m = Timeout.timeout(wait) { got.pop }
135
153
  expect(m.target.same_channel?(topic_target)).to be(true)
@@ -153,7 +171,7 @@ RSpec.shared_examples "a service mesh transport" do
153
171
  serve(config_for(groups[i]), subscribers: [subscriber(topic_target, ->(_) { count << true }, metadata: metadata[i])])
154
172
  end
155
173
 
156
- client.publish(message(topic_target))
174
+ publish(message(topic_target))
157
175
 
158
176
  wait_until { count.size >= want }
159
177
  sleep 0.1 # let an unwanted extra delivery show up
@@ -161,14 +179,18 @@ RSpec.shared_examples "a service mesh transport" do
161
179
  end
162
180
  end
163
181
 
164
- it "honours a group set on the target rather than the binding" do
182
+ it "ignores a group set on the target, which carries none" do
165
183
  count = Queue.new
166
184
  %w[a b].each do |group|
167
185
  t = ServiceMesh::Target.new(segments: topic_target.segments, kind: :topic, metadata: {ServiceMesh::CONSUMER_GROUP_KEY => group})
168
186
  serve(config_for("same"), subscribers: [subscriber(t, ->(_) { count << true })])
169
187
  end
170
- client.publish(message(topic_target))
171
- wait_until { count.size == 2 }
188
+
189
+ publish(message(topic_target))
190
+
191
+ wait_until { count.size >= 1 }
192
+ sleep 0.1 # let an unwanted extra delivery show up
193
+ expect(count.size).to eq(1)
172
194
  end
173
195
  end
174
196
 
@@ -177,9 +199,9 @@ RSpec.shared_examples "a service mesh transport" do
177
199
  serve(endpoints: [endpoint(route_target, ->(m) { m })])
178
200
  own = new_client.call(client_config, service_map)
179
201
 
180
- expect(own.request(message(route_target)).payload).to eq("")
202
+ expect(request(message(route_target), via: own).payload).to eq("")
181
203
  own.close
182
- expect { own.request(message(route_target)) }.to raise_error(StandardError)
204
+ expect { request(message(route_target), via: own) }.to raise_error(StandardError)
183
205
  end
184
206
  end
185
207
 
@@ -193,9 +215,9 @@ RSpec.shared_examples "a service mesh transport" do
193
215
  expect(rt.client).to equal(given)
194
216
  expect(rt.service_map).to equal(service_map)
195
217
  rt.start
196
- expect(given.request(req).payload).to eq("")
218
+ expect(request(req, via: given).payload).to eq("")
197
219
  rt.stop(1)
198
- expect { given.request(req) }.to raise_error(StandardError)
220
+ expect { request(req, via: given) }.to raise_error(StandardError)
199
221
  end
200
222
  end
201
223
 
@@ -230,7 +252,7 @@ RSpec.shared_examples "a service mesh transport" do
230
252
  message(route_target, payload: "done")
231
253
  })])
232
254
 
233
- reply = Thread.new { client.request(message(route_target)) }
255
+ reply = Thread.new { request(message(route_target)) }
234
256
  @conformance_threads << reply
235
257
  Timeout.timeout(wait) { entered.pop }
236
258
 
@@ -252,7 +274,7 @@ RSpec.shared_examples "a service mesh transport" do
252
274
  message(route_target)
253
275
  })])
254
276
 
255
- @conformance_threads << Thread.new { client.request(message(route_target)) rescue nil } # rubocop:disable Style/RescueModifier
277
+ @conformance_threads << Thread.new { request(message(route_target)) rescue nil } # rubocop:disable Style/RescueModifier
256
278
  Timeout.timeout(wait) { entered.pop }
257
279
 
258
280
  expect(rt.stop(0.1)).to be(false)
@@ -4,11 +4,43 @@ module ServiceMesh
4
4
  KINDS = %i[route topic].freeze
5
5
 
6
6
  # Configuration keys the specification defines. Everything else belongs to
7
- # a transport.
7
+ # a transport. DEPLOYMENT_GROUP_KEY is runtime configuration, and nothing
8
+ # else carries it. CONSUMER_GROUP_KEY is Endpoint and Subscriber metadata;
9
+ # when it is unset or empty, the runtime's deployment group applies, and
10
+ # CONSUMER_GROUP_NONE means no group. A Target carries neither.
8
11
  DEPLOYMENT_GROUP_KEY = "deployment_group"
9
12
  CONSUMER_GROUP_KEY = "consumer_group"
10
13
  CONSUMER_GROUP_NONE = "none"
11
14
 
15
+ # Metadata keys that start with RESERVED_PREFIX belong to transports and
16
+ # protocol layers, and application code does not set them. A key a
17
+ # transport defines for itself continues with the transport's name, such as
18
+ # "Mesh-Nats-". A transport may use any of the keys below, and one that does
19
+ # follows the meaning and format given here.
20
+ RESERVED_PREFIX = "Mesh-"
21
+
22
+ # Set by the serving transport on a reply when the Endpoint's handler
23
+ # failed. The value is the failure's text.
24
+ HANDLER_ERROR_KEY = "Mesh-Handler-Error"
25
+
26
+ # Set by the requesting transport on a request. The value is how long the
27
+ # caller waits for the reply, as a whole number of milliseconds.
28
+ TIMEOUT_KEY = "Mesh-Timeout"
29
+
30
+ # Set by the receiving transport on a message it hands to a handler. The
31
+ # value is when the handler's result stops mattering, in RFC 3339 with
32
+ # fractional seconds.
33
+ DEADLINE_KEY = "Mesh-Deadline"
34
+
35
+ # Set by the receiving transport on a message it hands to a handler. The
36
+ # value is which delivery of the message this is, starting at 1. It is
37
+ # absent when the transport does not track deliveries.
38
+ DELIVERY_ATTEMPT_KEY = "Mesh-Delivery-Attempt"
39
+
40
+ # Set by the publisher or caller. The value is an ID that stays the same
41
+ # when the same message is sent again, so handlers can recognize repeats.
42
+ MESSAGE_ID_KEY = "Mesh-Message-Id"
43
+
12
44
  # Identifies a receiving channel on the mesh. A transport assembles the
13
45
  # segments into its own address; that string never leaves the transport.
14
46
  Target = Data.define(:segments, :kind, :metadata) do
@@ -31,7 +63,9 @@ module ServiceMesh
31
63
  end
32
64
  end
33
65
 
34
- # What travels between services. Payload is always BINARY encoded.
66
+ # What travels between services. Payload is always BINARY encoded. Metadata
67
+ # keys that start with RESERVED_PREFIX belong to transports and protocol
68
+ # layers.
35
69
  Message = Data.define(:target, :metadata, :payload) do
36
70
  def initialize(target:, metadata: {}, payload: "")
37
71
  super(target: target, metadata: metadata.to_h.freeze, payload: payload.to_s.b.freeze)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceMesh
4
- VERSION = "0.4.4"
4
+ VERSION = "0.6.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_mesh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.4
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryant Morrill
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-09-29 00:00:00.000000000 Z
12
+ date: 2026-10-08 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: