3scale_client 2.9.0 → 2.10.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: 8fd79baa77606faea09171e9c577d1694c17ea5a
4
- data.tar.gz: 87ab11fd2aee6b31888ba5f22dcce546ee36d265
3
+ metadata.gz: a0c46a8b42107a91987d9861498f796287909fb5
4
+ data.tar.gz: 5d43b1ee44766fe2057fec8eea21f5a30a8ac83f
5
5
  SHA512:
6
- metadata.gz: 172557eb19d4c5bdca48c89e8a3d8785defdafc0300aac49d5d6692b9d32279fb78d5b059b8aea936cd1fa636ff62c4a559a11fdc06f88ffb79407f829d48845
7
- data.tar.gz: 631bea0687f9ae848ba1622ceb375edf27d82b039963651c8fb969e1a25e96ae350b27a21cb6f4bfa04155c851f15d5162e1de9c21cea77e32adf819f0518abc
6
+ metadata.gz: 1c3b09b26c8ec29367f68fc231b52c3d7af036d8360179b968d50d231a2a1332ba5b0c11139539e4af6a711cb2716fe47972eaa34e66fb4ba8addc29c876e33b
7
+ data.tar.gz: 3d3136b451d4694fc00291d013e07dd1fd562f4c605caf1bc32a7c4f1e5ca6c6bb9808eed46f54909d7e551fea5f7260363d6ef5c75aca9ef4f00b85f9d6c717
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
+ ## [2.10.0] - 2016-11-25
5
+ ### Added
6
+ - Added support for 3scale extensions (experimental or non-standard
7
+ features that are not part of the official API). You just need to
8
+ add the `:extensions` symbol and the value to the hash of options that
9
+ the client methods accept. The value is itself a hash containing the
10
+ parameter names as keys and the parameter values as values.
11
+
4
12
  ## [2.9.0] - 2016-10-21
5
13
  This version drops support for Ruby versions < 2.1 and JRuby < 9.1.1.0.
6
14
 
data/lib/3scale/client.rb CHANGED
@@ -7,6 +7,7 @@ require '3scale/client/version'
7
7
 
8
8
  require '3scale/response'
9
9
  require '3scale/authorize_response'
10
+ require '3scale/rack_query'
10
11
 
11
12
  module ThreeScale
12
13
  Error = Class.new(RuntimeError)
@@ -47,6 +48,9 @@ module ThreeScale
47
48
  'def report(transactions: [], service_id: nil).'.freeze
48
49
  private_constant :DEPRECATION_MSG_OLD_REPORT
49
50
 
51
+ EXTENSIONS_HEADER = '3scale-options'.freeze
52
+ private_constant :EXTENSIONS_HEADER
53
+
50
54
  def initialize(options)
51
55
  if options[:provider_key].nil? || options[:provider_key] =~ /^\s*$/
52
56
  raise ArgumentError, 'missing :provider_key'
@@ -69,6 +73,7 @@ module ThreeScale
69
73
 
70
74
  options_usage = options.delete :usage
71
75
  options_log = options.delete :log
76
+ extensions = options.delete :extensions
72
77
 
73
78
  options.each_pair do |param, value|
74
79
  path += "&#{param}=#{CGI.escape(value.to_s)}"
@@ -86,7 +91,8 @@ module ThreeScale
86
91
  path += "&#{log.join('&')}"
87
92
  end
88
93
 
89
- http_response = @http.get(path)
94
+ headers = extensions_to_header extensions if extensions
95
+ http_response = @http.get(path, headers: headers)
90
96
 
91
97
  case http_response
92
98
  when Net::HTTPSuccess,Net::HTTPConflict
@@ -148,7 +154,7 @@ module ThreeScale
148
154
  # The signature of this method is a bit complicated because we decided to
149
155
  # keep backwards compatibility with a previous version of the method:
150
156
  # def report(*transactions)
151
- def report(*reports, transactions: [], service_id: nil, **rest)
157
+ def report(*reports, transactions: [], service_id: nil, extensions: nil, **rest)
152
158
  if (!transactions || transactions.empty?) && rest.empty?
153
159
  raise ArgumentError, 'no transactions to report'
154
160
  end
