and-son 0.6.1 → 0.7.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.
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
19
  gem.require_paths = ["lib"]
20
20
 
21
- gem.add_dependency("sanford-protocol", ["~> 0.9"])
21
+ gem.add_dependency("sanford-protocol", ["~> 0.10"])
22
22
 
23
23
  gem.add_development_dependency("assert", ["~> 2.12"])
24
24
  end
@@ -0,0 +1,142 @@
1
+ require 'benchmark'
2
+ require 'logger'
3
+ require 'sanford-protocol'
4
+ require 'and-son/connection'
5
+ require 'and-son/response'
6
+
7
+ module AndSon
8
+
9
+ class CallRunner
10
+
11
+ DEFAULT_TIMEOUT = 60 # seconds
12
+
13
+ attr_reader :host, :port
14
+ attr_accessor :timeout_value, :params_value, :logger_value
15
+
16
+ def initialize(host, port)
17
+ @host = host
18
+ @port = port
19
+ @params_value = {}
20
+ @timeout_value = (ENV['ANDSON_TIMEOUT'] || DEFAULT_TIMEOUT).to_f
21
+ @logger_value = NullLogger.new
22
+ end
23
+
24
+ # chain runner methods by returning itself
25
+ def call_runner; self; end
26
+
27
+ def call(name, params = nil)
28
+ params ||= {}
29
+ if !params.kind_of?(Hash)
30
+ raise ArgumentError, "expected params to be a Hash instead of a #{params.class}"
31
+ end
32
+ client_response = nil
33
+ benchmark = Benchmark.measure do
34
+ client_response = self.call!(name, params)
35
+ end
36
+
37
+ summary_line = SummaryLine.new({
38
+ 'time' => RoundedTime.new(benchmark.real),
39
+ 'status' => client_response.protocol_response.code,
40
+ 'host' => "#{self.host}:#{self.port}",
41
+ 'service' => name,
42
+ 'params' => params
43
+ })
44
+ self.logger_value.info("[AndSon] #{summary_line}")
45
+
46
+ if block_given?
47
+ yield client_response.protocol_response
48
+ else
49
+ client_response.data
50
+ end
51
+ end
52
+
53
+ def call!(name, params)
54
+ call_params = self.params_value.merge(params)
55
+ AndSon::Connection.new(host, port).open do |connection|
56
+ connection.write(Sanford::Protocol::Request.new(name, call_params).to_hash)
57
+ connection.close_write
58
+ if !connection.peek(timeout_value).empty?
59
+ AndSon::Response.parse(connection.read(timeout_value))
60
+ else
61
+ raise AndSon::ConnectionClosedError.new
62
+ end
63
+ end
64
+ end
65
+
66
+ def hash
67
+ [ self.host,
68
+ self.port,
69
+ self.timeout_value,
70
+ self.params_value,
71
+ self.logger_value
72
+ ].hash
73
+ end
74
+
75
+ def ==(other)
76
+ other.kind_of?(self.class) ? self.hash == other.hash : super
77
+ end
78
+ alias :eql? :==
79
+
80
+ module InstanceMethods
81
+
82
+ # define methods here to allow configuring call runner params. be sure to
83
+ # use `tap` to return whatever instance `self.call_runner` returns so you
84
+ # can method-chain. `self.call_runner` returns a new runner instance if
85
+ # called on a client, but returns the chained instance if called on a runner
86
+
87
+ def timeout(seconds)
88
+ self.call_runner.tap{ |r| r.timeout_value = seconds.to_f }
89
+ end
90
+
91
+ def params(hash = nil)
92
+ if !hash.kind_of?(Hash)
93
+ raise ArgumentError, "expected params to be a Hash instead of a #{hash.class}"
94
+ end
95
+ self.call_runner.tap{ |r| r.params_value.merge!(stringify_keys(hash)) }
96
+ end
97
+
98
+ def logger(passed_logger)
99
+ self.call_runner.tap{ |r| r.logger_value = passed_logger }
100
+ end
101
+
102
+ private
103
+
104
+ def stringify_keys(hash)
105
+ hash.inject({}){|h, (k, v)| h.merge({ k.to_s => v }) }
106
+ end
107
+
108
+ end
109
+ include InstanceMethods
110
+
111
+ module SummaryLine
112
+ def self.new(line_attrs)
113
+ attr_keys = %w{time status host service params}
114
+ attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
115
+ end
116
+ end
117
+
118
+ module RoundedTime
119
+ ROUND_PRECISION = 2
120
+ ROUND_MODIFIER = 10 ** ROUND_PRECISION
121
+ def self.new(time_in_seconds)
122
+ (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
123
+ end
124
+ end
125
+
126
+ class NullLogger
127
+ ::Logger::Severity.constants.each do |name|
128
+ define_method(name.downcase){|*args| } # no-op
129
+ end
130
+
131
+ def hash; 1; end
132
+ end
133
+
134
+ end
135
+
136
+ class ConnectionClosedError < RuntimeError
137
+ def initialize
138
+ super "The server closed the connection, no response was written."
139
+ end
140
+ end
141
+
142
+ end
@@ -1,145 +1,113 @@
1
- require 'benchmark'
2
- require 'logger'
3
- require 'ostruct'
4
- require 'sanford-protocol'
5
- require 'and-son/connection'
6
- require 'and-son/response'
1
+ require 'and-son/call_runner'
7
2
  require 'and-son/stored_responses'
8
3
 
9
4
  module AndSon
10
5
 
11
- module CallRunnerMethods
6
+ module Client
12
7
 
13
- # define methods here to allow configuring call runner params. be sure to
14
- # use `tap` to return whatever instance `self.call_runner` returns so you
15
- # can method-chain. `self.call_runner` returns a new runner instance if
16
- # called on a client, but returns the chained instance if called on a runner
17
-
18
- def timeout(seconds)
19
- self.call_runner.tap{|r| r.timeout_value = seconds.to_f }
8
+ def self.new(host, port)
9
+ if !ENV['ANDSON_TEST_MODE']
10
+ AndSonClient.new(host, port)
11
+ else
12
+ TestClient.new(host, port)
13
+ end
20
14
  end
21
15
 
22
- def params(hash = nil)
23
- if !hash.kind_of?(Hash)
24
- raise ArgumentError, "expected params to be a Hash instead of a #{hash.class}"
16
+ def self.included(klass)
17
+ klass.class_eval do
18
+ include CallRunner::InstanceMethods
19
+ include InstanceMethods
25
20
  end
26
- self.call_runner.tap{|r| r.params_value.merge!(self.stringify_keys(hash)) }
27
21
  end
28
22
 
29
- def logger(passed_logger)
30
- self.call_runner.tap{|r| r.logger_value = passed_logger }
31
- end
23
+ module InstanceMethods
32
24
 
33
- protected
25
+ attr_reader :host, :port
26
+
27
+ def initialize(host, port)
28
+ @host, @port = host, port
29
+ end
34
30
 
35
- def stringify_keys(hash)
36
- hash.inject({}){|h, (k, v)| h.merge({ k.to_s => v }) }
37
31
  end
38
32
 
39
33
  end
40
34
 
41
- class Client
42
- include CallRunnerMethods
43
-
44
- DEFAULT_TIMEOUT = 60 #seconds
45
-
46
- attr_reader :host, :port, :responses
47
-
48
- def initialize(host, port)
49
- @host, @port = host, port
50
- @responses = AndSon::StoredResponses.new
51
- end
35
+ class AndSonClient
36
+ include Client
52
37
 
53
38
  # proxy the call method to the call runner
54
39
  def call(*args, &block); self.call_runner.call(*args, &block); end
55
40
 
56
41
  def call_runner
57
- # always start with this default CallRunner
58
- CallRunner.new({
59
- :host => host,
60
- :port => port,
61
- :timeout_value => (ENV['ANDSON_TIMEOUT'] || DEFAULT_TIMEOUT).to_f,
62
- :params_value => {},
63
- :logger_value => NullLogger.new,
64
- :responses => @responses,
65
- })
42
+ AndSon::CallRunner.new(host, port)
43
+ end
44
+
45
+ def hash
46
+ self.call_runner.hash
66
47
  end
48
+
49
+ def ==(other)
50
+ other.kind_of?(self.class) ? self.hash == other.hash : super
51
+ end
52
+ alias :eql? :==
53
+
67
54
  end
68
55
 
69
- class CallRunner < OpenStruct
70
- # { :host, :port, :timeout_value, :params_value, :logger_value, :responses }
71
- include CallRunnerMethods
56
+ class TestClient
57
+ include Client
72
58
 
73
- # chain runner methods by returning itself
74
- def call_runner; self; end
59
+ attr_accessor :timeout_value, :params_value, :logger_value
60
+ attr_reader :calls, :responses
61
+
62
+ def initialize(host, port)
63
+ super
64
+ @params_value = {}
65
+ @calls = []
66
+ @responses = AndSon::StoredResponses.new
67
+ end
75
68
 
76
69
  def call(name, params = nil)
77
70
  params ||= {}
78
- if !params.kind_of?(Hash)
79
- raise ArgumentError, "expected params to be a Hash instead of a #{params.class}"
80
- end
81
- client_response = nil
82
- benchmark = Benchmark.measure do
83
- client_response = self.responses.find(name, params) if ENV['ANDSON_TEST_MODE']
84
- client_response ||= self.call!(name, params)
85
- end
86
-
87
- summary_line = SummaryLine.new({
88
- 'time' => RoundedTime.new(benchmark.real),
89
- 'status' => client_response.protocol_response.code,
90
- 'host' => "#{self.host}:#{self.port}",
91
- 'service' => name,
92
- 'params' => params
93
- })
94
- self.logger_value.info("[AndSon] #{summary_line}")
95
-
71
+ response = self.responses.get(name, params)
72
+ self.calls << Call.new(name, params, response.protocol_response)
96
73
  if block_given?
97
- yield client_response.protocol_response
74
+ yield response.protocol_response
98
75
  else
99
- client_response.data
76
+ response.data
100
77
  end
101
78
  end
102
79
 
103
- def call!(name, params)
104
- call_params = self.params_value.merge(params)
105
- AndSon::Connection.new(host, port).open do |connection|
106
- connection.write(Sanford::Protocol::Request.new(name, call_params).to_hash)
107
- connection.close_write
108
- if !connection.peek(timeout_value).empty?
109
- AndSon::Response.parse(connection.read(timeout_value))
110
- else
111
- raise AndSon::ConnectionClosedError.new
112
- end
113
- end
114
- end
80
+ def call_runner; self; end
115
81
 
116
- end
82
+ def add_response(*args, &block)
83
+ self.responses.add(*args, &block)
84
+ end
117
85
 
118
- class ConnectionClosedError < RuntimeError
119
- def initialize
120
- super "The server closed the connection, no response was written."
86
+ def remove_response(*args)
87
+ self.responses.remove(*args)
121
88
  end
122
- end
123
89
 
124
- class NullLogger
125
- ::Logger::Severity.constants.each do |name|
126
- define_method(name.downcase){|*args| } # no-op
90
+ def reset
91
+ self.calls.clear
92
+ self.responses.remove_all
127
93
  end
128
- end
129
94
 
130
- module SummaryLine
131
- def self.new(line_attrs)
132
- attr_keys = %w{time status host service params}
133
- attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
95
+ def hash
96
+ [ self.host,
97
+ self.port,
98
+ self.timeout_value,
99
+ self.params_value,
100
+ self.logger_value
101
+ ].hash
134
102
  end
135
- end
136
103
 
137
- module RoundedTime
138
- ROUND_PRECISION = 2
139
- ROUND_MODIFIER = 10 ** ROUND_PRECISION
140
- def self.new(time_in_seconds)
141
- (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
104
+ def ==(other)
105
+ other.kind_of?(self.class) ? self.hash == other.hash : super
142
106
  end
107
+ alias :eql? :==
108
+
109
+ Call = Struct.new(:request_name, :request_params, :response)
110
+
143
111
  end
144
112
 
145
113
  end
@@ -8,7 +8,7 @@ module AndSon
8
8
  RequestData = Struct.new(:name, :params)
9
9
 
10
10
  def initialize
11
- @hash = {}
11
+ @hash = Hash.new{ default_response_proc }
12
12
  end
13
13
 
14
14
  def add(name, params = nil, &response_block)
@@ -16,13 +16,9 @@ module AndSon
16
16
  @hash[request_data] = response_block
17
17
  end
18
18
 
19
- def find(name, params = nil)
19
+ def get(name, params = nil)
20
20
  response_block = @hash[RequestData.new(name, params || {})]
21
- return if !response_block
22
- response = response_block.call
23
- if !response.kind_of?(Sanford::Protocol::Response)
24
- response = Sanford::Protocol::Response.new(200, response)
25
- end
21
+ response = handle_response_block(response_block)
26
22
  AndSon::Response.new(response)
27
23
  end
28
24
 
@@ -30,6 +26,28 @@ module AndSon
30
26
  @hash.delete(RequestData.new(name, params || {}))
31
27
  end
32
28
 
29
+ def remove_all
30
+ @hash.clear
31
+ end
32
+
33
+ private
34
+
35
+ def handle_response_block(response_block)
36
+ if response_block.arity == 0 || response_block.arity == -1
37
+ default_response.tap{ |r| r.data = response_block.call }
38
+ else
39
+ default_response.tap{ |r| response_block.call(r) }
40
+ end
41
+ end
42
+
43
+ def default_response
44
+ Sanford::Protocol::Response.new(200, {})
45
+ end
46
+
47
+ def default_response_proc
48
+ proc{ |r| r.data = Hash.new }
49
+ end
50
+
33
51
  end
34
52
 
35
53
  end
@@ -1,3 +1,3 @@
1
1
  module AndSon
2
- VERSION = "0.6.1"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -4,5 +4,6 @@
4
4
  # add the root dir to the load path
5
5
  $LOAD_PATH.unshift(File.expand_path("../..", __FILE__))
6
6
 
7
- # require pry for debugging (`binding.pry`)
8
- require 'pry'
7
+ require 'pry' # require pry for debugging (`binding.pry`)
8
+
9
+ require 'test/support/factory'
@@ -0,0 +1,6 @@
1
+ require 'assert/factory'
2
+
3
+ module Factory
4
+ extend Assert::Factory
5
+
6
+ end
@@ -43,7 +43,7 @@ class MakingRequestsTests < Assert::Context
43
43
 
44
44
  should "return the registered response" do
45
45
  client = AndSon.new('localhost', 12000)
46
- client.responses.add('echo', 'message' => 'test'){ 'test' }
46
+ client.add_response('echo', 'message' => 'test'){ 'test' }
47
47
 
48
48
  client.call('echo', 'message' => 'test') do |response|
49
49
  assert_equal 200, response.code