startback 0.11.4 → 0.12.1

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,41 @@
1
+ module Startback
2
+ module Support
3
+ # This method provides the `env` and `env!` methods that
4
+ # help querying environment variables easily.
5
+ module Env
6
+
7
+ # Returns an environment variable or raise an error if
8
+ # not set.
9
+ #
10
+ # The result is always a String with no leading/trailing
11
+ # spaces.
12
+ #
13
+ # If a block is given, the environment variable is yield
14
+ # and the result of the block returned.
15
+ def env!(key, default = nil, &bl)
16
+ v = ENV[key].to_s.strip
17
+ raise Startback::Error, "Missing ENV var `#{key}`" if v.empty?
18
+
19
+ env(key, default, &bl)
20
+ end
21
+ module_function :env!
22
+
23
+ # Returns an environment variable or the default value
24
+ # passed as second argument.
25
+ #
26
+ # The result is always a String with no leading/trailing
27
+ # spaces.
28
+ #
29
+ # If a block is given, the environment variable is yield
30
+ # and the result of the block returned.
31
+ def env(key, default = nil, &bl)
32
+ v = ENV[key].to_s.strip
33
+ v = v.empty? ? default : v
34
+ v = bl.call(v) if bl && v
35
+ v
36
+ end
37
+ module_function :env
38
+
39
+ end # module Env
40
+ end # module Support
41
+ end # module Startback
@@ -43,7 +43,7 @@ module Startback
43
43
  @@default_logger ||= begin
44
44
  l = ::Logger.new(STDOUT)
45
45
  l.formatter = LogFormatter.new
46
- l.warn(op: "#{self}", op_data: { msg: "Using default logger", trace: caller })
46
+ l.warn(op: "#{self}", op_data: { msg: "Using default logger to STDOUT" })
47
47
  @@default_logger = l
48
48
  end
49
49
  @@default_logger
@@ -107,8 +107,6 @@ module Startback
107
107
  }
108
108
  Tools.info(args, op_took: took)
109
109
  result
110
- rescue => ex
111
- raise
112
110
  end
113
111
 
114
112
  # Executes the block without letting errors propagate.
@@ -14,6 +14,7 @@ module Startback
14
14
 
15
15
  end # module Support
16
16
  end # module Startback
17
+ require_relative 'support/env'
17
18
  require_relative 'support/log_formatter'
18
19
  require_relative 'support/logger'
19
20
  require_relative 'support/robustness'
@@ -1,8 +1,8 @@
1
1
  module Startback
2
2
  module Version
3
3
  MAJOR = 0
4
- MINOR = 11
5
- TINY = 4
4
+ MINOR = 12
5
+ TINY = 1
6
6
  end
7
7
  VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::TINY}"
8
8
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
2
  require 'startback'
3
- require 'startback/bus'
3
+ require 'startback/event'
4
4
  require 'startback/support/fake_logger'
5
5
  require 'rack/test'
6
6
 
@@ -10,3 +10,8 @@ end
10
10
  RSpec.configure do |c|
11
11
  c.include SpecHelpers
12
12
  end
13
+
14
+ class User
15
+ class Changed < Startback::Event
16
+ end
17
+ end
@@ -10,18 +10,18 @@ module Startback
10
10
  include Rack::Test::Methods
11
11
 
12
12
  def app
13
- opts = middleware_options
13
+ build_args = self.build_args
14
14
  Rack::Builder.new do
15
- use Middleware, opts
15
+ use Middleware, *build_args
16
16
  run ->(env){
17
- ctx = env[Startback::Context::Middleware::RACK_ENV_KEY]
17
+ ctx = env[Startback::Context::Middleware::RACK_ENV_KEY]
18
18
  [200, {}, ctx.class.to_s]
19
19
  }
20
20
  end
21
21
  end
22
22
 
23
- context 'when used without option' do
24
- let(:middleware_options){ nil }
23
+ context 'when used without context' do
24
+ let(:build_args){ [] }
25
25
 
26
26
  it 'sets the default context class' do
27
27
  get '/'
@@ -31,9 +31,7 @@ module Startback
31
31
  end
32
32
 
33
33
  context 'when specifying the context class' do
34
- let(:middleware_options){{
35
- context_class: MyContextSubClass
36
- }}
34
+ let(:build_args){ [MyContextSubClass.new] }
37
35
 
38
36
  it 'sets the default context class' do