@@ -164,7 +170,8 @@ module ThreeScale
164
170
  payload['provider_key'] = CGI.escape(provider_key)
165
171
  payload['service_id'] = CGI.escape(service_id.to_s) if service_id
166
172
 
167
- http_response = @http.post('/transactions.xml', payload)
173
+ headers = extensions_to_header extensions if extensions
174
+ http_response = @http.post('/transactions.xml', payload, headers: headers)
168
175
 
169
176
  case http_response
170
177
  when Net::HTTPSuccess
@@ -189,6 +196,7 @@ module ThreeScale
189
196
  # usage:: predicted usage. It is optional. It is a hash where the keys are metrics
190
197
  # and the values their predicted usage.
191
198
  # Example: {'hits' => 1, 'my_metric' => 100}
199
+ # extensions:: Optional. Hash of extension keys and values.
192
200
  #
193
201
  # == Return
194
202
  #
@@ -210,9 +218,11 @@ module ThreeScale
210
218
  # end
211
219
  #
212
220
  def authorize(options)
221
+ extensions = options.delete :extensions
213
222
  path = "/transactions/authorize.xml" + options_to_params(options, ALL_PARAMS)
214
223
 
215
- http_response = @http.get(path)
224
+ headers = extensions_to_header extensions if extensions
225
+ http_response = @http.get(path, headers: headers)
216
226
 
217
227
  case http_response
218
228
  when Net::HTTPSuccess,Net::HTTPConflict
@@ -259,9 +269,11 @@ module ThreeScale
259
269
  # end
260
270
  #
261
271
  def oauth_authorize(options)
272
+ extensions = options.delete :extensions
262
273
  path = "/transactions/oauth_authorize.xml" + options_to_params(options, OAUTH_PARAMS)
263
274
 
264
- http_response = @http.get(path)
275
+ headers = extensions_to_header extensions if extensions
276
+ http_response = @http.get(path, headers: headers)
265
277
 
266
278
  case http_response
267
279
  when Net::HTTPSuccess,Net::HTTPConflict
@@ -275,10 +287,8 @@ module ThreeScale
275
287
 
276
288
  private
277
289
 
278
- # The support for the 'hierarchy' param is experimental. Its support is not
279
- # guaranteed for future versions.
280
- OAUTH_PARAMS = [:app_id, :app_key, :service_id, :redirect_url, :usage, :hierarchy]
281
- ALL_PARAMS = [:user_key, :app_id, :app_key, :service_id, :redirect_url, :usage, :hierarchy]
290
+ OAUTH_PARAMS = [:app_id, :app_key, :service_id, :redirect_url, :usage]
291
+ ALL_PARAMS = [:user_key, :app_id, :app_key, :service_id, :redirect_url, :usage]
282
292
  REPORT_PARAMS = [:user_key, :app_id, :service_id, :timestamp]
283
293
 
284
294
  def options_to_params(options, allowed_keys)
@@ -382,5 +392,10 @@ module ThreeScale
382
392
  response.error!(node.content.to_s.strip, node['code'].to_s.strip)
383
393
  response
384
394
  end
395
+
396
+ # Encode extensions header
397
+ def extensions_to_header(extensions)
398
+ { EXTENSIONS_HEADER => RackQuery.encode(extensions) }
399
+ end
385
400
  end
386
401
  end
@@ -39,20 +39,32 @@ module ThreeScale
39
39
  @port = port
40
40
  end
41
41
 
42
- def get_request(path)
42
+ def get_request(path, headers: nil)
43
43
  get = Net::HTTP::Get.new(path)
44
44
  get.add_field(*USER_CLIENT_HEADER)
45
45
  get.add_field('Host', @host)
46
+ add_request_headers(get, headers) if headers
46
47
  get
47
48
  end
48
49
 
49
- def post_request(path, payload)
50
+ def post_request(path, payload, headers: nil)
50
51
  post = Net::HTTP::Post.new(path)
51
52
  post.add_field(*USER_CLIENT_HEADER)
52
53
  post.add_field('Host', @host)
54
+ add_request_headers(post, headers) if headers
53
55
  post.set_form_data(payload)
54
56
  post
55
57
  end
