and-son 0.6.1 → 0.7.0

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