cotton-tail 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: ea50d4559d3b22547293d0efb07f7841837e272b877a96646c948a2d125313a8
4
- data.tar.gz: 640b18d1aefc48686535b9f1f3685c38c5fc1724492192bb766dfdd39573d0ca
3
+ metadata.gz: a096975de51e023b80e464904906b993f74192d556b868810c752571697d8de4
4
+ data.tar.gz: 70cec1e974a049e58ca92370e646cf3adddf138bd03b4cc101bfa2956c0c5fcd
5
5
  SHA512:
6
- metadata.gz: 433de64af1c506e5f997e7cd24c70453110822662b6ba5139673b443bff235100a15d6dad2f1e3e23f0e429cab08dd927c0c47b614094b3f0160893ee6644541
7
- data.tar.gz: 18a12763f6a88ab0940c20850e1110b0a4305f69451f5ef2ef2c7a5f82ec1f59bf48e41dccc8e9740da3854314c3c82c4c84a50d0878ae94c662617c2fa434bc
6
+ metadata.gz: 5334e9b79d5d5762c5f98066866c5e08cd54a4ed0b051dfc6a3b68d3b1bee80a4800b734edc063e5043a2a2a56527997c65e6e1fb5d7ed9b7091e51ca137062b
7
+ data.tar.gz: d936c74e21b1570b283b513e8b5921215df7c1ebc31b95db9a57bb64cab742208cccf65c294c13a078d8d80b2a6cecc027ebf5a895315a7abb0a36904ff7d573
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/.rspec CHANGED
@@ -1,4 +1,5 @@
1
1
  --format documentation
2
2
  --color
3
3
  --require spec_helper
4
+ --require integration_helper
4
5
  -I integration
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- cotton-tail (0.5.0)
4
+ cotton-tail (0.6.0)
5
5
  bunny (~> 2.12)
6
6
  ibsciss-middleware (~> 0.4.2)
7
7
 
@@ -12,7 +12,7 @@ GEM
12
12
  ast (2.4.0)
13
13
  benchmark-perf (0.4.0)
14
14
  benchmark-trend (0.2.0)
15
- bunny (2.12.1)
15
+ bunny (2.13.0)
16
16
  amq-protocol (~> 2.3, >= 2.3.0)
17
17
  diff-lcs (1.3)
18
18
  effin_utf8 (1.0)
data/examples/app.rb CHANGED
@@ -25,6 +25,11 @@ app.routes.draw do
25
25
  puts request: request
26
26
  puts response: response
27
27
  end
28
+
29
+ handle 'send.*:gift.to.*:name' do |_env, request, _response|
30
+ gift, name = request.route_params.values_at('gift', 'name')
31
+ puts "#{gift} sent to #{name}!"
32
+ end
28
33
  end
29
34
 
30
35
  queue 'require_ack_queue', exclusive: true, manual_ack: true do
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env bash
2
+
3
+ read -r -d '' MESSAGE << EOM
4
+ {
5
+ "properties":{},
6
+ "routing_key":"send.kitten.to.tom",
7
+ "payload": "ignored",
8
+ "payload_encoding": "string"
9
+ }
10
+ EOM
11
+
12
+ curl -i -XPOST -d"${MESSAGE}" http://guest:guest@localhost:15672/api/exchanges/%2f/amq.topic/publish
data/lib/cotton_tail.rb CHANGED
@@ -7,26 +7,15 @@ module CottonTail
7
7
  autoload :App, 'cotton_tail/app'
8
8
  autoload :Configuration, 'cotton_tail/configuration'
9
9
  autoload :DSL, 'cotton_tail/dsl'
10
+ autoload :MessageProperties, 'cotton_tail/message_properties'
10
11
  autoload :Middleware, 'cotton_tail/middleware'
11
12
  autoload :Queue, 'cotton_tail/queue'
13
+ autoload :Request, 'cotton_tail/request'
12
14
  autoload :Route, 'cotton_tail/route'
