whiplash-app 0.3.1 → 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9acc3cba8b3e4cef52a2068e9fe3f16c81d7815f
4
- data.tar.gz: c351232810577e3f357512c9c4b7e550bf0dbb2b
2
+ SHA256:
3
+ metadata.gz: af0e2584ec8d271d28ae6e773358050f539cc0a49f260507900cdfd2f9cb8d37
4
+ data.tar.gz: c56bd7ecbc582ebf360a71f142098b1043975c65cc54254469a80aa308d656c4
5
5
  SHA512:
6
- metadata.gz: f4bc91939f74965a430b4c5d6f21ca888d568fe9e31fa3e9ef67d94f45324768f983693e8db756836408768ad40cfa7fd9047ffa352fcaabcc88c44332dcc02a
7
- data.tar.gz: 25b613c592e8c728bf9b447624df347e2989bf2b04e2109b371ea200ae57a494883d2382c2a90b56abd8d4e62a56e43bdcb30765aea81b68e5b99d0cb44493f9
6
+ metadata.gz: d937fbb0f4e06b25efee934edef18e7a791a67b3c24a12a07b6757253458b0a9d35acde7ecc9f7de78f2ffa86a3650ace8c595d1ad15e5557e35ff23b23534eb
7
+ data.tar.gz: 345f2b474912b83daa0f6c8e0ee11b9dccc154d9353248da8f92142b0f27e659e7700cd6cd5c388c86a47be913668d1c1d060081dcf04366abfb20eba928e081
data/.gitignore CHANGED
@@ -7,3 +7,6 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ .env
11
+ .ruby-version
12
+ .idea/*
data/README.md CHANGED
@@ -22,7 +22,27 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
- ###Authentication
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
- Once those are set, authentication is handled in app. While authentication is
34
- mostly baked into the calls, there is a method that allows for getting the app's
35
- token from the main app: `Whiplash::App.token`.
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://testing.whiplashmerch.com` (test or dev environment) or `https://www.whiplashmerch.com` (prod environment).
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
- Whiplash::App.create(resource, params, headers)
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
- Whiplash::App.find(resource, id, headers)
56
- Whiplash::App.destroy(resource, id, headers)
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
- Whiplash::App.update(resource, id, params_to_update, headers)
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
- Whiplash::App.find_all('orders', {}, { customer_id: 187 })
69
- Whiplash::App.find('orders', 1)
70
- Whiplash::App.create('orders', { key: "value", key2: "value" }, { customer_id: 187 } )
71
- Whiplash::App.update('orders', 1, { key: "value"}, { customer_id: 187 } )
72
- Whiplash::App.destroy('orders', 1, { customer_id: 187 } )
73
- Whiplash::App.count('customers') #unlike other calls, which return Faraday responses, this call returns an integer.
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
- Whiplash::App.get('orders', {}, {})
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
- Whiplash::App.get('orders/non_restful_action', {}, {})
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
- Whiplash::App.post(endpoint, params, headers) #POST request to the specified endpoint passing the payload in params
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 that
123
- interface. However, if you intend to specify `REDIS` as your key-value store of
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
@@ -4,46 +4,83 @@ 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
- module App
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
- attr_accessor :customer_id, :shop_id
21
+ def initialize(token=nil, options={})
22
+ opts = options.with_indifferent_access
23
+ token ||= cache_store["whiplash_api_token"]
24
+ @token = format_token(token) unless token.nil?
25
+ @customer_id = options[:customer_id]
26
+ @shop_id = options[:shop_id]
27
+ end
21
28
 
22
- def client
23
- OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
24
- end
29
+ def self.whiplash_api_token
30
+ store = Moneta.new(:Redis, host: ENV["REDIS_HOST"], port: ENV["REDIS_PORT"], password: ENV["REDIS_PASSWORD"], expires: 7200)
31
+ cache_store = Moneta::Namespace.new store, Whiplash::App::Caching.namespace_value
32
+ cache_store["whiplash_api_token"]
33
+ end
25
34
 
26
- def connection
27
- out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
28
- conn.request :oauth2, token, token_type: "bearer"
29
- conn.request :json
30
- conn.response :json, :content_type => /\bjson$/
31
- conn.use :instrumentation
32
- conn.adapter Faraday.default_adapter
33
- end
34
- return out
35
+ def client
36
+ OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
37
+ end
38
+
39
+ def connection
40
+ out = Faraday.new [api_url, "api/v2"].join("/") do |conn|
41
+ conn.request :oauth2, token.token, token_type: "bearer"
42
+ conn.request :json
43
+ conn.response :json, :content_type => /\bjson$/
44
+ conn.use :instrumentation
45
+ conn.adapter Faraday.default_adapter
35
46
  end
47
+ return out
48
+ end
36
49
 
37
- def refresh_token!
38
- oauth_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
39
- new_token = oauth_token.token
50
+ def token=(oauth_token)
51
+ instance_variable_set("@token", format_token(oauth_token))
52
+ end
53
+
54
+ def refresh_token!
55
+ if token.blank? # If we're storing locally, grab a new token and cache it
56
+ access_token = client.client_credentials.get_token(scope: ENV["WHIPLASH_CLIENT_SCOPE"])
57
+ new_token = access_token.to_hash
40
58
  cache_store["whiplash_api_token"] = new_token
59
+ else
60
+ access_token = token.refresh!
41
61
  end
62
+ self.token = access_token
63
+ end
42
64
 
43
- def token
44
- refresh_token! unless cache_store["whiplash_api_token"]
45
- return cache_store["whiplash_api_token"]
65
+ def token_expired?
66
+ return token.expired? unless token.blank?
67
+ return true unless cache_store.key?("whiplash_api_token")
68
+ return true if cache_store["whiplash_api_token"].blank?
69
+ false
70
+ end
71
+
72
+ private
73
+ def format_token(oauth_token)
74
+ unless oauth_token.is_a?(OAuth2::AccessToken)
75
+ raise StandardError, "Token must either be a Hash or an OAuth2::AccessToken" unless oauth_token.is_a?(Hash)
76
+ oauth_token['expires'] = oauth_token['expires'].to_s # from_hash expects 'true'
77
+ if oauth_token.has_key?('token')
78
+ oauth_token['access_token'] = oauth_token['token']
79
+ oauth_token.delete('token')
80
+ end
81
+ oauth_token = OAuth2::AccessToken.from_hash(client, oauth_token)
46
82
  end
47
83
  end
84
+
48
85
  end
49
86
  end
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module ApiConfig
4
4
 
5
5
  def api_url
@@ -18,6 +18,10 @@ module Whiplash
18
18
  ENV["WHIPLASH_API_URL"] || "https://sandbox.getwhiplash.com"
19
19
  end
20
20
 
21
+ def rate_limit
22
+ (ENV['WHIPLASH_RATE_LIMIT'] || 25).to_i
23
+ end
24
+
21
25
  end
22
26
  end
23
27
  end
@@ -1,19 +1,19 @@
1
1
  require "moneta"
2
2
  require "whiplash/app/moneta/namespace"
3
3
  module Whiplash
4
- module App
4
+ class App
5
5
  module Caching
6
6
 
7
7
  def cache_store
8
8
  if ENV["REDIS_HOST"]
9
9
  store = Moneta.new(:Redis, host: ENV["REDIS_HOST"], port: ENV["REDIS_PORT"], password: ENV["REDIS_PASSWORD"], expires: 7200)
10
- Moneta::Namespace.new store, namespace_value
10
+ Moneta::Namespace.new store, Whiplash::App::Caching.namespace_value
11
11
  else
12
12
  Moneta.new(:File, dir: "tmp", expires: 7200)
13
13
  end
14
14
  end
15
15
 
16
- def namespace_value
16
+ def self.namespace_value
17
17
  ENV["REDIS_NAMESPACE"] || ENV["WHIPLASH_CLIENT_ID"]
18
18
  end
19
19
 
@@ -1,17 +1,59 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module Connections
4
4
 
5
- def app_request(options = {})
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
- connection.send(options[:method],
12
- endpoint,
13
- options[:params],
14
- sanitize_headers(options[:headers]))
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 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
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
2
+ class App
3
3
  module FinderMethods
4
4
 
5
5
  def count(resource, params = {}, headers = nil)
@@ -1,11 +1,6 @@
1
1
  module Whiplash
2
- module App
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
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
- module App
3
- VERSION = "0.3.1"
2
+ class App
3
+ VERSION = "0.6.1"
4
4
  end
5
5
  end
@@ -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", "~> 10.0"
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.3.1
4
+ version: 0.6.1
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: 2017-07-17 00:00:00.000000000 Z
11
+ date: 2020-05-27 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: '10.0'
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: '10.0'
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.4.5.1
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: