3scale_client 2.10.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +13 -0
- data/README.md +75 -60
- data/lib/3scale/client.rb +68 -32
- data/lib/3scale/client/rack_query.rb +41 -0
- data/lib/3scale/client/version.rb +1 -1
- data/test/benchmark.rb +14 -5
- data/test/client_test.rb +89 -12
- data/test/middleware_test.rb +1 -1
- data/test/persistence_test.rb +5 -1
- data/test/remote_test.rb +9 -3
- metadata +4 -4
- data/lib/3scale/rack_query.rb +0 -37
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d32c601eff7116122dfc1e2054d2a25a7d8717d
|
4
|
+
data.tar.gz: b1cf03f30513e43ef8662724a7c869a50d061e68
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 032857a37711005b6790446648c4794c133fd98edd7508f53e89d0d1f95c6e9d6ffde13030567538d089004108f9a9c9ba7445e95c2bb8919e38f8474e9f83eb
|
7
|
+
data.tar.gz: f853e1abaceb96ee439cbafbaa1a420e004cf29888db70bcef5e1acd9f3df97f22117571bb772e86d2b928746f0e5d2dc777bac4098d65f91adfc51e3fdb5344
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,19 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
+
## [2.11.0] - 2017-01-20
|
5
|
+
### Added
|
6
|
+
- Added support for (Service Tokens)[https://support.3scale.net/docs/accounts/tokens]
|
7
|
+
Just instantiate the client with `ThreeScale::Client.new(service_tokens: true)`
|
8
|
+
and specify in each call the `service_token` and `service_id` parameters.
|
9
|
+
- Deprecated usage of `provider_key` when instantiating the client.
|
10
|
+
- Deprecated usage of the provided Rack Auth middleware. You should write your own.
|
11
|
+
- Added optional parameter `warn_deprecated` defaulting to `true` to be able to
|
12
|
+
opt out of deprecation warnings. It is encouraged to not turn this off unless
|
13
|
+
you are sure you understand all deprecation warnings you get, and even so good
|
14
|
+
practice suggests you should turn it back on each time you upgrade this client
|
15
|
+
to check for new warnings.
|
16
|
+
|
4
17
|
## [2.10.0] - 2016-11-25
|
5
18
|
### Added
|
6
19
|
- Added support for 3scale extensions (experimental or non-standard
|
data/README.md
CHANGED
@@ -6,9 +6,9 @@
|
|
6
6
|
3scale is an API Infrastructure service which handles API Keys, Rate Limiting, Analytics, Billing Payments and Developer Management. Includes a configurable API dashboard and developer portal CMS. More product stuff at http://www.3scale.net/, support information at http://support.3scale.net/.
|
7
7
|
|
8
8
|
### Tutorials
|
9
|
-
Plugin Setup: https://support.3scale.net/howtos/api-configuration/plugin-setup
|
10
|
-
Rate Limiting: https://support.3scale.net/howtos/basics/provision-rate-limits
|
11
|
-
Analytics Setup: https://support.3scale.net/quickstarts/3scale-api-analytics
|
9
|
+
* Plugin Setup: https://support.3scale.net/howtos/api-configuration/plugin-setup
|
10
|
+
* Rate Limiting: https://support.3scale.net/howtos/basics/provision-rate-limits
|
11
|
+
* Analytics Setup: https://support.3scale.net/quickstarts/3scale-api-analytics
|
12
12
|
|
13
13
|
## Installation
|
14
14
|
|
@@ -36,28 +36,53 @@ Otherwise, require the gem in whatever way is natural to your framework of choic
|
|
36
36
|
|
37
37
|
## Usage
|
38
38
|
|
39
|
-
First, create an instance of the client
|
39
|
+
First, create an instance of the client:
|
40
40
|
|
41
41
|
```ruby
|
42
|
-
|
42
|
+
ThreeScale::Client.new(service_tokens: true)
|
43
43
|
```
|
44
|
+
|
45
|
+
> NOTE: unless you specify `service_tokens: true` you will be expected to specify
|
46
|
+
a `provider_key` parameter, which is deprecated in favor of Service Tokens:
|
47
|
+
```ruby
|
48
|
+
client = ThreeScale::Client.new(provider_key: 'your_provider_key')
|
49
|
+
```
|
50
|
+
|
44
51
|
Because the object is stateless, you can create just one and store it globally.
|
45
52
|
|
53
|
+
Then you can perform calls in the client:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
client.authorize(service_token: 'token', service_id: '123', usage: usage)
|
57
|
+
client.report(service_token: 'token', service_id: '123', usage: usage)
|
58
|
+
```
|
59
|
+
|
60
|
+
If you had configured a (deprecated) provider key, you would instead use:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
client.authrep(service_id: '123', usage: usage)
|
64
|
+
```
|
65
|
+
|
66
|
+
> NOTE: `service_id` is mandatory since November 2016, both when using service
|
67
|
+
tokens and when using provider keys
|
68
|
+
|
69
|
+
> NOTE: You might use the option `warn_deprecated: false` to avoid deprecation
|
70
|
+
warnings. This is enabled by default.
|
46
71
|
|
47
72
|
### SSL and Persistence
|
48
73
|
|
49
|
-
Starting with version 2.4.0 you can use two more options:
|
74
|
+
Starting with version 2.4.0 you can use two more options: `secure` and `persistent` like:
|
50
75
|
|
51
76
|
```ruby
|
52
|
-
client = ThreeScale::Client.new(:
|
77
|
+
client = ThreeScale::Client.new(provider_key: '...', secure: true, persistent: true)
|
53
78
|
```
|
54
79
|
|
55
|
-
####
|
80
|
+
#### `secure`
|
56
81
|
|
57
82
|
Enabling secure will force all traffic going through HTTPS.
|
58
|
-
Because estabilishing SSL/TLS for every call is expensive, there is
|
83
|
+
Because estabilishing SSL/TLS for every call is expensive, there is `persistent`.
|
59
84
|
|
60
|
-
####
|
85
|
+
#### `persistent`
|
61
86
|
|
62
87
|
Enabling persistent will use HTTP Keep-Alive to keep open connection to our servers.
|
63
88
|
This option requires installing gem `net-http-persistent`.
|
@@ -67,14 +92,13 @@ This option requires installing gem `net-http-persistent`.
|
|
67
92
|
Authrep is a 'one-shot' operation to authorize an application and report the associated transaction at the same time.
|
68
93
|
The main difference between this call and the regular authorize call is that usage will be reported if the authorization is successful. Read more about authrep at the [active docs page on the 3scale's support site](https://support.3scale.net/reference/activedocs#operation/66)
|
69
94
|
|
70
|
-
You can make request to this backend operation like this:
|
95
|
+
You can make request to this backend operation using `service_token` and `service_id`, and an authentication pattern like `user_key`, or `app_id` with an optional key, like this:
|
71
96
|
|
72
97
|
```ruby
|
73
|
-
response = client.authrep(:
|
98
|
+
response = client.authrep(service_token: 'token', service_id: 'service_id', app_id: 'app_id', app_key: 'app_key')
|
74
99
|
```
|
75
100
|
|
76
|
-
Then call the
|
77
|
-
successful.
|
101
|
+
Then call the `success?` method on the returned object to see if the authorization was successful.
|
78
102
|
|
79
103
|
```ruby
|
80
104
|
if response.success?
|
@@ -84,7 +108,7 @@ else
|
|
84
108
|
end
|
85
109
|
```
|
86
110
|
|
87
|
-
The example is using the app_id authentication pattern, but you can also use other patterns
|
111
|
+
The example is using the `app_id` authentication pattern, but you can also use other patterns such as `user_key`.
|
88
112
|
|
89
113
|
#### A rails example
|
90
114
|
|
@@ -95,19 +119,25 @@ class ApplicationController < ActionController
|
|
95
119
|
before_filter :authenticate
|
96
120
|
|
97
121
|
# You only need to instantiate a new Client once and store it as a global variable
|
98
|
-
#
|
122
|
+
# If you used a provider key it is advisable to fetch it from the environment, as
|
123
|
+
# it is secret.
|
99
124
|
def create_client
|
100
|
-
@@threescale_client ||= ThreeScale::Client.new(:
|
125
|
+
@@threescale_client ||= ThreeScale::Client.new(service_tokens: true)
|
101
126
|
end
|
102
127
|
|
103
128
|
# To record usage, create a new metric in your application plan. You will use the
|
104
129
|
# "system name" that you specifed on the metric/method to pass in as the key to the usage hash.
|
105
130
|
# The key needs to be a symbol.
|
106
131
|
# A way to pass the metric is to add a parameter that will pass the name of the metric/method along
|
132
|
+
#
|
133
|
+
# Note that you don't always want to retrieve the service token and service id from
|
134
|
+
# the parameters - this will depend on your application.
|
107
135
|
def authenticate
|
108
|
-
response = create_client.authrep(:
|
109
|
-
:
|
110
|
-
:
|
136
|
+
response = create_client.authrep(service_token: params['service_token']
|
137
|
+
service_id: params['service_id'],
|
138
|
+
app_id: params['app_id'],
|
139
|
+
app_key: params['app_key'],
|
140
|
+
usage: { params['metric'].to_sym => 1 })
|
111
141
|
if response.success?
|
112
142
|
return true
|
113
143
|
# All fine, the usage will be reported automatically. Proceeed.
|
@@ -122,15 +152,13 @@ end
|
|
122
152
|
|
123
153
|
### Authorize
|
124
154
|
|
125
|
-
To authorize an application, call the
|
126
|
-
optionally a key:
|
155
|
+
To authorize an application, call the `authorize` method passing it the `service_token` and `service_id`, as well as a supported pattern for application authentication:
|
127
156
|
|
128
157
|
```ruby
|
129
|
-
response = client.authorize(:
|
158
|
+
response = client.authorize(service_token: 'token', service_id: 'service_id', user_key: 'user_key')
|
130
159
|
```
|
131
160
|
|
132
|
-
Then call the
|
133
|
-
successful.
|
161
|
+
Then call the `success?` method on the returned object to see if the authorization was successful.
|
134
162
|
|
135
163
|
```ruby
|
136
164
|
if response.success?
|
@@ -140,16 +168,14 @@ else
|
|
140
168
|
end
|
141
169
|
```
|
142
170
|
|
143
|
-
If
|
144
|
-
information about the status of the application:
|
171
|
+
If the service (provided with the token and its id, or otherwise the id if the provider key was specified at instantiation time) and the application are valid, the response object contains additional information about the application's status:
|
145
172
|
|
146
173
|
```ruby
|
147
174
|
# Returns the name of the plan the application is signed up to.
|
148
175
|
response.plan
|
149
176
|
```
|
150
177
|
|
151
|
-
If the plan has defined usage limits, the response contains details about the usage broken
|
152
|
-
down by the metrics and usage limit periods.
|
178
|
+
If the plan has defined usage limits, the response contains details about the usage broken down by the metrics and usage limit periods.
|
153
179
|
|
154
180
|
```ruby
|
155
181
|
# The usage_reports array contains one element per each usage limit defined on the plan.
|
@@ -173,8 +199,7 @@ usage_report.max_value # 10000
|
|
173
199
|
usage_report.exceeded? # false
|
174
200
|
```
|
175
201
|
|
176
|
-
If the authorization failed, the
|
177
|
-
human readable error description:
|
202
|
+
If the authorization failed, the `error_code` returns system error code and `error_message` human readable error description:
|
178
203
|
|
179
204
|
```ruby
|
180
205
|
response.error_code # "usage_limits_exceeded"
|
@@ -183,13 +208,13 @@ response.error_message # "Usage limits are exceeded"
|
|
183
208
|
|
184
209
|
### OAuth Authorize
|
185
210
|
|
186
|
-
To authorize an application with OAuth, call the
|
211
|
+
To authorize an application with OAuth, call the `oauth_authorize` method passing it the `service_token` with `service_id` and the `app_id`.
|
187
212
|
|
188
213
|
```ruby
|
189
|
-
response = client.oauth_authorize(:
|
214
|
+
response = client.oauth_authorize(service_token: 'token', service_id: 'service_id', app_id: 'app_id')
|
190
215
|
```
|
191
216
|
|
192
|
-
If the authorization is successful, the response will contain the
|
217
|
+
If the authorization is successful, the response will contain the `app_key` and `redirect_url` defined for this application:
|
193
218
|
|
194
219
|
```ruby
|
195
220
|
response.app_key
|
@@ -198,45 +223,34 @@ response.redirect_url
|
|
198
223
|
|
199
224
|
### Report
|
200
225
|
|
201
|
-
To report usage, use the
|
226
|
+
To report usage, use the `report` method. You can report multiple transactions at the same time:
|
202
227
|
|
203
228
|
```ruby
|
204
229
|
response = client.report(
|
205
|
-
:
|
206
|
-
|
230
|
+
service_token: 'token',
|
231
|
+
service_id: 'service_id',
|
232
|
+
transactions: [{app_id: '1st app_id', usage: { 'hits' => 1 }},
|
233
|
+
{app_id: '2nd app_id', usage: { 'hits' => 1 }}])
|
207
234
|
```
|
208
235
|
|
209
|
-
|
210
|
-
```ruby
|
211
|
-
response = client.report(
|
212
|
-
:transactions => [{:app_id => "first app id", :usage => {'hits' => 1}},
|
213
|
-
{:app_id => "second app id", :usage => {'hits' => 1}}],
|
214
|
-
:service_id => 'service_123')
|
215
|
-
```
|
216
|
-
|
217
|
-
The :app_id and :usage parameters are required. Additionaly, you can specify a timestamp
|
218
|
-
of a transaction:
|
236
|
+
The `app_id` and `usage` parameters are required. Additionally, you can specify a timestamp of a transaction:
|
219
237
|
|
220
238
|
```ruby
|
221
239
|
response = client.report(
|
222
|
-
:transactions => [{:app_id
|
223
|
-
:
|
224
|
-
:
|
240
|
+
:transactions => [{app_id: 'app_id',
|
241
|
+
usage: { 'hits' => 1 },
|
242
|
+
timestamp: Time.local(2010, 4, 28, 12, 36)}])
|
225
243
|
```
|
226
244
|
|
227
|
-
The timestamp can be either a Time object (from ruby's standard library) or something that
|
228
|
-
"quacks" like it (for example, the ActiveSupport::TimeWithZone from Rails) or a string. The
|
229
|
-
string has to be in a format parseable by the Time.parse method. For example:
|
245
|
+
The timestamp can be either a `Time` object (from ruby's standard library) or something that _quacks_ like it (for example, the `ActiveSupport::TimeWithZone` from Rails) or a string. Such string has to be in a format parseable by the `Time.parse` method. For example:
|
230
246
|
|
231
247
|
```ruby
|
232
248
|
"2010-04-28 12:38:33 +0200"
|
233
249
|
```
|
234
250
|
|
235
|
-
If the timestamp is not in UTC, you have to specify a time offset. That's the "+0200"
|
236
|
-
(two hours ahead of the Universal Coordinate Time) in the example abowe.
|
251
|
+
If the timestamp is not in UTC, you have to specify a time offset. That's the "+0200" (two hours ahead of the Universal Coordinate Time) in the example abowe.
|
237
252
|
|
238
|
-
Then call the
|
239
|
-
successful.
|
253
|
+
Then call the `success?` method on the returned response object to see if the report was successful.
|
240
254
|
|
241
255
|
```ruby
|
242
256
|
if response.success?
|
@@ -246,19 +260,20 @@ successful.
|
|
246
260
|
end
|
247
261
|
```
|
248
262
|
|
249
|
-
In case of error, the
|
250
|
-
human readable error description:
|
263
|
+
In case of error, the `error_code` returns system error code and `error_message` human readable error description:
|
251
264
|
|
252
265
|
```ruby
|
253
266
|
response.error_code # "provider_key_invalid"
|
254
267
|
response.error_message # "provider key \"foo\" is invalid"
|
255
268
|
```
|
256
269
|
|
257
|
-
|
258
270
|
## Rack Middleware
|
259
271
|
|
260
272
|
You can use our Rack middleware to automatically authenticate your Rack applications.
|
261
273
|
|
274
|
+
> NOTE: this is deprecated. Please observe that there is no support for multiple
|
275
|
+
services nor for service tokens.
|
276
|
+
|
262
277
|
```ruby
|
263
278
|
require '3scale/middleware'
|
264
279
|
use ThreeScale::Middleware, provider_key, :user_key # or :app_id
|
data/lib/3scale/client.rb
CHANGED
@@ -7,7 +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
|
+
require '3scale/client/rack_query'
|
11
11
|
|
12
12
|
module ThreeScale
|
13
13
|
Error = Class.new(RuntimeError)
|
@@ -25,9 +25,9 @@ module ThreeScale
|
|
25
25
|
#
|
26
26
|
# == Example
|
27
27
|
#
|
28
|
-
# client = ThreeScale::Client.new(:
|
28
|
+
# client = ThreeScale::Client.new(service_tokens: true)
|
29
29
|
#
|
30
|
-
# response = client.authorize(:app_id
|
30
|
+
# response = client.authorize(service_token: 'token', service_id: '123', app_id: 'an app id', app_key: 'a secret key')
|
31
31
|
#
|
32
32
|
# if response.success?
|
33
33
|
# response = client.report(:app_id => "some app id", :usage => {"hits" => 1})
|
@@ -39,9 +39,15 @@ module ThreeScale
|
|
39
39
|
# end
|
40
40
|
# end
|
41
41
|
#
|
42
|
+
# Note: Provider Keys are deprecated in favor of Service Tokens with Service IDs
|
43
|
+
# The next major release of this client will default to use Service Tokens.
|
44
|
+
#
|
42
45
|
class Client
|
43
46
|
DEFAULT_HOST = 'su1.3scale.net'
|
44
47
|
|
48
|
+
DEPRECATION_MSG_PROVIDER_KEY = 'provider keys are deprecated - ' \
|
49
|
+
'please switch at your earliest convenience to use service tokens'.freeze
|
50
|
+
private_constant :DEPRECATION_MSG_PROVIDER_KEY
|
45
51
|
DEPRECATION_MSG_OLD_REPORT = 'warning: def report(*transactions) is '\
|
46
52
|
'deprecated. In next versions, the signature of the report method is '\
|
47
53
|
'going to be: '\
|
@@ -52,24 +58,24 @@ module ThreeScale
|
|
52
58
|
private_constant :EXTENSIONS_HEADER
|
53
59
|
|
54
60
|
def initialize(options)
|
55
|
-
if options[:provider_key].nil? || options[:provider_key] =~ /^\s*$/
|
56
|
-
raise ArgumentError, 'missing :provider_key'
|
57
|
-
end
|
58
|
-
|
59
61
|
@provider_key = options[:provider_key]
|
62
|
+
@service_tokens = options[:service_tokens]
|
63
|
+
@warn_deprecated = options.fetch(:warn_deprecated, true)
|
64
|
+
|
65
|
+
generate_creds_params
|
60
66
|
|
61
67
|
@host = options[:host] ||= DEFAULT_HOST
|
62
68
|
|
63
69
|
@http = ThreeScale::Client::HTTPClient.new(options)
|
64
70
|
end
|
65
71
|
|
66
|
-
attr_reader :provider_key, :host, :http
|
72
|
+
attr_reader :provider_key, :service_tokens, :host, :http
|
67
73
|
|
68
74
|
# Authorize and report an application.
|
69
75
|
# TODO (in the mean time read authorize comments or head over to https://support.3scale.net/reference/activedocs#operation/66 for details
|
70
76
|
#
|
71
77
|
def authrep(options)
|
72
|
-
path = "/transactions/authrep.xml
|
78
|
+
path = "/transactions/authrep.xml?#{creds_params(options)}"
|
73
79
|
|
74
80
|
options_usage = options.delete :usage
|
75
81
|
options_log = options.delete :log
|
@@ -108,9 +114,9 @@ module ThreeScale
|
|
108
114
|
#
|
109
115
|
# == Parameters
|
110
116
|
#
|
111
|
-
# Hash with
|
117
|
+
# Hash with up to three fields:
|
112
118
|
#
|
113
|
-
# transactions::
|
119
|
+
# transactions:: Required. Enumerable. Each element is a hash with the fields:
|
114
120
|
# app_id: ID of the application to report the transaction for. This parameter is
|
115
121
|
# required.
|
116
122
|
# usage: Hash of usage values. The keys are metric names and values are
|
@@ -123,8 +129,9 @@ module ThreeScale
|
|
123
129
|
# from the UTC. For example, "US Pacific Time" has offset -0800, "Tokyo"
|
124
130
|
# has offset +0900. This parameter is optional, and if not provided, equals
|
125
131
|
# to the current time.
|
126
|
-
# service_id::
|
127
|
-
#
|
132
|
+
# service_id:: ID of the service. It is optional. When not specified, the transactions
|
133
|
+
# are reported to the default service.
|
134
|
+
# service_token:: Token granting access to the specified service ID.
|
128
135
|
#
|
129
136
|
# == Return
|
130
137
|
#
|
@@ -154,7 +161,7 @@ module ThreeScale
|
|
154
161
|
# The signature of this method is a bit complicated because we decided to
|
155
162
|
# keep backwards compatibility with a previous version of the method:
|
156
163
|
# def report(*transactions)
|
157
|
-
def report(*reports, transactions: [], service_id: nil, extensions: nil, **rest)
|
164
|
+
def report(*reports, transactions: [], service_id: nil, extensions: nil, service_token: nil, **rest)
|
158
165
|
if (!transactions || transactions.empty?) && rest.empty?
|
159
166
|
raise ArgumentError, 'no transactions to report'
|
160
167
|
end
|
@@ -162,12 +169,17 @@ module ThreeScale
|
|
162
169
|
transactions = transactions.concat(reports)
|
163
170
|
|
164
171
|
unless rest.empty?
|
165
|
-
warn
|
172
|
+
warn DEPRECATION_MSG_OLD_REPORT if @warn_deprecated
|
166
173
|
transactions.concat([rest])
|
167
174
|
end
|
168
175
|
|
169
176
|
payload = encode_transactions(transactions)
|
170
|
-
|
177
|
+
if @service_tokens
|
178
|
+
raise ArgumentError, "service_token or service_id not specified" unless service_token && service_id
|
179
|
+
payload['service_token'] = CGI.escape(service_token)
|
180
|
+
else
|
181
|
+
payload['provider_key'] = CGI.escape(@provider_key)
|
182
|
+
end
|
171
183
|
payload['service_id'] = CGI.escape(service_id.to_s) if service_id
|
172
184
|
|
173
185
|
headers = extensions_to_header extensions if extensions
|
@@ -189,14 +201,15 @@ module ThreeScale
|
|
189
201
|
#
|
190
202
|
# Hash with options:
|
191
203
|
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
204
|
+
# service_token:: token granting access to the specified service_id.
|
205
|
+
# app_id:: id of the application to authorize. This is required.
|
206
|
+
# app_key:: secret key assigned to the application. Required only if application has
|
207
|
+
# a key defined.
|
208
|
+
# service_id:: id of the service (required if you have more than one service)
|
209
|
+
# usage:: predicted usage. It is optional. It is a hash where the keys are metrics
|
210
|
+
# and the values their predicted usage.
|
211
|
+
# Example: {'hits' => 1, 'my_metric' => 100}
|
212
|
+
# extensions:: Optional. Hash of extension keys and values.
|
200
213
|
#
|
201
214
|
# == Return
|
202
215
|
#
|
@@ -219,7 +232,8 @@ module ThreeScale
|
|
219
232
|
#
|
220
233
|
def authorize(options)
|
221
234
|
extensions = options.delete :extensions
|
222
|
-
|
235
|
+
creds = creds_params(options)
|
236
|
+
path = "/transactions/authorize.xml" + options_to_params(options, ALL_PARAMS) + '&' + creds
|
223
237
|
|
224
238
|
headers = extensions_to_header extensions if extensions
|
225
239
|
http_response = @http.get(path, headers: headers)
|
@@ -240,11 +254,12 @@ module ThreeScale
|
|
240
254
|
#
|
241
255
|
# Hash with options:
|
242
256
|
#
|
243
|
-
#
|
244
|
-
#
|
245
|
-
#
|
246
|
-
#
|
247
|
-
#
|
257
|
+
# service_token:: token granting access to the specified service_id.
|
258
|
+
# app_id:: id of the application to authorize. This is required.
|
259
|
+
# service_id:: id of the service (required if you have more than one service)
|
260
|
+
# usage:: predicted usage. It is optional. It is a hash where the keys are metrics
|
261
|
+
# and the values their predicted usage.
|
262
|
+
# Example: {'hits' => 1, 'my_metric' => 100}
|
248
263
|
#
|
249
264
|
# == Return
|
250
265
|
#
|
@@ -270,7 +285,8 @@ module ThreeScale
|
|
270
285
|
#
|
271
286
|
def oauth_authorize(options)
|
272
287
|
extensions = options.delete :extensions
|
273
|
-
|
288
|
+
creds = creds_params(options)
|
289
|
+
path = "/transactions/oauth_authorize.xml" + options_to_params(options, OAUTH_PARAMS) + '&' + creds
|
274
290
|
|
275
291
|
headers = extensions_to_header extensions if extensions
|
276
292
|
http_response = @http.get(path, headers: headers)
|
@@ -292,7 +308,7 @@ module ThreeScale
|
|
292
308
|
REPORT_PARAMS = [:user_key, :app_id, :service_id, :timestamp]
|
293
309
|
|
294
310
|
def options_to_params(options, allowed_keys)
|
295
|
-
params = {
|
311
|
+
params = {}
|
296
312
|
|
297
313
|
(allowed_keys - [:usage]).each do |key|
|
298
314
|
params[key] = options[key] if options.has_key?(key)
|
@@ -397,5 +413,25 @@ module ThreeScale
|
|
397
413
|
def extensions_to_header(extensions)
|
398
414
|
{ EXTENSIONS_HEADER => RackQuery.encode(extensions) }
|
399
415
|
end
|
416
|
+
|
417
|
+
# helper to generate the creds_params method
|
418
|
+
def generate_creds_params
|
419
|
+
define_singleton_method :creds_params,
|
420
|
+
if @service_tokens
|
421
|
+
lambda do |options|
|
422
|
+
token = options.delete(:service_token)
|
423
|
+
service_id = options[:service_id]
|
424
|
+
raise ArgumentError, "need to specify a service_token and a service_id" unless token && service_id
|
425
|
+
'service_token='.freeze + CGI.escape(token)
|
426
|
+
end
|
427
|
+
elsif @provider_key
|
428
|
+
warn DEPRECATION_MSG_PROVIDER_KEY if @warn_deprecated
|
429
|
+
lambda do |_|
|
430
|
+
"provider_key=#{CGI.escape @provider_key}".freeze
|
431
|
+
end
|
432
|
+
else
|
433
|
+
raise ArgumentError, 'missing credentials - either use "service_tokens: true" or specify a provider_key value'
|
434
|
+
end
|
435
|
+
end
|
400
436
|
end
|
401
437
|
end
|
@@ -0,0 +1,41 @@
|
|
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 ThreeScale
|
5
|
+
class Client
|
6
|
+
module RackQuery
|
7
|
+
class << self
|
8
|
+
def encode(hash)
|
9
|
+
hash.flat_map do |hk, hv|
|
10
|
+
encode_value(CGI.escape(hk.to_s), hv)
|
11
|
+
end.join('&'.freeze)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def encode_value(rack_param, val)
|
17
|
+
if val.is_a? Array
|
18
|
+
encode_array(rack_param, val)
|
19
|
+
elsif val.is_a? Hash
|
20
|
+
encode_hash(rack_param, val)
|
21
|
+
else
|
22
|
+
"#{rack_param}=#{CGI.escape(val.to_s)}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def encode_array(rack_param, val)
|
27
|
+
rack_param = rack_param + '[]'
|
28
|
+
val.flat_map do |v|
|
29
|
+
encode_value(rack_param, v)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def encode_hash(rack_param, val)
|
34
|
+
val.flat_map do |k, v|
|
35
|
+
encode_value(rack_param + "[#{CGI.escape(k.to_s)}]", v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/test/benchmark.rb
CHANGED
@@ -2,12 +2,21 @@ require 'benchmark'
|
|
2
2
|
|
3
3
|
require '3scale/client'
|
4
4
|
|
5
|
-
provider_key = ENV['TEST_3SCALE_PROVIDER_KEY']
|
5
|
+
provider_key = ENV['TEST_3SCALE_PROVIDER_KEY'] or raise 'No provider key set'
|
6
|
+
warn_deprecated = ENV['WARN_DEPRECATED'] == '1'
|
6
7
|
|
7
|
-
client = ThreeScale::Client.new(:
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
client = ThreeScale::Client.new(provider_key: provider_key,
|
9
|
+
warn_deprecated: warn_deprecated)
|
10
|
+
persistent_client = ThreeScale::Client.new(provider_key: provider_key,
|
11
|
+
warn_deprecated: warn_deprecated,
|
12
|
+
persistent: true)
|
13
|
+
persistent_ssl_client = ThreeScale::Client.new(provider_key: provider_key,
|
14
|
+
warn_deprecated: warn_deprecated,
|
15
|
+
secure: true,
|
16
|
+
persistent: true)
|
17
|
+
ssl_client = ThreeScale::Client.new(provider_key: provider_key,
|
18
|
+
warn_deprecated: warn_deprecated,
|
19
|
+
secure: true)
|
11
20
|
|
12
21
|
auth = { :app_id => ENV['TEST_3SCALE_APP_IDS'], :app_key => ENV['TEST_3SCALE_APP_KEYS'] }
|
13
22
|
|
data/test/client_test.rb
CHANGED
@@ -7,8 +7,11 @@ require '3scale/client'
|
|
7
7
|
|
8
8
|
class ThreeScale::ClientTest < MiniTest::Test
|
9
9
|
|
10
|
+
WARN_DEPRECATED = ENV['WARN_DEPRECATED'] == '1'
|
11
|
+
|
10
12
|
def client(options = {})
|
11
|
-
ThreeScale::Client.new({:
|
13
|
+
ThreeScale::Client.new({ provider_key: '1234abcd',
|
14
|
+
warn_deprecated: WARN_DEPRECATED }.merge(options))
|
12
15
|
end
|
13
16
|
|
14
17
|
def setup
|
@@ -19,36 +22,53 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
19
22
|
@host = ThreeScale::Client::DEFAULT_HOST
|
20
23
|
end
|
21
24
|
|
22
|
-
def
|
25
|
+
def test_raises_exception_if_no_credentials_are_specified
|
23
26
|
assert_raises ArgumentError do
|
24
27
|
ThreeScale::Client.new({})
|
25
28
|
end
|
29
|
+
assert_raises ArgumentError do
|
30
|
+
ThreeScale::Client.new(service_tokens: false)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_does_not_raise_if_some_credentials_are_specified
|
35
|
+
assert ThreeScale::Client.new(provider_key: 'some_key',
|
36
|
+
warn_deprecated: WARN_DEPRECATED)
|
37
|
+
assert ThreeScale::Client.new(service_tokens: true)
|
26
38
|
end
|
27
39
|
|
28
40
|
def test_default_host
|
29
|
-
client = ThreeScale::Client.new(:
|
41
|
+
client = ThreeScale::Client.new(provider_key: '1234abcd',
|
42
|
+
warn_deprecated: WARN_DEPRECATED)
|
30
43
|
|
31
44
|
assert_equal 'su1.3scale.net', client.host
|
32
45
|
end
|
33
46
|
|
34
47
|
def test_custom_host
|
35
|
-
client = ThreeScale::Client.new(:
|
48
|
+
client = ThreeScale::Client.new(provider_key: '1234abcd',
|
49
|
+
warn_deprecated: WARN_DEPRECATED,
|
50
|
+
host: "example.com")
|
36
51
|
|
37
52
|
assert_equal 'example.com', client.host
|
38
53
|
end
|
39
54
|
|
40
55
|
def test_default_protocol
|
41
|
-
client = ThreeScale::Client.new(:
|
56
|
+
client = ThreeScale::Client.new(provider_key: 'test',
|
57
|
+
warn_deprecated: WARN_DEPRECATED)
|
42
58
|
assert_equal false, client.http.use_ssl?
|
43
59
|
end
|
44
60
|
|
45
61
|
def test_insecure_protocol
|
46
|
-
client = ThreeScale::Client.new(:
|
62
|
+
client = ThreeScale::Client.new(provider_key: 'test',
|
63
|
+
warn_deprecated: WARN_DEPRECATED,
|
64
|
+
secure: false)
|
47
65
|
assert_equal false, client.http.use_ssl?
|
48
66
|
end
|
49
67
|
|
50
68
|
def test_secure_protocol
|
51
|
-
client = ThreeScale::Client.new(:
|
69
|
+
client = ThreeScale::Client.new(provider_key: 'test',
|
70
|
+
warn_deprecated: WARN_DEPRECATED,
|
71
|
+
secure: true)
|
52
72
|
assert_equal true, client.http.use_ssl?
|
53
73
|
end
|
54
74
|
|
@@ -631,7 +651,8 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
631
651
|
:status => ['403', 'Forbidden'],
|
632
652
|
:body => error_body)
|
633
653
|
|
634
|
-
client = ThreeScale::Client.new(:
|
654
|
+
client = ThreeScale::Client.new(provider_key: 'foo',
|
655
|
+
warn_deprecated: WARN_DEPRECATED)
|
635
656
|
transactions = [{ :app_id => 'abc', :usage => { 'hits' => 1 } }]
|
636
657
|
response = client.report(transactions: transactions)
|
637
658
|
|
@@ -659,7 +680,8 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
659
680
|
:status => ['200', 'OK'],
|
660
681
|
:body => success_body)
|
661
682
|
|
662
|
-
client = ThreeScale::Client.new(:
|
683
|
+
client = ThreeScale::Client.new(provider_key: 'foo',
|
684
|
+
warn_deprecated: WARN_DEPRECATED)
|
663
685
|
response = client.authorize(:app_id => 'foo')
|
664
686
|
|
665
687
|
assert response.success?
|
@@ -676,7 +698,8 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
676
698
|
FakeWeb.register_uri(:post, "http://#{@host}/transactions.xml",
|
677
699
|
:status => ['200', 'OK'],
|
678
700
|
:body => success_body)
|
679
|
-
client = ThreeScale::Client.new(:
|
701
|
+
client = ThreeScale::Client.new(provider_key: 'foo',
|
702
|
+
warn_deprecated: WARN_DEPRECATED)
|
680
703
|
transactions = [{ :app_id => 'abc', :usage => { 'hits' => 1 } }]
|
681
704
|
client.report(transactions: transactions)
|
682
705
|
|
@@ -692,7 +715,8 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
692
715
|
:status => ['200', 'OK'],
|
693
716
|
:body => success_body)
|
694
717
|
|
695
|
-
client = ThreeScale::Client.new(:
|
718
|
+
client = ThreeScale::Client.new(provider_key: 'foo',
|
719
|
+
warn_deprecated: WARN_DEPRECATED)
|
696
720
|
response = client.authrep(:app_id => 'abc')
|
697
721
|
|
698
722
|
assert response.success?
|
@@ -761,6 +785,56 @@ class ThreeScale::ClientTest < MiniTest::Test
|
|
761
785
|
assert_equal EXTENSIONS_STR, request['3scale-options']
|
762
786
|
end
|
763
787
|
|
788
|
+
def test_client_initialized_with_sevice_tokens_uses_percall_specified_token
|
789
|
+
body = '<status>
|
790
|
+
<authorized>true</authorized>
|
791
|
+
<plan>Ultimate</plan>
|
792
|
+
</status>'
|
793
|
+
transactions = [{ app_id: 'foo',
|
794
|
+
timestamp: Time.local(2010, 4, 27, 15, 00),
|
795
|
+
usage: {'hits' => 1 } }]
|
796
|
+
usage = { 'metric1' => 1, 'metric2' => 2}
|
797
|
+
|
798
|
+
FakeWeb.register_uri(:get, "http://#{@host}/transactions/authorize.xml?user_key=foo&service_id=1&service_token=newtoken", status: ['200', 'OK'], body: body)
|
799
|
+
FakeWeb.register_uri(:get, "http://#{@host}/transactions/authrep.xml?service_token=newtoken&user_key=foo&service_id=1&%5Busage%5D%5Bhits%5D=1", status: ['200', 'OK'], body: body)
|
800
|
+
FakeWeb.register_uri(:post, "http://#{@host}/transactions.xml", parameters: {service_token: 'newtoken', service_id: '1', transactions: transactions}, status: ['200', 'OK'])
|
801
|
+
FakeWeb.register_uri(:get, "http://#{@host}/transactions/oauth_authorize.xml?service_id=1&%5Busage%5D%5Bmetric1%5D=1&%5Busage%5D%5Bmetric2%5D=2&service_token=newtoken",
|
802
|
+
status: ['200', 'OK'], body: body)
|
803
|
+
|
804
|
+
client = ThreeScale::Client.new(service_tokens: true)
|
805
|
+
|
806
|
+
response = client.authorize(user_key: 'foo', service_token: 'newtoken', service_id: 1)
|
807
|
+
assert response.success?
|
808
|
+
response = client.authrep(user_key: 'foo', service_token: 'newtoken', service_id: 1)
|
809
|
+
assert response.success?
|
810
|
+
response = client.report(transactions: transactions, service_token: 'newtoken', service_id: 1)
|
811
|
+
assert response.success?
|
812
|
+
response = client.oauth_authorize(access_token: 'oauth', usage: usage, service_token: 'newtoken', service_id: 1)
|
813
|
+
assert response.success?
|
814
|
+
end
|
815
|
+
|
816
|
+
def test_client_initialized_with_service_tokens_raises_if_unspecified_percall
|
817
|
+
transactions = [{ app_id: 'foo',
|
818
|
+
timestamp: Time.local(2010, 4, 27, 15, 00),
|
819
|
+
usage: {'hits' => 1 } }]
|
820
|
+
usage = { 'metric1' => 1, 'metric2' => 2}
|
821
|
+
|
822
|
+
client = ThreeScale::Client.new(service_tokens: true)
|
823
|
+
|
824
|
+
assert_raises ArgumentError do
|
825
|
+
client.authorize(user_key: 'foo', service_id: 1)
|
826
|
+
end
|
827
|
+
assert_raises ArgumentError do
|
828
|
+
client.authrep(user_key: 'foo', service_id: 1)
|
829
|
+
end
|
830
|
+
assert_raises ArgumentError do
|
831
|
+
client.report(transactions: transactions, service_id: 1)
|
832
|
+
end
|
833
|
+
assert_raises ArgumentError do
|
834
|
+
client.oauth_authorize(user_key: 'foo', usage: usage, service_id: 1)
|
835
|
+
end
|
836
|
+
end
|
837
|
+
|
764
838
|
private
|
765
839
|
|
766
840
|
#OPTIMIZE this tricky test helper relies on fakeweb catching the urls requested by the client
|
@@ -789,7 +863,10 @@ end
|
|
789
863
|
class ThreeScale::NetHttpPersistentClientTest < ThreeScale::ClientTest
|
790
864
|
def client(options = {})
|
791
865
|
ThreeScale::Client::HTTPClient.persistent_backend = ThreeScale::Client::HTTPClient::NetHttpPersistent
|
792
|
-
ThreeScale::Client.new({:
|
866
|
+
ThreeScale::Client.new({ provider_key: '1234abcd',
|
867
|
+
warn_deprecated: WARN_DEPRECATED,
|
868
|
+
persistent: true,
|
869
|
+
}.merge(options))
|
793
870
|
end
|
794
871
|
end
|
795
872
|
|
data/test/middleware_test.rb
CHANGED
@@ -39,7 +39,7 @@ class ThreeScale::MiddlewareTest < MiniTest::Test
|
|
39
39
|
def test_nil_authenticator
|
40
40
|
authenticator = ThreeScale::Middleware::NilAuthenticator.new(mock)
|
41
41
|
assert authenticator.provided?
|
42
|
-
|
42
|
+
assert_nil authenticator.credentials
|
43
43
|
assert authenticator.to_proc.call(nil, nil)
|
44
44
|
end
|
45
45
|
end
|
data/test/persistence_test.rb
CHANGED
@@ -5,6 +5,8 @@ require 'mocha/setup'
|
|
5
5
|
|
6
6
|
if ENV['TEST_3SCALE_PROVIDER_KEY'] && ENV['TEST_3SCALE_APP_IDS'] && ENV['TEST_3SCALE_APP_KEYS']
|
7
7
|
class ThreeScale::NetHttpPersistenceTest < MiniTest::Test
|
8
|
+
WARN_DEPRECATED = ENV['WARN_DEPRECATED'] == '1'
|
9
|
+
|
8
10
|
def setup
|
9
11
|
ThreeScale::Client::HTTPClient.persistent_backend = ThreeScale::Client::HTTPClient::NetHttpPersistent
|
10
12
|
|
@@ -13,7 +15,9 @@ if ENV['TEST_3SCALE_PROVIDER_KEY'] && ENV['TEST_3SCALE_APP_IDS'] && ENV['TEST_3S
|
|
13
15
|
@app_id = ENV['TEST_3SCALE_APP_IDS']
|
14
16
|
@app_key = ENV['TEST_3SCALE_APP_KEYS']
|
15
17
|
|
16
|
-
@client = ThreeScale::Client.new(provider_key: provider_key,
|
18
|
+
@client = ThreeScale::Client.new(provider_key: provider_key,
|
19
|
+
warn_deprecated: WARN_DEPRECATED,
|
20
|
+
persistence: true)
|
17
21
|
|
18
22
|
if defined?(FakeWeb)
|
19
23
|
FakeWeb.allow_net_connect = true
|
data/test/remote_test.rb
CHANGED
@@ -5,6 +5,8 @@ if ENV['TEST_3SCALE_PROVIDER_KEY'] &&
|
|
5
5
|
ENV['TEST_3SCALE_APP_IDS'] &&
|
6
6
|
ENV['TEST_3SCALE_APP_KEYS']
|
7
7
|
class ThreeScale::RemoteTest < MiniTest::Test
|
8
|
+
WARN_DEPRECATED = ENV['WARN_DEPRECATED'] == '1'
|
9
|
+
|
8
10
|
def setup
|
9
11
|
@provider_key = ENV['TEST_3SCALE_PROVIDER_KEY']
|
10
12
|
|
@@ -13,7 +15,8 @@ if ENV['TEST_3SCALE_PROVIDER_KEY'] &&
|
|
13
15
|
@app_ids = ENV['TEST_3SCALE_APP_IDS'].split(',').map(&stripper)
|
14
16
|
@app_keys = ENV['TEST_3SCALE_APP_KEYS'].split(',').map(&stripper)
|
15
17
|
|
16
|
-
@client = ThreeScale::Client.new(:
|
18
|
+
@client = ThreeScale::Client.new(provider_key: @provider_key,
|
19
|
+
warn_deprecated: WARN_DEPRECATED)
|
17
20
|
|
18
21
|
if defined?(FakeWeb)
|
19
22
|
FakeWeb.clean_registry
|
@@ -39,7 +42,9 @@ if ENV['TEST_3SCALE_PROVIDER_KEY'] &&
|
|
39
42
|
end
|
40
43
|
|
41
44
|
def test_successful_secure_authrep
|
42
|
-
@client = ThreeScale::Client.new(:
|
45
|
+
@client = ThreeScale::Client.new(provider_key: @provider_key,
|
46
|
+
warn_deprecated: WARN_DEPRECATED,
|
47
|
+
secure: true)
|
43
48
|
test_successful_authrep
|
44
49
|
end
|
45
50
|
|
@@ -96,7 +101,8 @@ if ENV['TEST_3SCALE_PROVIDER_KEY'] &&
|
|
96
101
|
{:app_id => app_id, :usage => {'hits' => 1}}
|
97
102
|
end
|
98
103
|
|
99
|
-
client = ThreeScale::Client.new(:
|
104
|
+
client = ThreeScale::Client.new(provider_key: 'invalid-key',
|
105
|
+
warn_deprecated: WARN_DEPRECATED)
|
100
106
|
response = client.report(transactions: transactions)
|
101
107
|
assert !response.success?
|
102
108
|
assert_equal 'provider_key_invalid', response.error_code
|
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.
|
4
|
+
version: 2.11.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:
|
15
|
+
date: 2017-01-20 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: bundler
|
@@ -176,9 +176,9 @@ files:
|
|
176
176
|
- lib/3scale/authorize_response.rb
|
177
177
|
- lib/3scale/client.rb
|
178
178
|
- lib/3scale/client/http_client.rb
|
179
|
+
- lib/3scale/client/rack_query.rb
|
179
180
|
- lib/3scale/client/version.rb
|
180
181
|
- 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
|
@@ -206,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
206
|
version: '0'
|
207
207
|
requirements: []
|
208
208
|
rubyforge_project:
|
209
|
-
rubygems_version: 2.6.
|
209
|
+
rubygems_version: 2.6.8
|
210
210
|
signing_key:
|
211
211
|
specification_version: 4
|
212
212
|
summary: Client for 3scale Web Service Management System API
|
data/lib/3scale/rack_query.rb
DELETED
@@ -1,37 +0,0 @@
|
|
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
|