whiplash-app 0.3.0 → 0.6.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 +5 -5
- data/.gitignore +3 -0
- data/README.md +81 -44
- data/lib/errors/whiplash_api_error.rb +94 -0
- data/lib/whiplash/app.rb +56 -26
- data/lib/whiplash/app/api_config.rb +6 -2
- data/lib/whiplash/app/caching.rb +1 -1
- data/lib/whiplash/app/connections.rb +137 -6
- data/lib/whiplash/app/finder_methods.rb +1 -1
- data/lib/whiplash/app/signing.rb +6 -6
- data/lib/whiplash/app/version.rb +2 -2
- data/whiplash-app.gemspec +2 -1
- metadata +23 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29188ceeacb00228aee15d9f56ab151aac8b491c43b05463d25e648114fbb8ac
|
4
|
+
data.tar.gz: 93b8d38a3809d3e30d570f4acfe1ad3007f3754273d2ae1cf7299f3f4ce0f113
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a952642f993be0711907cbef3567362f7eb55026c43f22a2d0a1a83e40c24742d5f26db1e9ab0ca36ad7ca8479c9b04e3dc5b930cc8eebfa97b09a7d9e834ca
|
7
|
+
data.tar.gz: a2e597b3658c0cdfec8727e44f91a5b6050871ab62d876f59fddcfb8c9412aba62bd0ecac8a4d300ffc5a85002553e746234cde7b70bbdf29c656655fff43a87
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -22,7 +22,27 @@ Or install it yourself as:
|
|
22
22
|
|
23
23
|
## Usage
|
24
24
|
|
25
|
-
|
25
|
+
**NOTE: 0.4.0 introduces a breaking change and is NOT backward compatible with previous versions.**
|
26
|
+
|
27
|
+
To upgrade from < 0.4.0, you need to make two small changes:
|
28
|
+
1. `Whiplash::App` must now be instantiated.
|
29
|
+
2. Tokens are **not** automatically refreshed
|
30
|
+
|
31
|
+
Before:
|
32
|
+
```ruby
|
33
|
+
api = Whiplash::App
|
34
|
+
```
|
35
|
+
|
36
|
+
After:
|
37
|
+
```ruby
|
38
|
+
api = Whiplash::App.new
|
39
|
+
api.refresh_token! # Since you don't have one yet
|
40
|
+
api.token # Confirm you've got a token
|
41
|
+
. . .
|
42
|
+
api.refresh_token! if api.token_expired?
|
43
|
+
```
|
44
|
+
|
45
|
+
### Authentication
|
26
46
|
In order to authenticate, make sure the following `ENV` vars are set:
|
27
47
|
|
28
48
|
```ruby
|
@@ -30,85 +50,90 @@ ENV["WHIPLASH_CLIENT_ID"]
|
|
30
50
|
ENV["WHIPLASH_CLIENT_SECRET"]
|
31
51
|
ENV["WHIPLASH_CLIENT_SCOPE"]
|
32
52
|
```
|
33
|
-
|
34
|
-
|
35
|
-
|
53
|
+
|
54
|
+
Once those are set, authentication is handled in app.
|
55
|
+
|
56
|
+
### Oauth Client Credentials
|
57
|
+
You can authenticate using Oauth Client Credentials (i.e. auth an entire app).
|
58
|
+
You probably want this for apps that work offline, _on behalf_ of users or customers, or that don't work at the user/customer-level at all.
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
api = Whiplash::App.new
|
62
|
+
api.refresh_token! # Since you don't have one yet
|
63
|
+
api.token # Confirm you've got a token
|
64
|
+
```
|
65
|
+
|
66
|
+
### Oauth Authorization Code
|
67
|
+
You can also authenticate using Oauth Authorization Code (i.e. auth an individual user). This is most common for user-facing app's with a front end.
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# Authenticate using Devise Omniauthenticateable strategy; you'll get oauth creds back as a hash
|
71
|
+
api = Whiplash::App.new(oauth_credentials_hash)
|
72
|
+
api.token # Confirm you've got a token
|
73
|
+
```
|
36
74
|
|
37
75
|
### API URL
|
38
76
|
In order to set your api url, you can use the following environment URL:
|
39
77
|
```
|
40
78
|
ENV["WHIPLASH_API_URL"]
|
41
79
|
```
|
42
|
-
If it isn't set, then the API URL defaults to either `https://
|
80
|
+
If it isn't set, then the API URL defaults to either `https://sandbox.getwhiplash.com` (test or dev environment) or `https://www.getwhiplash.com` (prod environment).
|
81
|
+
|
82
|
+
### Sending Customer ID and Shop ID headers
|
83
|
+
You can send the headers in `headers` array, like `{customer_id: 123, shop_id: 111}`.
|
84
|
+
Alternatively, you can set them on instantiation like `Whiplash::App.new(token, {customer_id: 123, shop_id: 111})`.
|
43
85
|
|
44
|
-
###Rails AR type calls
|
86
|
+
### Rails AR type calls
|
45
87
|
|
46
88
|
In order to make the use of the gem seem more "AR-ish", we've added AR oriented methods that can be used for basic object creation/deletion/updating/viewing. The basic gist of these AR style CRUD methods is that they will all follow the same pattern. If you are performing a collection action, such as `create` or `find`, the pattern is this:
|
47
89
|
|
48
90
|
```ruby
|
49
|
-
|
91
|
+
api.create(resource, params, headers)
|
50
92
|
```
|
51
93
|
|
52
94
|
For member actions, such as `show`, or `destroy` methods, the pattern is this:
|
53
95
|
|
54
96
|
```ruby
|
55
|
-
|
56
|
-
|
97
|
+
api.find(resource, id, headers)
|
98
|
+
api.destroy(resource, id, headers)
|
57
99
|
```
|
58
100
|
|
59
101
|
Finally, for `update` calls, it's a mixture of those:
|
60
102
|
|
61
103
|
```ruby
|
62
|
-
|
104
|
+
api.update(resource, id, params_to_update, headers)
|
63
105
|
```
|
64
106
|
|
65
107
|
So, basic AR style calls can be performed like so:
|
66
108
|
|
67
109
|
```ruby
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
110
|
+
api.find_all('orders', {}, { customer_id: 187 })
|
111
|
+
api.find('orders', 1)
|
112
|
+
api.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
|
113
|
+
api.update('orders', 1, { key: "value"}, { customer_id: 187 } )
|
114
|
+
api.destroy('orders', 1, { customer_id: 187 } )
|
115
|
+
api.count('customers')
|
74
116
|
```
|
75
117
|
|
76
|
-
###CRUD Wrapper methods
|
118
|
+
### CRUD Wrapper methods
|
77
119
|
In reality, all of these methods are simply wrapper methods around simple `GET/POST/PUT/DELETE` wrappers on Faraday, so if you want to get more granular,you can also make calls that simply reference the lower level REST verb:
|
78
120
|
|
79
121
|
```ruby
|
80
|
-
|
122
|
+
api.get('orders')
|
81
123
|
```
|
82
124
|
Which will return all orders and roughly correspond to an index call. If you need to use `Whiplash::App` for nonRESTful calls, simply drop the full endpoint in as your first argument:
|
83
125
|
|
84
126
|
```ruby
|
85
|
-
|
127
|
+
api.get('orders/non_restful_action', {}, {})
|
86
128
|
```
|
87
129
|
`POST`, `PUT`, and `DELETE` calls can be performed in much the same way:
|
88
130
|
```ruby
|
89
|
-
|
131
|
+
api.post(endpoint, params, headers) # POST request to the specified endpoint passing the payload in params
|
132
|
+
api.put(endpoint, params, headers) # PUT request to the specified endpoint passing the payload in params
|
133
|
+
api.delete(endpoint, params, headers) # DELETE request to the specified endpoint. Params would probably just be an id.
|
90
134
|
```
|
91
|
-
```ruby
|
92
|
-
Whiplash::App.put(endpoint, params, headers) #PUT request to the specified endpoint passing the payload in params
|
93
|
-
```
|
94
|
-
```ruby
|
95
|
-
Whiplash::App.delete(endpoint, params, headers) #DELETE request to the specified endpoint. Params would probably just be an id.
|
96
|
-
```
|
97
|
-
|
98
|
-
*IMPORTANT!!!! PLEASE READ!*
|
99
|
-
It's best if possible to use the AR style methods. They hide a lot of issues with the way Faraday handles params. For example, this should, in theory, work:
|
100
|
-
```ruby
|
101
|
-
Whiplash::App.get('orders', {id: 1}, {customer_id: 187})
|
102
|
-
```
|
103
|
-
BUT, due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
|
104
|
-
|
105
|
-
The gem works best when the base CRUD methods are used ONLY for situations where a singular endpoint can't be reached, such as a cancel action, or uncancel, which would have to be done like so:
|
106
|
-
```ruby
|
107
|
-
Whiplash::App.post('orders/1/cancel', {}, {customer_id: 187})
|
108
|
-
```
|
109
|
-
|
110
135
|
|
111
|
-
###Signing and Verifying.
|
136
|
+
### Signing and Verifying.
|
112
137
|
`whiplash-app` supports signing and verifying signatures like so:
|
113
138
|
```ruby
|
114
139
|
Whiplash::App.signature(request_body)
|
@@ -118,10 +143,9 @@ and verifications are done like so:
|
|
118
143
|
Whiplash::App.verified?(request)
|
119
144
|
```
|
120
145
|
|
121
|
-
###Caching
|
122
|
-
`whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide
|
123
|
-
|
124
|
-
choice, it's dead simple. Simply declare the following variables:
|
146
|
+
### Caching
|
147
|
+
`whiplash-app` is Cache agnostic, relying on the `moneta` gem to provide a local store, if needed.
|
148
|
+
However, if you intend to specify `REDIS` as your key-value store of choice, it's dead simple. Simply declare the following variables:
|
125
149
|
```
|
126
150
|
ENV["REDIS_HOST"]
|
127
151
|
ENV["REDIS_PORT"]
|
@@ -130,6 +154,19 @@ ENV["REDIS_NAMESPACE"]
|
|
130
154
|
```
|
131
155
|
If those are provided, `moneta` will use your redis connection and will namespace your cache storage under the redis namespace. By default, if you do not declare a `REDIS_NAMESPACE` value, the app will default to the `WHIPLASH_CLIENT_ID`.
|
132
156
|
|
157
|
+
**For user-facing apps, best practice is to store the `oauth_credentials_hash` in a session variable.**
|
158
|
+
|
159
|
+
### Gotchas
|
160
|
+
Due to the way Faraday handles params, this would not, as expected, route to `orders#show` in the Whiplash App, but would instead route to `orders#index`, so it wouldn't return the expected singular order with an ID of 1, but all orders for that customer.
|
161
|
+
```ruby
|
162
|
+
api.get('orders', {id: 1}, {customer_id: 187})
|
163
|
+
```
|
164
|
+
Instead, you'd want to do:
|
165
|
+
```ruby
|
166
|
+
api.get('orders/1', {}, {customer_id: 187})
|
167
|
+
```
|
168
|
+
|
169
|
+
|
133
170
|
## Development
|
134
171
|
|
135
172
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
@@ -0,0 +1,94 @@
|
|
1
|
+
class WhiplashApiError < StandardError
|
2
|
+
class Deadlock < WhiplashApiError
|
3
|
+
end
|
4
|
+
|
5
|
+
class Timeout < WhiplashApiError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Configuration < WhiplashApiError
|
9
|
+
end
|
10
|
+
|
11
|
+
class Inventory < WhiplashApiError
|
12
|
+
end
|
13
|
+
|
14
|
+
class Ignore < WhiplashApiError
|
15
|
+
end
|
16
|
+
|
17
|
+
# 400
|
18
|
+
class BadRequest < WhiplashApiError
|
19
|
+
end
|
20
|
+
|
21
|
+
# 401
|
22
|
+
class Unauthorized < WhiplashApiError
|
23
|
+
end
|
24
|
+
|
25
|
+
# 403
|
26
|
+
class Forbidden < WhiplashApiError
|
27
|
+
end
|
28
|
+
|
29
|
+
# 404
|
30
|
+
class RecordNotFound < WhiplashApiError
|
31
|
+
end
|
32
|
+
|
33
|
+
# 405
|
34
|
+
class MethodNotAllowed < WhiplashApiError
|
35
|
+
end
|
36
|
+
|
37
|
+
# 406
|
38
|
+
class NotAcceptable < WhiplashApiError
|
39
|
+
end
|
40
|
+
|
41
|
+
# 408
|
42
|
+
class Timeout < WhiplashApiError
|
43
|
+
end
|
44
|
+
|
45
|
+
# 409
|
46
|
+
class Conflict < WhiplashApiError
|
47
|
+
end
|
48
|
+
|
49
|
+
# 415
|
50
|
+
class UnsupportedMediaType < WhiplashApiError
|
51
|
+
end
|
52
|
+
|
53
|
+
# 422
|
54
|
+
class UnprocessableEntity < WhiplashApiError
|
55
|
+
end
|
56
|
+
|
57
|
+
# 429
|
58
|
+
class OverRateLimit < WhiplashApiError
|
59
|
+
end
|
60
|
+
|
61
|
+
class SSLError < WhiplashApiError
|
62
|
+
end
|
63
|
+
|
64
|
+
# 500+
|
65
|
+
class InternalServerError < WhiplashApiError
|
66
|
+
end
|
67
|
+
|
68
|
+
# 503
|
69
|
+
class ServiceUnavailable < WhiplashApiError
|
70
|
+
end
|
71
|
+
|
72
|
+
# ???
|
73
|
+
class UnknownError < WhiplashApiError
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.codes
|
77
|
+
{
|
78
|
+
400 => WhiplashApiError::BadRequest,
|
79
|
+
401 => WhiplashApiError::Unauthorized,
|
80
|
+
403 => WhiplashApiError::Forbidden,
|
81
|
+
404 => WhiplashApiError::RecordNotFound,
|
82
|
+
405 => WhiplashApiError::MethodNotAllowed,
|
83
|
+
406 => WhiplashApiError::NotAcceptable,
|
84
|
+
408 => WhiplashApiError::Timeout,
|
85
|
+
409 => WhiplashApiError::Conflict,
|
86
|
+
415 => WhiplashApiError::UnsupportedMediaType,
|
87
|
+
422 => WhiplashApiError::UnprocessableEntity,
|
88
|
+
429 => WhiplashApiError::OverRateLimit,
|
89
|
+
495 => WhiplashApiError::SSLError,
|
90
|
+
500 => WhiplashApiError::InternalServerError,
|
91
|
+
503 => WhiplashApiError::ServiceUnavailable
|
92
|
+
}
|
93
|
+
end
|
94
|
+
end
|
data/lib/whiplash/app.rb
CHANGED
@@ -4,46 +4,76 @@ require "whiplash/app/connections"
|
|
4
4
|
require "whiplash/app/finder_methods"
|
5
5
|
require "whiplash/app/signing"
|
6
6
|
require "whiplash/app/version"
|
7
|
+
require "errors/whiplash_api_error"
|
7
8
|
require "oauth2"
|
8
9
|
require "faraday_middleware"
|
9
10
|
|
10
11
|
module Whiplash
|
12
|
+
class App
|
13
|
+
include Whiplash::App::ApiConfig
|
14
|
+
include Whiplash::App::Caching
|
15
|
+
include Whiplash::App::Connections
|
16
|
+
include Whiplash::App::FinderMethods
|
17
|
+
extend Whiplash::App::Signing
|
11
18
|
|
12
|
-
|
13
|
-
class << self
|
14
|
-
include Whiplash::App::ApiConfig
|
15
|
-
include Whiplash::App::Caching
|
16
|
-
include Whiplash::App::Connections
|
17
|
-
include Whiplash::App::FinderMethods
|
18
|
-
include Whiplash::App::Signing
|
19
|
+
attr_accessor :customer_id, :shop_id, :token
|
19
20
|
|
20
|
-
|
21
|
+
def initialize(token=nil, options={})
|
22
|
+
opts = options.with_indifferent_access
|
23
|
+
@token = format_token(token) unless token.nil?
|
24
|
+
@customer_id = options[:customer_id]
|
25
|
+
@shop_id = options[:shop_id]
|
26
|
+
end
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
def client
|
29
|
+
OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
|
30
|
+
end
|
25
31
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
end
|
34
|
-
return out
|
32
|
+
def connection
|
33
|
+
out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
|
34
|
+
conn.request :oauth2, token.token, token_type: "bearer"
|
35
|
+
conn.request :json
|
36
|
+
conn.response :json, :content_type => /\bjson$/
|
37
|
+
conn.use :instrumentation
|
38
|
+
conn.adapter Faraday.default_adapter
|
35
39
|
end
|
40
|
+
return out
|
41
|
+
end
|
42
|
+
|
43
|
+
def token=(oauth_token)
|
44
|
+
instance_variable_set("@token", format_token(oauth_token))
|
45
|
+
end
|
36
46
|
|
37
|
-
|
38
|
-
|
39
|
-
|
47
|
+
def refresh_token!
|
48
|
+
if token.blank? # If we're storing locally, grab a new token and cache it
|
49
|
+
access_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
|
50
|
+
new_token = access_token.to_hash
|
40
51
|
cache_store["whiplash_api_token"] = new_token
|
52
|
+
else
|
53
|
+
access_token = token.refresh!
|
41
54
|
end
|
55
|
+
self.token = access_token
|
56
|
+
end
|
57
|
+
|
58
|
+
def token_expired?
|
59
|
+
return token.expired? unless token.blank?
|
60
|
+
return true unless cache_store.has_key?("whiplash_api_token")
|
61
|
+
return true if cache_store["whiplash_api_token"].blank?
|
62
|
+
false
|
63
|
+
end
|
42
64
|
|
43
|
-
|
44
|
-
|
45
|
-
|
65
|
+
private
|
66
|
+
def format_token(oauth_token)
|
67
|
+
unless oauth_token.is_a?(OAuth2::AccessToken)
|
68
|
+
raise StandardError, "Token must either be a Hash or an OAuth2::AccessToken" unless oauth_token.is_a?(Hash)
|
69
|
+
oauth_token['expires'] = oauth_token['expires'].to_s # from_hash expects 'true'
|
70
|
+
if oauth_token.has_key?('token')
|
71
|
+
oauth_token['access_token'] = oauth_token['token']
|
72
|
+
oauth_token.delete('token')
|
73
|
+
end
|
74
|
+
oauth_token = OAuth2::AccessToken.from_hash(client, oauth_token)
|
46
75
|
end
|
47
76
|
end
|
77
|
+
|
48
78
|
end
|
49
79
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module Whiplash
|
2
|
-
|
2
|
+
class App
|
3
3
|
module ApiConfig
|
4
4
|
|
5
5
|
def api_url
|
@@ -15,7 +15,11 @@ module Whiplash
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def testing_url
|
18
|
-
ENV["WHIPLASH_API_URL"] || "https://
|
18
|
+
ENV["WHIPLASH_API_URL"] || "https://sandbox.getwhiplash.com"
|
19
|
+
end
|
20
|
+
|
21
|
+
def rate_limit
|
22
|
+
(ENV['WHIPLASH_RATE_LIMIT'] || 25).to_i
|
19
23
|
end
|
20
24
|
|
21
25
|
end
|
data/lib/whiplash/app/caching.rb
CHANGED
@@ -1,17 +1,59 @@
|
|
1
1
|
module Whiplash
|
2
|
-
|
2
|
+
class App
|
3
3
|
module Connections
|
4
4
|
|
5
|
-
def
|
5
|
+
def base_app_request(options={})
|
6
6
|
if options[:params][:id]
|
7
7
|
endpoint = [options[:endpoint], options[:params].delete(:id)].join('/')
|
8
8
|
else
|
9
9
|
endpoint = options[:endpoint]
|
10
10
|
end
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
options[:headers] ||= {}
|
12
|
+
options[:headers][:customer_id] ||= customer_id if customer_id
|
13
|
+
options[:headers][:shop_id] ||= shop_id if shop_id
|
14
|
+
|
15
|
+
args = [
|
16
|
+
options[:method],
|
17
|
+
endpoint,
|
18
|
+
options[:params],
|
19
|
+
sanitize_headers(options[:headers])
|
20
|
+
]
|
21
|
+
|
22
|
+
connection.send(*args)
|
23
|
+
end
|
24
|
+
|
25
|
+
def app_request(options={})
|
26
|
+
return base_app_request(options) unless defined?(Sidekiq)
|
27
|
+
limiter = Sidekiq::Limiter.window('whiplash-core', self.rate_limit, :second, wait_timeout: 15)
|
28
|
+
limiter.within_limit do
|
29
|
+
base_app_request(options)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def app_request!(options = {})
|
34
|
+
begin
|
35
|
+
response = app_request(options)
|
36
|
+
return response.body if response.success?
|
37
|
+
message = response.body if response.body.is_a? String
|
38
|
+
message = response.body.dig('error') if response.body.respond_to?(:dig)
|
39
|
+
store_whiplash_error!(response.status)
|
40
|
+
error_response(response.status, message)
|
41
|
+
rescue Faraday::ConnectionFailed => e
|
42
|
+
case e.message
|
43
|
+
when 'end of file reached'
|
44
|
+
store_whiplash_error!(:eof, options)
|
45
|
+
Rails.logger.error "[Whiplash][EOF] Failed to connect to #{url}"
|
46
|
+
raise ProviderError::InternalServerError, e.message
|
47
|
+
when 'Net::OpenTimeout'
|
48
|
+
store_whiplash_error!(:timeout, options)
|
49
|
+
Rails.logger.error "[Whiplash][Timeout] Request to #{url} timed out"
|
50
|
+
raise ProviderError::Timeout, e.message
|
51
|
+
else
|
52
|
+
store_whiplash_error!(:connection, options)
|
53
|
+
Rails.logger.error "[Whiplash] Request to #{url} failed"
|
54
|
+
raise ProviderError::InternalServerError, e.message
|
55
|
+
end
|
56
|
+
end
|
15
57
|
end
|
16
58
|
|
17
59
|
def delete(endpoint, params = {}, headers = nil)
|
@@ -42,6 +84,65 @@ module Whiplash
|
|
42
84
|
headers: headers)
|
43
85
|
end
|
44
86
|
|
87
|
+
def delete!(endpoint, params = {}, headers = nil)
|
88
|
+
app_request!(method: :delete,
|
89
|
+
endpoint: endpoint,
|
90
|
+
params: params,
|
91
|
+
headers: headers)
|
92
|
+
end
|
93
|
+
|
94
|
+
def get!(endpoint, params = {}, headers = nil)
|
95
|
+
app_request!(method: :get,
|
96
|
+
endpoint: endpoint,
|
97
|
+
params: params,
|
98
|
+
headers: headers)
|
99
|
+
end
|
100
|
+
|
101
|
+
def post!(endpoint, params = {}, headers = nil)
|
102
|
+
app_request!(method: :post,
|
103
|
+
endpoint: endpoint,
|
104
|
+
params: params,
|
105
|
+
headers: headers)
|
106
|
+
end
|
107
|
+
|
108
|
+
def put!(endpoint, params = {}, headers = nil)
|
109
|
+
app_request!(method: :put,
|
110
|
+
endpoint: endpoint,
|
111
|
+
params: params,
|
112
|
+
headers: headers)
|
113
|
+
end
|
114
|
+
|
115
|
+
def get_body_or_empty(endpoint, params = {}, headers = nil)
|
116
|
+
response = get(endpoint, params, headers)
|
117
|
+
if !response.success?
|
118
|
+
case response.status
|
119
|
+
when 500
|
120
|
+
Appsignal.send_error(WhiplashApiError::InternalServerError.new(response.body), {raised: false})
|
121
|
+
else
|
122
|
+
end
|
123
|
+
|
124
|
+
case get_context(endpoint)
|
125
|
+
when 'collection'
|
126
|
+
Rails.logger.info "[WhiplashApi] Returned #{response.status}. Returning an empty array..."
|
127
|
+
return []
|
128
|
+
when 'member'
|
129
|
+
Rails.logger.info "[WhiplashApi] Returned #{response.status}. Returning nil..."
|
130
|
+
return nil
|
131
|
+
when 'aggregate'
|
132
|
+
Rails.logger.info "[WhiplashApi] Returned #{response.status}. Returning an empty hash..."
|
133
|
+
return {}
|
134
|
+
end
|
135
|
+
end
|
136
|
+
response.body
|
137
|
+
end
|
138
|
+
|
139
|
+
def get_context(endpoint)
|
140
|
+
parts = endpoint.split('/').compact
|
141
|
+
return 'member' if (parts.last =~ /\d+/).present?
|
142
|
+
return 'aggregate' if parts.include?('aggregate')
|
143
|
+
'collection'
|
144
|
+
end
|
145
|
+
|
45
146
|
def sanitize_headers(headers)
|
46
147
|
if headers
|
47
148
|
{}.tap do |hash|
|
@@ -52,6 +153,36 @@ module Whiplash
|
|
52
153
|
end
|
53
154
|
end
|
54
155
|
|
156
|
+
def store_whiplash_error!(error, options={})
|
157
|
+
return unless defined?(Appsignal)
|
158
|
+
options = options.with_indifferent_access
|
159
|
+
Appsignal.increment_counter(
|
160
|
+
"whiplash_error",
|
161
|
+
1.0,
|
162
|
+
shop_id: options[:shop_id],
|
163
|
+
customer_id: options[:customer_id],
|
164
|
+
error: error.to_s
|
165
|
+
)
|
166
|
+
end
|
167
|
+
|
168
|
+
def error_codes
|
169
|
+
WhiplashApiError.codes
|
170
|
+
end
|
171
|
+
|
172
|
+
def select_error(status_code)
|
173
|
+
unless error_codes.keys.include? status_code
|
174
|
+
Rails.logger.info "[Provider] Unknown status code from #{self.class.name}: #{status_code}"
|
175
|
+
return WhiplashApiError::UnknownError
|
176
|
+
end
|
177
|
+
error_codes[status_code]
|
178
|
+
end
|
179
|
+
|
180
|
+
# Select an applicable error message on a request to a provider.
|
181
|
+
def error_response(status_code, message=nil)
|
182
|
+
message ||= "Your request has been denied as a #{status_code} error"
|
183
|
+
raise select_error(status_code), message
|
184
|
+
end
|
185
|
+
|
55
186
|
end
|
56
187
|
end
|
57
188
|
end
|
data/lib/whiplash/app/signing.rb
CHANGED
@@ -1,11 +1,6 @@
|
|
1
1
|
module Whiplash
|
2
|
-
|
2
|
+
class App
|
3
3
|
module Signing
|
4
|
-
|
5
|
-
def request_body(body)
|
6
|
-
body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
|
7
|
-
end
|
8
|
-
|
9
4
|
def signature(body)
|
10
5
|
sha256 = OpenSSL::Digest::SHA256.new
|
11
6
|
OpenSSL::HMAC.hexdigest(sha256,
|
@@ -17,6 +12,11 @@ module Whiplash
|
|
17
12
|
request.headers["X-WHIPLASH-SIGNATURE"] == signature(body)
|
18
13
|
end
|
19
14
|
|
15
|
+
private
|
16
|
+
|
17
|
+
def request_body(body)
|
18
|
+
body.blank? ? ENV["WHIPLASH_CLIENT_ID"] : body
|
19
|
+
end
|
20
20
|
end
|
21
21
|
end
|
22
22
|
end
|
data/lib/whiplash/app/version.rb
CHANGED
data/whiplash-app.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_dependency "moneta", "~> 0.8.0"
|
24
24
|
|
25
25
|
spec.add_development_dependency "bundler", "~> 1.11"
|
26
|
-
spec.add_development_dependency "rake", "
|
26
|
+
spec.add_development_dependency "rake", ">= 12.3.3"
|
27
27
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
|
+
spec.add_development_dependency "pry" , '~> 0.12.2'
|
28
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: whiplash-app
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Don Sullivan, Mark Dickson
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: oauth2
|
@@ -70,16 +70,16 @@ dependencies:
|
|
70
70
|
name: rake
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - ">="
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version:
|
75
|
+
version: 12.3.3
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version:
|
82
|
+
version: 12.3.3
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
84
|
name: rspec
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '3.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 0.12.2
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 0.12.2
|
97
111
|
description: this gem provides connectivity to the Whiplash API for authentication
|
98
112
|
and REST requests.
|
99
113
|
email:
|
@@ -103,6 +117,7 @@ extensions: []
|
|
103
117
|
extra_rdoc_files: []
|
104
118
|
files:
|
105
119
|
- ".gitignore"
|
120
|
+
- ".idea/.gitignore"
|
106
121
|
- ".rspec"
|
107
122
|
- ".travis.yml"
|
108
123
|
- Gemfile
|
@@ -110,6 +125,7 @@ files:
|
|
110
125
|
- Rakefile
|
111
126
|
- bin/console
|
112
127
|
- bin/setup
|
128
|
+
- lib/errors/whiplash_api_error.rb
|
113
129
|
- lib/whiplash/app.rb
|
114
130
|
- lib/whiplash/app/api_config.rb
|
115
131
|
- lib/whiplash/app/caching.rb
|
@@ -138,10 +154,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
154
|
version: '0'
|
139
155
|
requirements: []
|
140
156
|
rubyforge_project:
|
141
|
-
rubygems_version: 2.
|
157
|
+
rubygems_version: 2.7.6.2
|
142
158
|
signing_key:
|
143
159
|
specification_version: 4
|
144
160
|
summary: this gem provides connectivity to the Whiplash API for authentication and
|
145
161
|
REST requests.
|
146
162
|
test_files: []
|
147
|
-
has_rdoc:
|