cotton-tail 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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