service_mesh 0.5.0 → 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: edccbfa1e181934bed8df91ecb53d15849335cb10730450e088fee1e28ac44ef
4
- data.tar.gz: 64cbe1ba2b464e7ccd1dc840066eedf2dd0cd4639804af86e0fde65615cf064c
3
+ metadata.gz: 643fe078e4376b1f4d19e3e45e0dbbe7e8b61e620e96591f67c7b0ed18865a50
4
+ data.tar.gz: 57c88b02dd056895234d1536b16f00973202cdec9b6600f7ab31621452e52d93
5
5
  SHA512:
6
- metadata.gz: bdff9e8e86405e3fcca5df769e800d317df47307d4974c88f9161728c165242ecfa7acf72880bfb41b2606f310c30a6fd6cf35dbb56d22dab202d27e1201725f
7
- data.tar.gz: c3513ecbc6ae3832509419f8f3b9e9c93c637199eb6d8fd9b9183ef4046bf75d53dd3cc1cc73ee6d16dcaa2b116626956068ce3e3569404aba69bb90e0bc3e7a
6
+ metadata.gz: 56f15b80a1db1ad69e0e95105e8ed221b873b4ae762ff85296e576ad7cb1625b22209d5bf13aad4e85b903a2144b08bd9f6bdfef0169b49860f6c3350ebcdc4d
7
+ data.tar.gz: 7be31a262f4c7cf55b5a2f7661c69baf7922e2261a5924a59537bbb3e0c0c3141eb0247977aca65941ed033a5eeb6dd66f5a900978e980d4a28d66cff6cc3aff
data/README.md CHANGED
@@ -26,6 +26,9 @@ Requires Ruby 3.3 or newer. The gem has no runtime dependencies.
26
26
  - The configuration keys the specification defines. `DEPLOYMENT_GROUP_KEY` is runtime
27
27
  configuration. `CONSUMER_GROUP_KEY`, with the value `CONSUMER_GROUP_NONE`, is `Endpoint` and
28
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`.
29
32
  - The errors defined by the specification as `ServiceMesh::Error`: `ServiceMesh::KindMismatch`,
30
33
  `ServiceMesh::InvalidTarget`, and `ServiceMesh::NoDeploymentGroup`.
31
34
  - A conformance suite a transport runs against its own `Client` and
@@ -73,6 +76,18 @@ RSpec.describe MyTransport do
73
76
  end
74
77
  ```
75
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
+
76
91
  The suite covers:
77
92
 
78
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
@@ -168,7 +186,7 @@ RSpec.shared_examples "a service mesh transport" do
168
186
  serve(config_for("same"), subscribers: [subscriber(t, ->(_) { count << true })])
169
187
  end
170
188
 
171
- client.publish(message(topic_target))
189
+ publish(message(topic_target))
172
190
 
173
191
  wait_until { count.size >= 1 }
174
192
  sleep 0.1 # let an unwanted extra delivery show up
@@ -181,9 +199,9 @@ RSpec.shared_examples "a service mesh transport" do
181
199
  serve(endpoints: [endpoint(route_target, ->(m) { m })])
182
200
  own = new_client.call(client_config, service_map)
183
201
 
184
- expect(own.request(message(route_target)).payload).to eq("")
202
+ expect(request(message(route_target), via: own).payload).to eq("")
185
203
  own.close
186
- expect { own.request(message(route_target)) }.to raise_error(StandardError)
204
+ expect { request(message(route_target), via: own) }.to raise_error(StandardError)
187
205
  end
188
206
  end
189
207
 
@@ -197,9 +215,9 @@ RSpec.shared_examples "a service mesh transport" do
197
215
  expect(rt.client).to equal(given)
198
216
  expect(rt.service_map).to equal(service_map)
199
217
  rt.start
200
- expect(given.request(req).payload).to eq("")
218
+ expect(request(req, via: given).payload).to eq("")
201
219
  rt.stop(1)
202
- expect { given.request(req) }.to raise_error(StandardError)
220
+ expect { request(req, via: given) }.to raise_error(StandardError)
203
221
  end
204
222
  end
205
223
 
@@ -234,7 +252,7 @@ RSpec.shared_examples "a service mesh transport" do
234
252
  message(route_target, payload: "done")
235
253
  })])
236
254
 
237
- reply = Thread.new { client.request(message(route_target)) }
255
+ reply = Thread.new { request(message(route_target)) }
238
256
  @conformance_threads << reply
239
257
  Timeout.timeout(wait) { entered.pop }
240
258
 
@@ -256,7 +274,7 @@ RSpec.shared_examples "a service mesh transport" do
256
274
  message(route_target)
257
275
  })])
258
276
 
259
- @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
260
278
  Timeout.timeout(wait) { entered.pop }
261
279
 
262
280
  expect(rt.stop(0.1)).to be(false)
@@ -12,6 +12,35 @@ module ServiceMesh
12
12
  CONSUMER_GROUP_KEY = "consumer_group"
13
13
  CONSUMER_GROUP_NONE = "none"
14
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
+
15
44
  # Identifies a receiving channel on the mesh. A transport assembles the
16
45
  # segments into its own address; that string never leaves the transport.
17
46
  Target = Data.define(:segments, :kind, :metadata) do
@@ -34,7 +63,9 @@ module ServiceMesh
34
63
  end
35
64
  end
36
65
 
37
- # 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.
38
69
  Message = Data.define(:target, :metadata, :payload) do
39
70
  def initialize(target:, metadata: {}, payload: "")
40
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.5.0"
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.5.0
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-30 00:00:00.000000000 Z
12
+ date: 2026-10-08 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description:
15
15
  email: