rester 0.4.2 → 0.5.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
  SHA1:
3
- metadata.gz: ed31a2e9f930e5607d9905098c61fae665750d3a
4
- data.tar.gz: a3cfb90ab6eb177fd2291d5bd6e5013014023f63
3
+ metadata.gz: e0d4b971390eaa9fe9c3aa6abdfb754f141bd8bb
4
+ data.tar.gz: 23c86151fc635c434a83249f85b0a66f66bf93ce
5
5
  SHA512:
6
- metadata.gz: fb965a25b5f15759c4998897c831a4959d512d6b89225d8228879ed2e8185cb99397617417ee54b7c8e2b69444d5f8af1ca9ba8a903e7fd39b70d2ea407ccb58
7
- data.tar.gz: a9fc47ffea8f0a68e66f8577e82e1733d06639c6ba7a8275121ddd92deee1b131ee42ca1192cb492e65b86fd0d7e3530c58abb8b2db190e330697011b333caa7
6
+ metadata.gz: 3782bdebc01e6c081ab57224f025b9c8abc0f5002b83f3cf120b67626232f42855910714885e0d0f49572eb011b8e3e537f175fa0aa13e4d0db3e5d7c9e9839c
7
+ data.tar.gz: 6fe88686d1afa14ee878b4868ee3e67e201ca4ede8dfdaf0a9c4ab4369b81812cf51542aad5cd6cbec05042c0e624547af3457aabc4d5bd030d0111092406792
@@ -36,11 +36,22 @@ module Rester
36
36
  raise NotImplementedError
37
37
  end
38
38
 
39
- def request(verb, path, params={})
40
- params ||= {}
39
+ ##
40
+ # Sends an HTTP request to the service.
41
+ #
42
+ # `params` should be a hash if specified.
43
+ def request(verb, path, params = nil)
41
44
  _validate_verb(verb)
42
- params = _validate_params(params)
43
- public_send("#{verb}!", path.to_s, params)
45
+ request!(verb, path.to_s, Utils.encode_www_data(params))
46
+ end
47
+
48
+ ##
49
+ # Sends an HTTP request to the service.
50
+ #
51
+ # `encoded_data` should be URL encoded set of parameters
52
+ # (e.g., "key1=value1&key2=value2")
53
+ def request!(verb, path, encoded_data)
54
+ fail NotImplementedError
44
55
  end
45
56
 
46
57
  [:get, :post, :put, :delete].each do |verb|
@@ -49,13 +60,6 @@ module Rester
49
60
  define_method(verb) { |*args|
50
61
  request(verb, *args)
51
62
  }
52
-
53
- ##
54
- # Define implementation methods: get!, post!, put!, delete!
55
- # These methods should be overridden by the specific adapter.
56
- define_method("#{verb}!") { |*args|
57
- raise NotImplementedError
58
- }
59
63
  end
60
64
 
61
65
  protected
@@ -73,54 +77,9 @@ module Rester
73
77
  delete: true
74
78
  }.freeze
75
79
 
76
- ##
77
- # PARAM_KEY_TRANSFORMERS
78
- #
79
- # Defines how to transform a key value before being sent to the server.
80
- # At the moment, this is a simple to_s conversion.
81
- PARAM_KEY_TRANSFORMERS = Hash.new { |_, key|
82
- proc { |value|
83
- fail ArgumentError, "Invalid param key type: #{key.inspect}"
84
- }
85
- }.merge(
86
- String => :to_s.to_proc,
87
- Symbol => :to_s.to_proc
88
- ).freeze
89
-
90
- ##
91
- # PARAM_VALUE_TRANSFORMERS
92
- #
93
- # Defines how values should be transformed before being sent to the
94
- # server. Mostly, this is just a simple conversion to a string, but in
95
- # the case of `nil` we want to convert it to 'null'.
96
- PARAM_VALUE_TRANSFORMERS = Hash.new { |_, key|
97
- proc { |value|
98
- fail ArgumentError, "Invalid param value type: #{key.inspect}"
99
- }
100
- }.merge(
101
- String => :to_s.to_proc,
102
- Symbol => :to_s.to_proc,
103
- Fixnum => :to_s.to_proc,
104
- Integer => :to_s.to_proc,
105
- Float => :to_s.to_proc,
106
- DateTime => :to_s.to_proc,
107
- TrueClass => :to_s.to_proc,
108
- FalseClass => :to_s.to_proc,
109
- NilClass => proc { 'null' }
110
- ).freeze
111
-
112
80
  def _validate_verb(verb)
113
81
  VALID_VERBS[verb] or
114
- raise ArgumentError, "Invalid verb: #{verb.inspect}"
115
- end
116
-
117
- def _validate_params(params)
118
- params.map { |key, value|
119
- [
120
- PARAM_KEY_TRANSFORMERS[key.class].call(key),
121
- PARAM_VALUE_TRANSFORMERS[value.class].call(value)
122
- ]
123
- }.to_h
82
+ fail ArgumentError, "Invalid verb: #{verb.inspect}"
124
83
  end
125
84
  end # Adapter
126
85
  end # Client::Adapters
@@ -18,44 +18,18 @@ module Rester
18
18
  @timeout = opts[:timeout]
19
19
  end
20
20
 
21
- def get(path, params={})
21
+ def request(verb, path, params={})
22
22
  _request(
23
- :get,
23
+ verb,
24
24
  _path(path, params[:query]),
25
- _prepare_headers(params[:headers])
26
- )
27
- end
28
-
29
- def delete(path, params={})
30
- _request(
31
- :delete,
32
- _path(path, params[:query]),
33
- _prepare_headers(params[:headers])
34
- )
35
- end
36
-
37
- def put(path, params={})
38
- _request(
39
- :put,
40
- _path(path),
41
- _prepare_data_headers(params[:headers]),
42
- _encode_data(params[:data])
43
- )
44
- end
45
-
46
- def post(path, params={})
47
- _request(
48
- :post,
49
- _path(path),
50
- _prepare_data_headers(params[:headers]),
51
- _encode_data(params[:data])
25
+ _headers(verb, params[:headers]),
26
+ params[:data]
52
27
  )
53
28
  end
54
29
 
55
30
  private
56
31
 
57
- def _request(verb, path, headers={}, data='')
58
- data = nil if [:get, :delete].include?(verb)
32
+ def _request(verb, path, headers, data)
59
33
  _http.public_send(verb, *[path, data, headers].compact)
60
34
  rescue Net::ReadTimeout, Net::OpenTimeout
61
35
  fail Errors::TimeoutError
@@ -64,12 +38,16 @@ module Rester
64
38
  def _path(path, query=nil)
65
39
  u = url.dup
66
40
  u.path = Utils.join_paths(u.path, path)
67
- u.query = URI.encode_www_form(query) if query && !query.empty?
41
+ u.query = query if query
68
42
  u.request_uri
69
43
  end
70
44
 
71
- def _encode_data(data)
72
- URI.encode_www_form(data || {})
45
+ def _headers(verb, headers)
46
+ if [:post, :put].include?(verb)
47
+ _prepare_data_headers(headers)
48
+ else
49
+ _prepare_headers(headers)
50
+ end
73
51
  end
74
52
 
75
53
  def _prepare_data_headers(headers)
@@ -25,24 +25,14 @@ module Rester
25
25
  !!connection
26
26
  end
27
27
 
28
- def get!(path, params={})
28
+ def request!(verb, path, encoded_data)
29
29
  _require_connection
30
- _prepare_response(connection.get(path, headers: headers, query: params))
31
- end
32
30
 
33
- def delete!(path, params={})
34
- _require_connection
35
- _prepare_response(connection.delete(path, headers: headers, query: params))
36
- end
31
+ data_key = [:get, :delete].include?(verb) ? :query : :data
32
+ response = connection.request(verb, path,
33
+ headers: headers, data_key => encoded_data)
37
34
 
38
- def put!(path, params={})
39
- _require_connection
40
- _prepare_response(connection.put(path, headers: headers, data: params))
41
- end
42
-
43
- def post!(path, params={})
44
- _require_connection
45
- _prepare_response(connection.post(path, headers: headers, data: params))
35
+ _prepare_response(response)
46
36
  end
47
37
 
48
38
  private
@@ -12,7 +12,7 @@ module Rester
12
12
 
13
13
  class << self
14
14
  def can_connect_to?(service)
15
- service.is_a?(Class) && service < Service
15
+ service.is_a?(Class) && !!(service < Service)
16
16
  end