39
37
  get '/'
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ class Event
4
+ describe Bus::Memory do
5
+
6
+ subject{
7
+ Bus::Memory::Async.new
8
+ }
9
+
10
+ it 'allows emiting an receiving' do
11
+ seen = nil
12
+ subject.listen("user_changed") do |evt|
13
+ seen = evt
14
+ end
15
+ subject.emit(Event.new("user_changed", {id: 12}))
16
+ expect(seen).to be_a(Event)
17
+ expect(seen.type).to eql("user_changed")
18
+ expect(seen.data.to_h).to eql({id: 12})
19
+ end
20
+
21
+ it 'allows mixin Symbol vs. String for event type' do
22
+ seen = nil
23
+ subject.listen(:user_changed) do |evt|
24
+ seen = evt
25
+ end
26
+ subject.emit(Event.new(:user_changed, {id: 12}))
27
+ expect(seen).to be_a(Event)
28
+ expect(seen.type).to eql("user_changed")
29
+ expect(seen.data.to_h).to eql({id: 12})
30
+ end
31
+
32
+ it 'does not raise errors synchronously' do
33
+ subject.listen("user_changed") do |evt|
34
+ raise "An error occured"
35
+ end
36
+ expect {
37
+ subject.emit(Event.new("user_changed", {id: 12}))
38
+ }.not_to raise_error
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,43 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ class Event
4
+ describe Bus::Memory do
5
+
6
+ subject{
7
+ Bus::Memory::Sync.new
8
+ }
9
+
10
+ it 'allows emiting an receiving' do
11
+ seen = nil
12
+ subject.listen("user_changed") do |evt|
13
+ seen = evt
14
+ end
15
+ subject.emit(Event.new("user_changed", {id: 12}))
16
+ expect(seen).to be_a(Event)
17
+ expect(seen.type).to eql("user_changed")
18
+ expect(seen.data.to_h).to eql({id: 12})
19
+ end
20
+
21
+ it 'allows mixin Symbol vs. String for event type' do
22
+ seen = nil
23
+ subject.listen(:user_changed) do |evt|
24
+ seen = evt
25
+ end
26
+ subject.emit(Event.new(:user_changed, {id: 12}))
27
+ expect(seen).to be_a(Event)
28
+ expect(seen.type).to eql("user_changed")
29
+ expect(seen.data.to_h).to eql({id: 12})
30
+ end
31
+
32
+ it 'raises emit errors synchronously' do
33
+ subject.listen("user_changed") do |evt|
34
+ raise "An error occured"
35
+ end
36
+ expect {
37
+ subject.emit(Event.new("user_changed", {id: 12}))
38
+ }.to raise_error("An error occured")
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,75 @@
1
+ require 'spec_helper'
2
+ module Startback
3
+ module Support
4
+ describe Env do
5
+ include Env
6
+
7
+ before do
8
+ ENV['FOO'] = 'BAR'
9
+ ENV['FOOL'] = ''
10
+ ENV['FOOLISH'] = ' BAR '
11
+ end
12
+
13
+ after do
14
+ ENV.delete('FOO')
15
+ ENV.delete('FOOL')
16
+ end
17
+
18
+ describe "env" do
19
+ it 'returns an env variable' do
20
+ expect(env('FOO')).to eql('BAR')
21
+ end
22
+
23
+ it 'returns nil otherwise' do
24
+ expect(env('BAR')).to be_nil
25
+ end
26
+
27
+ it 'strips the value' do
28
+ expect(env('FOOLISH')).to eql('BAR')
29
+ end
30
+
31
+ it 'yields the block if any' do
32
+ expect(env('FOO'){|x| x.downcase }).to eql('bar')
33
+ end
34
+
35
+ it 'support a default value' do
36
+ expect(env('BAR', 'BAZ')).to eql('BAZ')
37
+ end
38
+
39
+ it 'yields the block with the default if any' do
40
+ expect(env('BAR', 'BAZ'){|x| x.downcase }).to eql('baz')
41
+ end
42
+
43
+ it 'returns nil when empty' do
44
+ expect(env('FOOL')).to be_nil
45
+ end
46
+
47
+ it 'yields the block with the default if empty' do
48
+ expect(env('FOOL', 'BAZ'){|x| x.downcase }).to eql('baz')
49
+ end
50
+ end
51
+
52
+ describe "env!" do
53
+ it 'returns an env variable' do
54
+ expect(env!('FOO')).to eql('BAR')
55
+ end
56
+
57
+ it 'strips the value' do
58
+ expect(env!('FOOLISH')).to eql('BAR')
59
+ end
60
+
61
+ it 'raise otherwise' do
62
+ expect{ env!('BAR') }.to raise_error(Startback::Errors::Error, /BAR/)
63
+ end
64
+
65
+ it 'raise on empty' do
66
+ expect{ env!('FOOL') }.to raise_error(Startback::Errors::Error, /FOOL/)
67
+ end
68
+
69
+ it 'yields the block if any' do
70
+ expect(env('FOO'){|x| x.downcase }).to eql('bar')
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -4,7 +4,7 @@ module Startback
4
4
  describe Event do
