whiplash-app 0.4.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5b252f73755dcd2e6cc7d907d0009393a0661993
4
- data.tar.gz: d38c18badde2221b2c9de962db701ca54a486739
2
+ SHA256:
3
+ metadata.gz: 3d70f904d63e4b816748c23f7a3287763420f8c222b2e7e4c63519c63614613f
4
+ data.tar.gz: 3a267c71d211814b6e85bfd8fa8075d1f048a90f936187b4353a0d36bc40b4d2
5
5
  SHA512:
6
- metadata.gz: ae66c0ded848674943ad97c2781236569d4d3cfff515ad30218b7025028660ce0cf10374f243d516b553781ca3a023460ca4b25e21713225b0a135c3ec6708cb
7
- data.tar.gz: '04845cdd6a2eb36c1f05b43932f11bf25c6d32d0c91fbb74db2cb7f23b5ca9d84d3f8719fe58926831a4d26c1c07119aa03d3b74a92b7f1528f706fa169b37f1'
6
+ metadata.gz: 27d80f78a200d4d202b1236941ec7b7c5b3b2e185bdcf3c51ba98ce7304010cc258c350f2cf524d684343eae2c8111f837ced586b10fda93439ed2f1e608a556
7
+ data.tar.gz: 92bcf7a2e0bc197d7b97aba7d4748fe4759b9ede63cf60ec9027645b44ee4155057b2e6b63ed2177b2a6d208fb8563409cf2d7dae0bb68a04cca0e8c0eed8e8c
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
  /tmp/
10
10
  .env
11
11
  .ruby-version
12
+ .idea/*
@@ -0,0 +1,99 @@
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
+ # 502
69
+ class Timeout < WhiplashApiError
70
+ end
71
+
72
+ # 503
73
+ class ServiceUnavailable < WhiplashApiError
74
+ end
75
+
76
+ # ???
77
+ class UnknownError < WhiplashApiError
78
+ end
79
+
80
+ def self.codes
81
+ {
82
+ 400 => WhiplashApiError::BadRequest,
83
+ 401 => WhiplashApiError::Unauthorized,
84
+ 403 => WhiplashApiError::Forbidden,
85
+ 404 => WhiplashApiError::RecordNotFound,
86
+ 405 => WhiplashApiError::MethodNotAllowed,
87
+ 406 => WhiplashApiError::NotAcceptable,
88
+ 408 => WhiplashApiError::Timeout,
89
+ 409 => WhiplashApiError::Conflict,
90
+ 415 => WhiplashApiError::UnsupportedMediaType,
91
+ 422 => WhiplashApiError::UnprocessableEntity,
92
+ 429 => WhiplashApiError::OverRateLimit,
93
+ 495 => WhiplashApiError::SSLError,
94
+ 500 => WhiplashApiError::InternalServerError,
95
+ 502 => WhiplashApiError::Timeout,
96
+ 503 => WhiplashApiError::ServiceUnavailable
97
+ }
98
+ end
99
+ end
@@ -4,6 +4,7 @@ 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
 
@@ -19,11 +20,18 @@ module Whiplash
19
20
 
20
21
  def initialize(token=nil, options={})
21
22
  opts = options.with_indifferent_access
23
+ token ||= cache_store["whiplash_api_token"]
22
24
  @token = format_token(token) unless token.nil?
23
25
  @customer_id = options[:customer_id]
24
26
  @shop_id = options[:shop_id]
25
27
  end
26
28
 
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
34
+
27
35
  def client
28
36
  OAuth2::Client.new(ENV["WHIPLASH_CLIENT_ID"], ENV["WHIPLASH_CLIENT_SECRET"], site: api_url)
29
37
  end
@@ -56,7 +64,7 @@ module Whiplash
56
64
 
57
65
  def token_expired?
58
66
  return token.expired? unless token.blank?
59
- return true unless cache_store.has_key?("whiplash_api_token")
67
+ return true unless cache_store.key?("whiplash_api_token")
60
68
  return true if cache_store["whiplash_api_token"].blank?
61
69
  false
62
70
  end
@@ -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
@@ -7,13 +7,13 @@ module Whiplash
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
 
@@ -2,7 +2,7 @@ module Whiplash
2
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
@@ -11,10 +11,49 @@ module Whiplash
11
11
  options[:headers] ||= {}
12
12
  options[:headers][:customer_id] ||= customer_id if customer_id
13
13
  options[:headers][:shop_id] ||= shop_id if shop_id
14
- connection.send(options[:method],
15
- endpoint,
16
- options[:params],
17
- sanitize_headers(options[:headers]))
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
18
57
  end
19
58
 
20
59
  def delete(endpoint, params = {}, headers = nil)
@@ -45,6 +84,65 @@ module Whiplash
45
84
  headers: headers)
46
85
  end
47
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
+
48
146
  def sanitize_headers(headers)
49
147
  if headers
50
148
  {}.tap do |hash|
@@ -55,6 +153,36 @@ module Whiplash
55
153
  end
56
154
  end
57
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
+
58
186
  end
59
187
  end
60
188
  end
@@ -1,5 +1,5 @@
1
1
  module Whiplash
2
2
  class App
3
- VERSION = "0.4.0"
3
+ VERSION = "0.6.2"
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.4.0
4
+ version: 0.6.2
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-09-12 00:00:00.000000000 Z
11
+ date: 2020-07-13 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,7 +154,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
138
154
  version: '0'
139
155
  requirements: []
140
156
  rubyforge_project:
141
- rubygems_version: 2.6.11
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