rester 0.4.2 → 0.5.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.
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