5
5
 
6
6
  subject{
7
- Event.new("user_changed", { "foo" => "bar" })
7
+ Event.new("User::Changed", { "foo" => "bar" })
8
8
  }
9
9
 
10
10
  it 'presents an ostruct on top of its data' do
@@ -15,7 +15,7 @@ module Startback
15
15
 
16
16
  JSON_SRC = <<-JSON.gsub(/\s+/, "")
17
17
  {
18
- "type": "user_changed",
18
+ "type": "User::Changed",
19
19
  "data": {
20
20
  "foo": "bar"
21
21
  }
@@ -27,10 +27,10 @@ module Startback
27
27
  end
28
28
 
29
29
  it 'has a to_json that dumps the context if any' do
30
- evt = Event.new("user_changed", { "foo" => "bar" }, { "baz": "context" })
30
+ evt = Event.new("User::Changed", { "foo" => "bar" }, { "baz": "context" })
31
31
  expect(evt.to_json).to eql(<<-JSON.gsub(/\s+/, ""))
32
32
  {
33
- "type": "user_changed",
33
+ "type": "User::Changed",
34
34
  "data": {
35
35
  "foo": "bar"
36
36
  },
@@ -43,26 +43,16 @@ module Startback
43
43
 
44
44
 
45
45
  it 'has a json class method that works as expected' do
46
- evt = Event.json(JSON_SRC)
46
+ evt = Event.json(JSON_SRC, nil)
47
47
  expect(evt).to be_a(Event)
48
- expect(evt.type).to eql("user_changed")
48
+ expect(evt.type).to eql("User::Changed")
49
49
  expect(evt.data).to eql(subject.data)
50
50
  end
51
51
 
52
- it 'accepts an explicit context in the world' do
53
- evt = Event.json(JSON_SRC, context: 12)
52
+ it 'accepts an explicit context as second argument' do
53
+ evt = Event.json(JSON_SRC, 12)
54
54
  expect(evt.context).to eql(12)
55
55
  end
56
-
57
- it 'accepts an context factory in the world' do
58
- cf = ->(arg) {
59
- expect(arg).to eql(JSON.parse(JSON_SRC))
60
- 12
61
- }
62
- evt = Event.json(JSON_SRC, context_factory: cf)
63
- expect(evt.context).to eql(12)
64
- end
65
-
66
56
  end
67
57
 
68
58
  end
@@ -53,7 +53,7 @@ module Startback
53
53
 
54
54
  def app
55
55
  Rack::Builder.new do
56
- use Context::Middleware, context_class: MyContextWithErrorHandler
56
+ use Context::Middleware, MyContextWithErrorHandler.new
57
57
  use CatchAll
58
58
  run ->(env){ raise AnError, "Hello error" }
59
59
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: startback
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.4
4
+ version: 0.12.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bernard Lambeau
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-05-18 00:00:00.000000000 Z
11
+ date: 2022-05-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -385,12 +385,6 @@ files:
385
385
  - lib/startback/audit/prometheus.rb
386
386
  - lib/startback/audit/shared.rb
387
387
  - lib/startback/audit/trailer.rb
388
- - lib/startback/bus.rb
389
- - lib/startback/bus/bunny.rb
390
- - lib/startback/bus/bunny/async.rb
391
- - lib/startback/bus/memory.rb
392
- - lib/startback/bus/memory/async.rb
393
- - lib/startback/bus/memory/sync.rb
394
388
  - lib/startback/caching/entity_cache.rb
395
389
  - lib/startback/caching/no_store.rb
396
390
  - lib/startback/caching/store.rb
@@ -398,6 +392,16 @@ files:
398
392
  - lib/startback/context/middleware.rb
399
393
  - lib/startback/errors.rb
400
394
  - lib/startback/event.rb
395
+ - lib/startback/event/agent.rb
396
+ - lib/startback/event/bus.rb
397
+ - lib/startback/event/bus/bunny.rb
398
+ - lib/startback/event/bus/bunny/async.rb
399
+ - lib/startback/event/bus/memory.rb
400
+ - lib/startback/event/bus/memory/async.rb
401
+ - lib/startback/event/bus/memory/sync.rb
402
+ - lib/startback/event/engine.rb
403
+ - lib/startback/event/ext/context.rb
404
+ - lib/startback/event/ext/operation.rb
401
405
  - lib/startback/ext.rb
402
406
  - lib/startback/ext/date_time.rb
403
407
  - lib/startback/ext/time.rb
@@ -405,6 +409,7 @@ files:
405
409
  - lib/startback/operation/error_operation.rb
406
410
  - lib/startback/operation/multi_operation.rb
407
411
  - lib/startback/support.rb
412
+ - lib/startback/support/env.rb
408
413
  - lib/startback/support/fake_logger.rb
409
414
  - lib/startback/support/hooks.rb
410
415
  - lib/startback/support/log_formatter.rb
@@ -428,17 +433,18 @@ files:
428
433
  - spec/spec_helper.rb
429
434
  - spec/unit/audit/test_prometheus.rb
430
435
  - spec/unit/audit/test_trailer.rb
431
- - spec/unit/bus/memory/test_async.rb
432
- - spec/unit/bus/memory/test_sync.rb
433
436
  - spec/unit/caching/test_entity_cache.rb
434
437
  - spec/unit/context/test_abstraction_factory.rb
435
438
  - spec/unit/context/test_dup.rb
436
439
  - spec/unit/context/test_h_factory.rb
437
440
  - spec/unit/context/test_middleware.rb
441
+ - spec/unit/event/bus/memory/test_async.rb
442
+ - spec/unit/event/bus/memory/test_sync.rb
438
443
  - spec/unit/support/hooks/test_after_hook.rb
439
444
  - spec/unit/support/hooks/test_before_hook.rb
440
445
  - spec/unit/support/operation_runner/test_around_run.rb
441
446
  - spec/unit/support/operation_runner/test_before_after_call.rb
447
+ - spec/unit/support/test_env.rb
442
448
  - spec/unit/support/test_robusteness.rb
443
449
  - spec/unit/support/test_transaction_manager.rb
444
450
  - spec/unit/test_event.rb
@@ -1,123 +0,0 @@
1
- require 'bunny'
2
- module Startback
3
- class Bus
4
- module Bunny
5
- #
6
- # Asynchronous implementation of the bus abstraction, on top of RabbitMQ
7
- # and using the 'bunny' gem (you need to include it in your Gemfile
8
- # yourself: it is NOT a startback official dependency).
9
- #
10
- # This bus implementation emits events by dumping them to RabbitMQ using
11
- # the event type as exchange name. Listeners may use the `processor`
12
- # parameter to specify the queue name ; otherwise a default "main" queue
13
- # is used.
14
- #
15
- # Examples:
16
- #
17
- # # Connects to RabbitMQ using all default options
18
- # #
19
- # # Uses the STARTBACK_BUS_BUNNY_ASYNC_URL environment variable for
20
- # # connection URL if present.
21
- # Startback::Bus::Bunny::Async.new
22
- #
23
- # # Connects to RabbitMQ using a specific URL
24
- # Startback::Bus::Bunny::Async.new("amqp://rabbituser:rabbitpass@192.168.17.17")
25
- # Startback::Bus::Bunny::Async.new(url: "amqp://rabbituser:rabbitpass@192.168.17.17")
26
- #
27
- # # Connects to RabbitMQ using specific connection options. See Bunny's own
28
- # # documentation
29
- # Startback::Bus::Bunny::Async.new({
30
- # connection_options: {
31
- # host: "192.168.17.17"
32
- # }
33
- # })
34
- #
35
- class Async
36
- include Support::Robustness
37
-
38
- CHANNEL_KEY = 'Startback::Bus::Bunny::Async::ChannelKey'
39
-
40
- DEFAULT_OPTIONS = {
41
- # (optional) The URL to use for connecting to RabbitMQ.
42
- url: ENV['STARTBACK_BUS_BUNNY_ASYNC_URL'],
43
-
44
- # (optional) The options has to pass to ::Bunny constructor
45
- connection_options: nil,
46
-
47
- # (optional) The options to use for the emitter/listener fanout
48
- fanout_options: {},
49
-
50
- # (optional) The options to use for the listener queue
51
- queue_options: {},
52
-
53
- # (optional) Default event factory to use, if any
54
- event_factory: nil,
55
-
56
- # (optional) A default context to use for general logging
57
- context: nil
58
- }
59
-
60
- # Creates a bus instance, using the various options provided to
61
- # fine-tune behavior.
62
- def initialize(options = {})
63
- options = { url: options } if options.is_a?(String)
64
- @options = DEFAULT_OPTIONS.merge(options)
65
- retried = 0
66
- conn = options[:connection_options] || options[:url]
67
- try_max_times(10) do
68
- @bunny = ::Bunny.new(conn)
69
- @bunny.start
70
- channel
71
- log(:info, {op: "#{self.class.name}#connect", op_data: conn}, options[:context])
72
- end
73
- end
74
- attr_reader :options
75
-
76
- def channel
77
- Thread.current[CHANNEL_KEY] ||= @bunny.create_channel
78
- end
79
-
80
- def emit(event)
81
- stop_errors(self, "emit", event.context) do
82
- fanout = channel.fanout(event.type.to_s, fanout_options)
83
- fanout.publish(event.to_json)
84
- end
85
- end
86
-
87
- def listen(type, processor = nil, listener = nil, &bl)
88
- raise ArgumentError, "A listener must be provided" unless listener || bl
89
- fanout = channel.fanout(type.to_s, fanout_options)
90
- queue = channel.queue((processor || "main").to_s, queue_options)
91
- queue.bind(fanout)
92
- queue.subscribe do |delivery_info, properties, body|
93
- event = stop_errors(self, "listen") do
94
- factor_event(body)
95
- end
96
- stop_errors(self, "listen", event.context) do
97
- (listener || bl).call(event)
98
- end
99
- end
100
- end
101
-
102
- protected
103
-
104
- def fanout_options
105
- options[:fanout_options]
106
- end
107
-
108
- def queue_options
109
- options[:queue_options]
110
- end
111
-
112
- def factor_event(body)
113
- if options[:event_factory]
114
- options[:event_factory].call(body)
115
- else
116
- Event.json(body, options)
117
- end
118
- end
119
-
120
- end # class Async
121
- end # module Bunny
122
- end # class Bus
123
- end # module Klaro
@@ -1,40 +0,0 @@
1
- module Startback
2
- class Bus
3
- module Memory
4
- #
5
- # Asynchronous implementation of the Bus abstraction, for use between
6
- # components sharing the same process.
7
- #
8
- # This implementation actually calls listeners synchronously (it mays)
9
- # but hides error raised by them. See Bus::Bunny::Async for another
10
- # implementation that is truly asynchronous and relies on RabbitMQ.
11
- #
12
- class Async
13
- include Support::Robustness
14
-
15
- DEFAULT_OPTIONS = {
16
- }
17
-
18
- def initialize(options = {})
19
- @options = DEFAULT_OPTIONS.merge(options)
20
- @listeners = {}
21
- end
22
-
23
- def emit(event)
24
- (@listeners[event.type.to_s] || []).each do |l|
25
- stop_errors(self, "emit", event) {
26
- l.call(event)
27
- }
28
- end
29
- end
30
-
31
- def listen(type, processor = nil, listener = nil, &bl)
32
- raise ArgumentError, "A listener must be provided" unless listener || bl
33
- @listeners[type.to_s] ||= []
34
- @listeners[type.to_s] << (listener || bl)
35
- end
36
-
37
- end # class Sync
38
- end # module Memory
39
- end # class Bus
40
- end # module Klaro
@@ -1,30 +0,0 @@
1
- module Startback
2
- class Bus
3
- module Memory
4
- #
5
- # Synchronous implementation of the Bus abstraction, for use between
6
- # components sharing the same process.
7
- #
8
- class Sync
9
- include Support::Robustness
10
-
11
- def initialize
12
- @listeners = {}
13
- end
14
-
15
- def emit(event)
16
- (@listeners[event.type.to_s] || []).each do |l|
17
- l.call(event)
18
- end
19
- end
20
-
21
- def listen(type, processor = nil, listener = nil, &bl)
22
- raise ArgumentError, "A listener must be provided" unless listener || bl
23
- @listeners[type.to_s] ||= []
24
- @listeners[type.to_s] << (listener || bl)
25
- end
26
-
27
- end # class Sync
28
- end # module Memory
29
- end # class Bus
30
- end # module Klaro