17
17
  end # Class Methods
18
18
 
@@ -24,27 +24,16 @@ module Rester
24
24
  !!service
25
25
  end
26
26
 
27
- def get!(path, params={})
28
- _request(:get, path, headers: headers, query: params)
29
- end
30
-
31
- def delete!(path, params={})
32
- _request(:delete, path, headers: headers, query: params)
33
- end
34
-
35
- def put!(path, params={})
36
- _request(:put, path, headers: headers, data: params)
37
- end
38
-
39
- def post!(path, params={})
40
- _request(:post, path, headers: headers, data: params)
27
+ def request!(verb, path, encoded_data)
28
+ data_key = [:get, :delete].include?(verb) ? :query : :data
29
+ _request(verb, path, headers: headers, data_key => encoded_data)
41
30
  end
42
31
 
43
32
  private
44
33
 
45
34
  def _request(verb, path, opts={})
46
- body = URI.encode_www_form(opts[:data] || {})
47
- query = URI.encode_www_form(opts[:query] || {})
35
+ body = opts[:data] || ''
36
+ query = opts[:query] || ''
48
37
 
49
38
  response = Timeout::timeout(timeout) do
50
39
  service.call(
@@ -30,20 +30,9 @@ module Rester
30
30
  !!stub
31
31
  end
32
32
 
33
- def get!(path, params={})
34
- _request('GET', path, params)
35
- end
36
-
37
- def post!(path, params={})
38
- _request('POST', path, params)
39
- end
40
-
41
- def put!(path, params={})
42
- _request('PUT', path, params)
43
- end
44
-
45
- def delete!(path, params={})
46
- _request('DELETE', path, params)
33
+ def request!(verb, path, encoded_data)
34
+ params = Rack::Utils.parse_nested_query(encoded_data)
35
+ _request(verb.to_s.upcase, path, params)
47
36
  end
48
37
 
49
38
  def with_context(context)
@@ -60,8 +49,13 @@ module Rester
60
49
  end
61
50
 
62
51
  def _process_request(path, verb, params)
63
- fail Errors::StubError, "#{path} not found" unless stub[path]
64
- fail Errors::StubError, "#{verb} #{path} not found" unless stub[path][verb]
52
+ unless stub[path]
53
+ fail Errors::StubError, "#{path} not found"
54
+ end
55
+
56
+ unless stub[path][verb]
57
+ fail Errors::StubError, "#{verb} #{path} not found"
58
+ end
65
59
 
66
60
  context = @_context || _find_context_by_params(path, verb, params)
67
61
 
@@ -70,10 +64,13 @@ module Rester
70
64
  "#{verb} #{path} with context '#{context}' not found"
71
65
  end
72
66
 
73
- # Verify body, if there is one
74
- unless (request = spec['request']) == params
67
+ # Verify request params. Compile a list of mismatched params values and
68
+ # any incoming request param keys which aren't specified in the stub
69
+ unless (spec_params = spec['request']) == params
70
+ diff = _param_diff(params, spec_params)
75
71
  fail Errors::StubError,
76
- "#{verb} #{path} with context '#{context}' params don't match stub. Expected: #{request} Got: #{params}"
72
+ "#{verb} #{path} with context '#{context}' params don't match "\
73
+ "stub: #{diff}"
77
74
  end
78
75
 
79
76
  # At this point, the 'request' is valid by matching a corresponding
@@ -81,6 +78,30 @@ module Rester
81
78
  stub[path][verb][context]
82
79
  end
83
80
 
81
+ ##
82
+ # Generate the diff string in the case when the request params of the
83
+ # service don't match the params specified in the stub file.
84
+ def _param_diff(params, spec_params)
85
+ params = params.dup
86
+ # Compile a list of mismatched params values
87
+ diff = spec_params.map { |k,v|
88
+ param_value = params.delete(k)
89
+ unless v == param_value
90
+ "#{k.inspect} should equal #{v.inspect} but got "\
91
+ "#{param_value.inspect}"
92
+ end
93
+ }.compact.join(', ')
94
+
95
+ unless params.empty?
96
+ # Add any param keys which aren't specified in the spec
97
+ diff << ', and ' unless diff.empty?
98
+ unexpected_str = params.keys.map(&:to_s).map(&:inspect).join(', ')
99
+ diff << "received unexpected key(s): #{unexpected_str}"
100
+ end
101
+
102
+ diff
103
+ end
104
+
84
105
  ##
85
106
  # Find the first request object with the same params as what's passed in.
86
107
  # Useful for testing without having to set the context.
@@ -2,7 +2,7 @@ module Rester
2
2
  class Service::Resource
3
3
  class Params
4
4
  DEFAULT_OPTS = { strict: true }.freeze
5
- BASIC_TYPES = [String, Symbol, Float, Integer].freeze
5
+ BASIC_TYPES = [String, Symbol, Float, Integer, Array, Hash].freeze
6
6
 
7
7
  DEFAULT_TYPE_MATCHERS = {
8
8
  Integer => /\A\d+\z/,
@@ -59,14 +59,8 @@ module Rester
59
59
  end
60
60
 
61
61
  def validate!(key, value)
62
- _error!("expected string value for #{key}") unless value.is_a?(String)
63
-
64
62
  klass, opts = @_validators[key]
65
- _validate_match(key, value, opts[:match]) if opts[:match]
66
-
67
- _parse_with_class(klass, value).tap do |obj|
68
- _validate_obj(key, obj)
69
- end
63
+ _validate(key, value, klass, opts)
70
64
  end
71
65
 
72
66
  def use(params)
@@ -74,6 +68,10 @@ module Rester
74
68
  nil
75
69
  end
76
70
 
71
+ def required?(key)
72
+ @_required_fields.include?(key.to_sym)
73
+ end
74
+
77
75
  ##
78
76
  # The basic data types all have helper methods named after them in Kernel.
79
77
  # This allows you to do things like String(1234) to get '1234'. It's the
@@ -83,7 +81,13 @@ module Rester
83
81
  # them so we can capture their calls. If this weren't the case, then we'd
84
82
  # be catch them in `method_missing`.
85
83
  BASIC_TYPES.each do |type|
86
- define_method(type.to_s) { |name, opts={}|
84
+ define_method(type.to_s) { |name, opts={}, &block|
85
+ if type == Hash || (type == Array && opts[:type] == Hash)
86
+ elem_type = (options = @options.merge(opts)).delete(:type)
87
+ opts = elem_type ? { type: elem_type } : {}
88
+ opts.merge!(use: self.class.new(options, &block))
89
+ end
90
+
87
91
  _add_validator(name, type, opts)
88
92
  }
89
93
  end
@@ -167,9 +171,49 @@ module Rester
167
171
  raise error if error
168
172
  end
169
173
 
170
- def _parse_with_class(klass, value)
171
- return nil if value == 'null'
174
+ ##
175
+ # Validates and parses a given value. `klass` is the intended type for the
176
+ # value (e.g., String, Integer, Array, etc.). `opts` contains the
177
+ # validation options.
178
+ def _validate(key, value, klass, opts)
179
+ case value
180
+ when String
181
+ _validate_str(key, value, klass, opts)
182
+ when Array
183
+ _validate_array(key, value, klass, opts)
184
+ when Hash
185
+ _validate_hash(key, value, klass, opts)
186
+ when NilClass
187
+ _validate_required(key, false)
188
+ else
189
+ _error!("unexpected value type for #{key}: #{value.class}")
190
+ end
191
+ end
192
+
193
+ def _validate_str(key, value, klass, opts)
194
+ fail unless value.is_a?(String) # assert
195
+
196
+ _validate_match(key, value, opts[:match]) if opts[:match]
197
+ _parse_with_class(klass, value).tap do |obj|
198
+ _validate_type(key, obj, klass) if obj
199
+ _validate_obj(key, obj, opts)
200
+ end
201
+ end
172
202
 
203
+ def _validate_array(key, value, klass, opts)
204
+ _error!("unexpected array for #{key}") unless klass == Array
205
+ type = (opts = opts.dup).delete(:type) || String
206
+
207
+ value.each_with_index
208
+ .map { |e, i| _validate("#{key}[#{i}]", e, type, opts) }
209
+ end
210
+
211
+ def _validate_hash(key, value, klass, opts)
212
+ _error!("unexpected hash for #{key}") unless klass == Hash
213
+ (validator = opts[:use]) && validator.validate(value)
214
+ end
215
+
216
+ def _parse_with_class(klass, value)
173
217
  if klass == String
174
218
  value
175
219
  elsif klass == Integer
@@ -185,13 +229,8 @@ module Rester
185
229
  end
186
230
  end
187
231
 
188
- def _validate_obj(key, obj)
189
- if obj.nil? && @_required_fields.include?(key)
190
- _error!("#{key} cannot be null")
191
- end
192
-
193
- klass, opts = @_validators[key]
194
- _validate_type(key, obj, klass) if obj
232
+ def _validate_obj(key, obj, opts)
233
+ fail if obj.nil? # Assert, at this point should be guaranteed
195
234
 
196
235
  opts.each do |opt, value|
197
236
  case opt
@@ -213,6 +252,20 @@ module Rester
213
252
  end
214
253
  end
215
254
 
255
+ def _validate_required(key, is_defined)
256
+ unless is_defined
257
+ _, key, index = /(\w+)(\[\d+\])?/.match(key).to_a
258
+
259
+ if required?(key)
260
+ if index
261
+ _error!("#{key} cannot contain null elements")
262
+ else
263
+ _error!("#{key} cannot be null")
264
+ end
265
+ end
266
+ end
267
+ end
268
+
216
269
  def _valid_type?(obj, type)
217
270
  case type
218
271
  when :boolean
@@ -56,7 +56,7 @@ module Rester
56
56
  # Converts all the values in the request hash to strings, which mimics
57
57
  # how the data will be received on the service side.
58
58
  def _update_request(path, verb, context, spec)
59
- spec['request'] = Utils.stringify_vals(spec['request'] || {})
59
+ spec['request'] = Utils.stringify(spec['request'] || {})
60
60
  end
61
61
 
62
62
  ##
data/lib/rester/utils.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'date'
2
+ require 'uri'
2
3
 
3
4
  module Rester
4
5
  module Utils
@@ -20,6 +21,38 @@ module Rester
20
21
  end
21
22
  end
22
23
 
24
+ ##
25
+ # Copied from Rack::Utils.build_nested_query (version 1.6.0)
26
+ #
27
+ # We want to be able to support Rack >= 1.5.2, but this method is broken
28
+ # in that version.
29
+ def encode_www_data(value, prefix = nil)
30
+ # Rack::Utils.build_nested_query(value)
31
+ case value
32
+ when Array
33
+ value.map { |v|
34
+ encode_www_data(v, "#{prefix}[]")
35
+ }.join("&")
36
+ when Hash
37
+ value.map { |k, v|
38
+ encode_www_data(v, prefix ? "#{prefix}[#{escape(k)}]" : escape(k))
39
+ }.reject(&:empty?).join('&')
40
+ when nil
41
+ prefix
42
+ else
43
+ raise ArgumentError, "value must be a Hash" if prefix.nil?
44
+ "#{prefix}=#{escape(value)}"
45
+ end
46
+ end
47
+
48
+ def decode_www_data(data)
49
+ Rack::Utils.parse_nested_query(data)
50
+ end
51
+
52
+ def escape(value)
53
+ URI.encode_www_form_component(value)
54
+ end
55
+
23
56
  def join_paths(*paths)
24
57
  paths.map(&:to_s).reject { |p| p.nil? || p.empty? }
25
58
  .join('/').gsub(/\/+/, '/')
@@ -50,17 +83,10 @@ module Rester
50
83
  hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
51
84
  end
52
85
 
53
- def stringify_vals(hash={})
54
- hash.each_with_object({}) { |(k,v), memo|
55
- case v
56
- when Hash
57
- memo[k] = stringify_vals(v)
58
- when NilClass
59
- memo[k] = 'null'
60
- else
61
- memo[k] = v.to_s
62
- end
63
- }
86
+ ##
87
+ # Converts all keys and values to strings.
88
+ def stringify(hash={})
89
+ decode_www_data(encode_www_data(hash))
64
90
  end
65
91
 
66
92
  def classify(str)
@@ -1,3 +1,3 @@
1
1
  module Rester
2
- VERSION = '0.4.2'
2
+ VERSION = '0.5.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rester
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Honer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-12-04 00:00:00.000000000 Z
12
+ date: 2015-12-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rack