58
+
59
+ private
60
+
61
+ def add_request_headers(req, headers)
62
+ if headers
63
+ headers.each do |hk, hv|
64
+ req.add_field(hk, hv)
65
+ end
66
+ end
67
+ end
56
68
  end
57
69
 
58
70
  class NetHttpPersistent < BaseClient
@@ -77,15 +89,15 @@ module ThreeScale
77
89
  @protocol = 'https'
78
90
  end
79
91
 
80
- def get(path)
92
+ def get(path, headers: nil)
81
93
  uri = full_uri(path)
82
- @http.request(uri, get_request(path))
94
+ @http.request(uri, get_request(path, headers: headers))
83
95
  end
84
96
 
85
97
 
86
- def post(path, payload)
98
+ def post(path, payload, headers: nil)
87
99
  uri = full_uri(path)
88
- @http.request(uri, post_request(path, payload))
100
+ @http.request(uri, post_request(path, payload, headers: headers))
89
101
  end
90
102
 
91
103
  def full_uri(path)
@@ -107,12 +119,12 @@ module ThreeScale
107
119
  @http.use_ssl = true
108
120
  end
109
121
 
110
- def get(path)
111
- @http.request get_request(path)
122
+ def get(path, headers: nil)
123
+ @http.request get_request(path, headers: headers)
112
124
  end
113
125
 
114
- def post(path, payload)
115
- @http.request post_request(path, payload)
126
+ def post(path, payload, headers: nil)
127
+ @http.request post_request(path, payload, headers: headers)
116
128
  end
117
129
  end
118
130
 
@@ -1,5 +1,5 @@
1
1
  module ThreeScale
2
2
  class Client
3
- VERSION = '2.9.0'
3
+ VERSION = '2.10.0'
4
4
  end
5
5
  end
@@ -0,0 +1,37 @@
1
+ # A simple module to encode hashes of param keys and values as expected by
2
+ # Rack in its nested queries parsing.
3
+ #
4
+ module RackQuery
5
+ class << self
6
+ def encode(hash)
7
+ hash.flat_map do |hk, hv|
8
+ encode_value(CGI.escape(hk.to_s), hv)
9
+ end.join('&'.freeze)
10
+ end
11
+
12
+ private
13
+
14
+ def encode_value(rack_param, val)
15
+ if val.is_a? Array
16
+ encode_array(rack_param, val)
17
+ elsif val.is_a? Hash
18
+ encode_hash(rack_param, val)
19
+ else
20
+ "#{rack_param}=#{CGI.escape(val.to_s)}"
21
+ end
22
+ end
23
+
24
+ def encode_array(rack_param, val)
25
+ rack_param = rack_param + '[]'
26
+ val.flat_map do |v|
27
+ encode_value(rack_param, v)
28
+ end
29
+ end
30
+
31
+ def encode_hash(rack_param, val)
32
+ val.flat_map do |k, v|
33
+ encode_value(rack_param + "[#{CGI.escape(k.to_s)}]", v)
34
+ end
35
+ end
36
+ end
37
+ end
data/test/client_test.rb CHANGED
@@ -267,7 +267,7 @@ class ThreeScale::ClientTest < MiniTest::Test
267
267
  # calls.
268
268
  urls = [:authorize, :authrep, :oauth_authorize].inject({}) do |acc, method|
269
269
  acc[method] = "http://#{@host}/transactions/#{method}.xml?"\
270
- "provider_key=1234abcd&app_id=foo&hierarchy=1"
270
+ "provider_key=1234abcd&app_id=foo"
271
271
  acc[method] << "&%5Busage%5D%5Bhits%5D=1" if method == :authrep
272
272
  acc
273
273
  end
@@ -317,7 +317,7 @@ class ThreeScale::ClientTest < MiniTest::Test
317
317
 
318
318
  urls.each do |method, url|
319
319
  FakeWeb.register_uri(:get, url, :status => ['200', 'OK'], :body => body)
320
- response = @client.send(method, :app_id => 'foo', :hierarchy => 1)
320
+ response = @client.send(method, :app_id => 'foo', extensions: { :hierarchy => 1 })
321
321
  assert_equal response.hierarchy, { 'parent1' => ['child1', 'child2'],
322
322
  'parent2' => ['child3'] }
323
323
  end