15
+ autoload :RouteSegment, 'cotton_tail/route_segment'
13
16
  autoload :Router, 'cotton_tail/router'
14
17
  autoload :Version, 'cotton_tail/version'
15
18
 
16
- Request = Struct.new(:delivery_info, :properties, :payload) do
17
- def routing_key
18
- delivery_info[:routing_key]
19
- end
20
-
21
- def delivery_tag
22
- delivery_info[:delivery_tag]
23
- end
24
-
25
- def channel
26
- delivery_info[:channel]
27
- end
28
- end
29
-
30
19
  Response = Struct.new(:body)
31
20
 
32
21
  class RouteConflictError < StandardError
@@ -10,9 +10,9 @@ module CottonTail
10
10
  @context = context
11
11
  end
12
12
 
13
- def handle(key, handler = nil, &block)
14
- bind(key)
15
- @context.handle(key, handler, &block)
13
+ def handle(pattern, handler = nil, &block)
14
+ bind pattern
15
+ @context.handle(pattern, handler, &block)
16
16
  end
17
17
 
18
18
  def topic(routing_prefix, &block)
@@ -20,10 +20,10 @@ module CottonTail
20
20
  topic.instance_eval(&block)
21
21
  end
22
22
 
23
- def bind(key)
23
+ def bind(pattern)
24
24
  return unless @queue.respond_to?(:bind)
25
25
 
26
- @queue.bind key
26
+ @queue.bind pattern
27
27
  end
28
28
  end
29
29
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CottonTail
4
+ # Wrapper around Bunny MessageProperties, used to supply route params
5
+ class MessageProperties < Bunny::MessageProperties
6
+ def merge(properties)
7
+ self.class.new(@properties.merge(properties))
8
+ end
9
+
10
+ def route_params
11
+ @properties[:route_params]
12
+ end
13
+
14
+ def ==(other)
15
+ to_h == other.to_h
16
+ end
17
+ end
18
+ end
@@ -15,30 +15,50 @@ module CottonTail
15
15
 
16
16
  def call(message)
17
17
  env, req, = message
18
- @app.call [env, req, response(req.routing_key, message)]
18
+ @app.call [env, req, response(*message)]
19
19
  end
20
20
 
21
21
  private
22
22
 
23
- def route(routing_key)
24
- CottonTail::Route.new(routing_key)
25
- end
23
+ def response(env, req, res)
24
+ routing_key = req.routing_key
25
+ handler = lookup_handler(routing_key)
26
+ route = lookup_route(routing_key)
27
+ req = add_route_params(req, route) if route_params?(route, routing_key)
26
28
 
27
- def response(routing_key, message)
28
- CottonTail::Response.new handler(routing_key).call(message)
29
+ CottonTail::Response.new handler.call([env, req, res])
29
30
  end
30
31
 
31
32
  def routes(routing_key)
32
33
  handlers.keys.select { |route| route.match? routing_key }
33
34
  end
34
35
 
35
- def handler(routing_key)
36
+ def lookup_handler(routing_key)
37
+ handlers[lookup_route(routing_key)]
38
+ end
39
+
40
+ def lookup_route(routing_key)
36
41
  route, *conflicts = routes(routing_key)
37
42
  raise UndefinedRouteError if route.nil?
38
43
 
39
44
  raise RouteConflictError unless conflicts.empty?
40
45
 
41
- handlers[route]
46
+ route
47
+ end
48
+
49
+ def add_route_params(req, route)
50
+ delivery_info, properties, payload = req.to_a
51
+ Request.new(
52
+ delivery_info,
53
+ properties.merge(
54
+ route_params: route.extract_params(req.routing_key)
55
+ ),
56
+ payload
57
+ )
58
+ end
59
+
60
+ def route_params?(route, routing_key)
61
+ route.extract_params(routing_key) == {} || true
42
62
  end
43
63
  end
44
64
  end
@@ -30,11 +30,12 @@ module CottonTail
30
30
  end
31
31
 
32
32
  def pop
33
- Request.new(*super)
33
+ delivery_info, properties, payload = super
34
+ Request.new(delivery_info, MessageProperties.new(properties.to_h), payload)
34
35
  end
35
36
 
36
37
  def bind(routing_key)
37
- source.bind('amq.topic', routing_key: routing_key)
38
+ source.bind('amq.topic', routing_key: Route.new(routing_key).binding)
38
39
  end
39
40
 
40
41
  private
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CottonTail
4
+ # Value object wrapper for Bunny Message
5
+ class Request
6
+ extend Forwardable
7
+
8
+ attr_reader :delivery_info, :properties, :payload
9
+
10
+ def initialize(delivery_info, properties, payload)
11
+ @delivery_info = delivery_info
12
+ @properties = properties
13
+ @payload = payload
14
+ end
15
+
16
+ def to_a
17
+ [delivery_info, properties, payload]
18
+ end
19
+
20
+ def to_h
21
+ {
22
+ delivery_info: delivery_info,
23
+ properties: properties,
24
+ payload: payload
25
+ }
26
+ end
27
+
28
+ def ==(other)
29
+ to_h == other.to_h
30
+ end
31
+
32
+ def_delegators :delivery_info, :routing_key, :delivery_tag, :channel
33
+ def_delegators :properties, :route_params
34
+ end
35
+ end
@@ -1,36 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CottonTail
4
- # Route value object
5
- class Route
6
- attr_reader :pattern
7
-
4
+ # Route pattern matcher
5
+ class Route < SimpleDelegator
8
6
  def initialize(pattern)
9
7
  @pattern = pattern
8
+ super build_regex
10
9
  end
11
10
 
12
- def match?(routing_key)
13
- return true if @pattern == routing_key
11
+ def extract_params(routing_key)
12
+ return {} unless match? routing_key
14
13
 
15
- regex.match? routing_key
14
+ match(routing_key).named_captures
16
15
  end
17
16
 
18
- def to_s
19
- @pattern
17
+ def binding
18
+ segments.map(&:binding).join('.')
20
19
  end
21
20
 
22
21
  private
23
22
 
24
- def regex
25
- @regex ||= Regexp.new build_regex(@pattern)
23
+ def explode
24
+ @pattern.split('.').map(&RouteSegment.method(:new))
25
+ end
26
+
27
+ def collapse
28
+ segments.zip(separators).join
29
+ end
30
+
31
+ def segments
32
+ @segments ||= explode
33
+ end
34
+
35
+ def separators
36
+ separators = segments.each_with_index.map do |segment, idx|
37
+ [Regexp.escape('.')].tap do |sep|
38
+ sep << '?' if segment.hash? && idx.zero?
39
+ end
40
+ end
41
+ separators.map(&:join)[0..-2]
26
42
  end
27
43
 