@@ -702,6 +702,65 @@ class ThreeScale::ClientTest < MiniTest::Test
702
702
  assert_equal "su1.3scale.net", request["host"]
703
703
  end
704
704
 
705
+ EXTENSIONS_HASH = {
706
+ 'a special &=key' => 'a special =&value',
707
+ 'ary' => [1,2],
708
+ 'a hash' => { one: 'one', two: 'two' },
709
+ 'combined' =>
710
+ { v: 'v', nested: [1, { h: [ { hh: [ { hhh: :deep }, 'val' ] } ], h2: :h2 } ] }
711
+ }
712
+ private_constant :EXTENSIONS_HASH
713
+ EXTENSIONS_STR = "a+special+%26%3Dkey=a+special+%3D%26value&ary[]=1&ary[]=2&" \
714
+ "a+hash[one]=one&a+hash[two]=two&combined[v]=v&" \
715
+ "combined[nested][]=1&combined[nested][][h][][hh][][hhh]=deep&" \
716
+ "combined[nested][][h][][hh][]=val&combined[nested][][h2]=h2".freeze
717
+ private_constant :EXTENSIONS_STR
718
+
719
+ def test_authorize_with_extensions
720
+ body = '<status>
721
+ <authorized>true</authorized>
722
+ <plan>Ultimate</plan>
723
+ </status>'
724
+ FakeWeb.register_uri(:get,
725
+ "http://#{@host}/transactions/authorize.xml?provider_key=1234abcd&app_id=foo",
726
+ :status => ['200', 'OK'], body: body)
727
+
728
+ @client.authorize(:app_id => 'foo', extensions: EXTENSIONS_HASH)
729
+
730
+ request = FakeWeb.last_request
731
+ assert_equal EXTENSIONS_STR, request[ThreeScale::Client.const_get('EXTENSIONS_HEADER')]
732
+ end
733
+
734
+ def test_authrep_with_extensions
735
+ body = '<status>
736
+ <authorized>true</authorized>
737
+ <plan>Ultimate</plan>
738
+ </status>'
739
+ FakeWeb.register_uri(:get,
740
+ "http://#{@host}/transactions/authrep.xml?provider_key=1234abcd&app_id=foo&%5Busage%5D%5Bhits%5D=1",
741
+ :status => ['200', 'OK'], body: body)
742
+
743
+ @client.authrep(:app_id => 'foo', extensions: EXTENSIONS_HASH)
744
+
745
+ request = FakeWeb.last_request
746
+ assert_equal EXTENSIONS_STR, request['3scale-options']
747
+ end
748
+
749
+ def test_report_with_extensions
750
+ FakeWeb.register_uri(:post, "http://#{@host}/transactions.xml",
751
+ :status => ['200', 'OK'])
752
+
753
+ transactions = [{ :app_id => 'app_id_1',
754
+ :usage => { 'hits' => 1 },
755
+ :timestamp => '2016-07-18 15:42:17 0200' }]
756
+
757
+ @client.report(transactions: transactions, service_id: 'a_service_id',
758
+ extensions: EXTENSIONS_HASH)
759
+
760
+ request = FakeWeb.last_request
761
+ assert_equal EXTENSIONS_STR, request['3scale-options']
762
+ end
763
+
705
764
  private
706
765
 
707
766
  #OPTIMIZE this tricky test helper relies on fakeweb catching the urls requested by the client
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: 3scale_client
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.9.0
4
+ version: 2.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michal Cichra
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2016-10-21 00:00:00.000000000 Z
15
+ date: 2016-11-25 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bundler
@@ -172,13 +172,13 @@ files:
172
172
  - LICENCE
173
173
  - README.md
174
174
  - Rakefile
175
- - VERSION
176
175
  - gemfiles/rack_1.gemfile
177
176
  - lib/3scale/authorize_response.rb
178
177
  - lib/3scale/client.rb
179
178
  - lib/3scale/client/http_client.rb
180
179
  - lib/3scale/client/version.rb
181
180
  - lib/3scale/middleware.rb
181
+ - lib/3scale/rack_query.rb
182
182
  - lib/3scale/response.rb
183
183
  - lib/3scale_client.rb
184
184
  - test/benchmark.rb
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 2.8.2