28
- def build_regex(pattern)
29
- [
30
- '^',
31
- pattern.gsub('*', '([^.]+)').gsub(/\.?#\.?/, '([^.]{0,}\.?)+'),
32
- '$'
33
- ].join
44
+ def build_regex
45
+ Regexp.new "^#{collapse}$"
34
46
  end
35
47
  end
36
48
  end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CottonTail
4
+ # RouteSegment implements the pattern matching for route segments
5
+ class RouteSegment < SimpleDelegator
6
+ def initialize(value)
7
+ @value = value
8
+ super Regexp.new definition(value)
9
+ end
10
+
11
+ def star?
12
+ /^#{STAR}|#{NAMED_STAR}$/.match? @value
13
+ end
14
+
15
+ def hash?
16
+ /^#{HASH}|#{NAMED_HASH}$/.match? @value
17
+ end
18
+
19
+ def binding
20
+ return '*' if star?
21
+
22
+ return '#' if hash?
23
+
24
+ @value
25
+ end
26
+
27
+ private
28
+
29
+ TRANSFORM = ->(val, func) { func.call(val) }
30
+
31
+ def definition(value)
32
+ transformers.reduce(value, &TRANSFORM)
33
+ end
34
+
35
+ # Converts named route segment to Regexp named capture group
36
+ # "#:foo" -> "(?<foo>.+)"
37
+ def sub_named_group_wildcard(pattern)
38
+ pattern.gsub(NAMED_HASH, '(?<\1>.+)')
39
+ end
40
+
41
+ # Converts named route segment to Regexp named capture group
42
+ # "*:foo" -> "(?<foo>[^.]+)"
43
+ def sub_named_single_wildcard(pattern)
44
+ pattern.gsub(NAMED_STAR, '(?<\1>[^.]+)')
45
+ end
46
+
47
+ def sub_single_wildcard(pattern)
48
+ pattern.gsub(STAR, '([^.]+)')
49
+ end
50
+
51
+ def sub_multi_wildcard(pattern)
52
+ pattern.gsub(HASH, '([^.]{0,}\.?)+')
53
+ end
54
+
55
+ def transformers
56
+ [
57
+ method(:sub_named_group_wildcard),
58
+ method(:sub_named_single_wildcard),
59
+ method(:sub_single_wildcard),
60
+ method(:sub_multi_wildcard)
61
+ ]
62
+ end
63
+
64
+ STAR = /\*/.freeze
65
+ HASH = /#/.freeze
66
+ NAMED = /:(\w+)/.freeze
67
+ NAMED_STAR = /#{STAR}#{NAMED}/.freeze
68
+ NAMED_HASH = /#{HASH}#{NAMED}/.freeze
69
+ end
70
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CottonTail
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: cotton-tail
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
  - James Brennan
@@ -30,7 +30,7 @@ cert_chain:
30
30
  fXe/xr/Sc+2wCjHPVE2J+auN5hk3KCp1I4s2fKqyLIwyhTEF3shuYfCpC8rt/YdN
31
31
  cy9/lg5LCI3OvakzxL4Xt1Sq4h/xJZ06ydTVJ1wxfk6BXHrg
32
32
  -----END CERTIFICATE-----
33
- date: 2018-12-13 00:00:00.000000000 Z
33
+ date: 2019-01-16 00:00:00.000000000 Z
34
34
  dependencies:
35
35
  - !ruby/object:Gem::Dependency
36
36
  name: bunny
@@ -205,6 +205,7 @@ files:
205
205
  - examples/messages/intercept.with.middleware
206
206
  - examples/messages/say.goodbye
207
207
  - examples/messages/say.hello
208
+ - examples/messages/send.kitten.to.tom
208
209
  - lib/cotton_tail.rb
209
210
  - lib/cotton_tail/app.rb
210
211
  - lib/cotton_tail/configuration.rb
@@ -212,6 +213,7 @@ files:
212
213
  - lib/cotton_tail/dsl/queue.rb
213
214
  - lib/cotton_tail/dsl/routes.rb
214
215
  - lib/cotton_tail/dsl/topic.rb
216
+ - lib/cotton_tail/message_properties.rb
215
217
  - lib/cotton_tail/middleware.rb
216
218
  - lib/cotton_tail/middleware/router.rb
217
219
  - lib/cotton_tail/queue.rb
@@ -219,7 +221,9 @@ files:
219
221
  - lib/cotton_tail/queue/memory.rb
220
222
  - lib/cotton_tail/queue/reader.rb
221
223
  - lib/cotton_tail/queue/supervisor.rb
224
+ - lib/cotton_tail/request.rb
222
225
  - lib/cotton_tail/route.rb
226
+ - lib/cotton_tail/route_segment.rb
223
227
  - lib/cotton_tail/version.rb
224
228
  homepage: https://github.com/jamesBrennan/cotton-tail
225
229
  licenses:
metadata.gz.sig CHANGED
Binary file