hasoffersv3 0.5.5 → 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 +4 -4
- data/.gitignore +2 -0
- data/CHANGELOG.md +3 -0
- data/README.md +6 -0
- data/lib/hasoffersv3/api_error.rb +54 -0
- data/lib/hasoffersv3/client.rb +14 -2
- data/lib/hasoffersv3/configuration.rb +1 -0
- data/lib/hasoffersv3/error.rb +28 -0
- data/lib/hasoffersv3/response.rb +15 -2
- data/lib/hasoffersv3/version.rb +1 -1
- data/spec/lib/client_spec.rb +127 -9
- data/spec/lib/response_spec.rb +12 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0ae8c8dcc8a60460f3f3a0954be68f9a9a2208b0
|
4
|
+
data.tar.gz: e8c79bdcb82d3806749f5cb3b6cc9c7b541e79df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 976cb9593dfbed447919aac92021c5a268a696aed5e2627f586b83bd1c59a20614306947093f788aba639131f7aa390d6c5c269f1631bcac9e48c7bfcc720431
|
7
|
+
data.tar.gz: ea19c6886c520a074a274b6710734f17f26b2b97d38d0c12f02aaa40e5010f242910869ef75c070fb911c3c4051792685c18ec7448b079313b92bbe4635996cf
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
# 0.6.0
|
2
|
+
- [#61] Added configurable (via `config.raise_errors = true`) raising of internal exceptions (`HasOffersV3::Error` and its descendants) on various API error conditions. Incompatible changes: JSON parsing error now raises a `HasOffersV3::ParseError` that wraps the original error from a particular JSON driver used. (@vittorius)
|
3
|
+
|
1
4
|
# 0.5.5
|
2
5
|
- [#58] Added `AdvertiserBilling::findAllInvoicesByIds`, `AffiliateBilling::findAllInvoicesByIds` methods. (@kamil89)
|
3
6
|
- [#59] Added `Advertiser::getSingupAnswers` and `Affiliate::getSingupAnswers` methods. (@kamil89)
|
data/README.md
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
### Overview
|
2
2
|
|
3
3
|
[](http://travis-ci.org/applift/hasoffersv3)
|
4
|
+
[](https://badge.fury.io/rb/hasoffersv3)
|
4
5
|
|
5
6
|
### Synopsis
|
6
7
|
|
@@ -28,6 +29,7 @@ HasOffersV3.configure do |config|
|
|
28
29
|
config.api_key = 'Your HasOffers API Key'
|
29
30
|
config.network_id = 'Your HasOffers Network ID'
|
30
31
|
config.read_timeout = 10
|
32
|
+
config.raise_errors = true # add this if you want the hasoffersv3 to raise errors upon detected API error messages in responses; defaults to `false`
|
31
33
|
|
32
34
|
# Optionally configure a proxy:
|
33
35
|
config.proxy_host = 'yourproxy.com'
|
@@ -72,6 +74,10 @@ HasOffersV3::Advertiser.signup({
|
|
72
74
|
})
|
73
75
|
```
|
74
76
|
|
77
|
+
### Error handling
|
78
|
+
|
79
|
+
If `config.raise_errors` was set to `true`, the `hasoffersv3` will raise internal exceptions when error occurs at protocol or business logic level. See the [`HasOffersV3::Error`](https://github.com/applift/hasoffersv3/blob/master/lib/hasoffersv3/error.rb) and its descendants' definitions for more comments and details.
|
80
|
+
|
75
81
|
### Logging
|
76
82
|
|
77
83
|
To enable log you can set a logger in configuration. All HTTP requests and responses will be logged.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'hasoffersv3/error'
|
3
|
+
|
4
|
+
class HasOffersV3
|
5
|
+
# Any error emitted at business logic level of the HasOffersV3 API that is not fatal but tells that a particular operation cannot be performed.
|
6
|
+
class APIError < ResponseError
|
7
|
+
class << self
|
8
|
+
def from_response(response)
|
9
|
+
error_class_chain.each do |error_class|
|
10
|
+
err_msg = error_class.detect(response)
|
11
|
+
break error_class.new(err_msg, response) if err_msg
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def error_class_chain
|
18
|
+
# the order is important, more "blocker-like" errors go before "lax" ones; don't forget to add any new error classes to this chain
|
19
|
+
@error_class_chain ||= [IPNotWhitelistedError, MissingParamError, FieldError, InternalError, UnknownError]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class IPNotWhitelistedError < APIError
|
25
|
+
def self.detect(response)
|
26
|
+
response.error_messages.grep(/IP \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} is not white-listed/).first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class MissingParamError < APIError
|
31
|
+
def self.detect(response)
|
32
|
+
response.error_messages.grep(/Missing required argument/).first
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class FieldError < APIError
|
37
|
+
def self.detect(response)
|
38
|
+
response.error_messages.grep(/Field '.*' does not exist or is not allowed to be used./).first
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class InternalError < APIError
|
43
|
+
def self.detect(response)
|
44
|
+
# E.g. "There was a database error with the trackable id [SE-5888e90b944af]. Contact support for more assistance."
|
45
|
+
response.error_messages.grep(/error with the trackable id/).first
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class UnknownError < APIError
|
50
|
+
def self.detect(response)
|
51
|
+
response.error_messages.first
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/hasoffersv3/client.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'net/http' if RUBY_VERSION < '2'
|
2
2
|
require 'active_support/core_ext/object/to_query'
|
3
|
+
require 'hasoffersv3/error'
|
4
|
+
require 'hasoffersv3/api_error'
|
3
5
|
|
4
6
|
class HasOffersV3
|
5
7
|
class Client
|
6
|
-
|
7
8
|
attr_accessor :configuration
|
8
9
|
|
9
10
|
def initialize(configuration)
|
@@ -32,7 +33,9 @@ class HasOffersV3
|
|
32
33
|
|
33
34
|
logger.log_response(http_response)
|
34
35
|
|
35
|
-
|
36
|
+
with_error_detection do
|
37
|
+
Response.new(http_response, @configuration.json_driver)
|
38
|
+
end
|
36
39
|
end
|
37
40
|
|
38
41
|
def execute_request(net_http, raw_request)
|
@@ -73,5 +76,14 @@ class HasOffersV3
|
|
73
76
|
configuration.http_logger
|
74
77
|
end
|
75
78
|
|
79
|
+
def with_error_detection
|
80
|
+
response = yield
|
81
|
+
return response unless configuration.raise_errors
|
82
|
+
|
83
|
+
raise HTTPError.from_response(response) unless response.http_ok?
|
84
|
+
raise APIError.from_response(response) unless response.status_ok?
|
85
|
+
|
86
|
+
response
|
87
|
+
end
|
76
88
|
end
|
77
89
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support/core_ext/module/delegation'
|
3
|
+
|
4
|
+
class HasOffersV3
|
5
|
+
class Error < StandardError; end
|
6
|
+
|
7
|
+
class ResponseError < Error
|
8
|
+
attr_reader :response
|
9
|
+
delegate :http_status_code, :http_message, :http_headers, to: :response
|
10
|
+
|
11
|
+
def initialize(message, response)
|
12
|
+
super(message)
|
13
|
+
@response = response
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# An error caught when parsing the JSON response body got from API endpoint.
|
18
|
+
# Wraps the original JSON driver error emitted.
|
19
|
+
# Raised always (does not depend on the config settings).
|
20
|
+
class ParseError < ResponseError; end
|
21
|
+
|
22
|
+
# Any HTTP error that has occurred during the call to the API endpoint (gateway timeout, internal server error etc.)
|
23
|
+
class HTTPError < ResponseError
|
24
|
+
def self.from_response(response)
|
25
|
+
new("HTTP error: #{response.http_message}", response)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/hasoffersv3/response.rb
CHANGED
@@ -3,14 +3,27 @@ class HasOffersV3
|
|
3
3
|
attr_reader :body, :http_status_code, :http_message, :http_headers
|
4
4
|
|
5
5
|
def initialize(response, json=default_json_driver)
|
6
|
-
|
6
|
+
begin
|
7
|
+
@body = json.load(response.body.to_s)
|
8
|
+
rescue
|
9
|
+
raise ParseError.new('Error parsing response body, examine the `cause` property for details', response)
|
10
|
+
end
|
11
|
+
|
7
12
|
@http_status_code = response.code
|
8
13
|
@http_message = response.message
|
9
14
|
@http_headers = response.to_hash
|
10
15
|
end
|
11
16
|
|
12
17
|
def success?
|
13
|
-
|
18
|
+
http_ok? && status_ok?
|
19
|
+
end
|
20
|
+
|
21
|
+
def http_ok?
|
22
|
+
@http_status_code.to_s == '200'
|
23
|
+
end
|
24
|
+
|
25
|
+
def status_ok?
|
26
|
+
status == 1
|
14
27
|
end
|
15
28
|
|
16
29
|
def status
|
data/lib/hasoffersv3/version.rb
CHANGED
data/spec/lib/client_spec.rb
CHANGED
@@ -1,30 +1,150 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
describe HasOffersV3::Client do
|
2
4
|
let(:config) { HasOffersV3::Configuration.new }
|
3
|
-
|
5
|
+
let(:client) { HasOffersV3::Client.new(config) }
|
6
|
+
subject { client }
|
4
7
|
|
5
8
|
describe '#new_http' do
|
6
|
-
|
7
9
|
context 'when configuration proxy_host is present' do
|
8
|
-
|
9
|
-
let(:config) {
|
10
|
+
let(:config) do
|
10
11
|
result = HasOffersV3::Configuration.new
|
11
12
|
result.proxy_host = 'proxy.com'
|
12
13
|
result.proxy_port = '8080'
|
13
14
|
result
|
14
|
-
|
15
|
+
end
|
15
16
|
|
16
17
|
it 'generates a connection with proxy' do
|
17
18
|
http_client = subject.new_http(URI('http://hasoffers.com:9300/'))
|
18
19
|
expect(http_client.proxyaddr).to eq('proxy.com')
|
19
20
|
expect(http_client.proxyport).to eq('8080')
|
20
21
|
end
|
21
|
-
|
22
22
|
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#request' do
|
26
|
+
context 'raising errors' do
|
27
|
+
let(:raise_errors) { true }
|
28
|
+
let(:config) { HasOffersV3::Configuration.new(raise_errors: raise_errors) }
|
29
|
+
let(:url) { api_url 'Advertiser' }
|
30
|
+
let(:response_body) { JSON.load(default_return[:body]) } # it's more convenient for us to have response_body as a Ruby object
|
31
|
+
let(:status) { default_return[:status] }
|
32
|
+
let(:headers) { {} }
|
33
|
+
let(:response) do
|
34
|
+
{
|
35
|
+
body: JSON.dump(response_body),
|
36
|
+
status: status,
|
37
|
+
headers: headers
|
38
|
+
}
|
39
|
+
end
|
40
|
+
let(:http_method) { :post }
|
41
|
+
|
42
|
+
before { stub_call(http_method, response) }
|
43
|
+
|
44
|
+
subject { client.request(http_method, 'Advertiser', 'findAll', {}) }
|
45
|
+
|
46
|
+
shared_examples 'does not raise errors when configured to not raise errors' do
|
47
|
+
let(:raise_errors) { false }
|
48
|
+
|
49
|
+
it 'does not raise errors' do
|
50
|
+
expect { subject }.not_to raise_error
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'no errors' do
|
55
|
+
it 'does not raise error if no error messages were detected' do
|
56
|
+
expect { subject }.not_to raise_error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'HTTP errors' do
|
61
|
+
let(:response_body) { nil }
|
62
|
+
let(:headers) { { 'Content-Length' => 0, 'Connection' => 'Close' } }
|
63
|
+
let(:status) { [504, 'GATEWAY_TIMEOUT'] }
|
64
|
+
|
65
|
+
it 'raises an appropriate error when HTTP failure detected' do
|
66
|
+
expect { subject }.to raise_error do |error|
|
67
|
+
expect(error).to be_a HasOffersV3::HTTPError
|
68
|
+
expect(error).to have_attributes(
|
69
|
+
message: 'HTTP error: GATEWAY_TIMEOUT',
|
70
|
+
http_status_code: '504',
|
71
|
+
http_message: 'GATEWAY_TIMEOUT',
|
72
|
+
http_headers: {
|
73
|
+
'content-length' => ['0'],
|
74
|
+
'connection' => ['Close']
|
75
|
+
}
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
it_behaves_like 'does not raise errors when configured to not raise errors'
|
81
|
+
end
|
23
82
|
|
83
|
+
context 'API errors' do
|
84
|
+
shared_examples 'API error is detected and raised' do
|
85
|
+
let(:response_body) do
|
86
|
+
{
|
87
|
+
'response' => {
|
88
|
+
'status' => -1,
|
89
|
+
'httpStatus' => 200,
|
90
|
+
'data' => '',
|
91
|
+
'errors' => [{'publicMessage' => error_message }],
|
92
|
+
'errorMessage' => error_message
|
93
|
+
}
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'raises an appropriate error when such an error detected' do
|
98
|
+
expect { subject }.to raise_error(error_class, error_message)
|
99
|
+
end
|
100
|
+
|
101
|
+
it_behaves_like 'does not raise errors when configured to not raise errors'
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'IP is not whitelisted' do
|
105
|
+
let(:error_message) { 'IP 178.161.91.191 is not white-listed. Please enable it in the application, Support => API' }
|
106
|
+
let(:error_class) { HasOffersV3::IPNotWhitelistedError }
|
107
|
+
|
108
|
+
it_behaves_like 'API error is detected and raised'
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'parameter missing' do
|
112
|
+
subject { client.request(http_method, 'Advertiser', 'findById', {}) }
|
113
|
+
|
114
|
+
let(:error_message) { 'Missing required argument: id' }
|
115
|
+
let(:error_class) { HasOffersV3::MissingParamError }
|
116
|
+
|
117
|
+
it_behaves_like 'API error is detected and raised'
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'field error' do
|
121
|
+
let(:url) { api_url 'Report' }
|
122
|
+
subject { client.request(http_method, 'Report', 'getStats', { 'fields[]' => 'Offer.nam' }) }
|
123
|
+
|
124
|
+
let(:error_message) { "Field 'Offer.nam' does not exist or is not allowed to be used." }
|
125
|
+
let(:error_class) { HasOffersV3::FieldError }
|
126
|
+
|
127
|
+
it_behaves_like 'API error is detected and raised'
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'internal error' do
|
131
|
+
let(:error_message) { 'There was a database error with the trackable id [SE-5888e90b944af]. Contact support for more assistance.' }
|
132
|
+
let(:error_class) { HasOffersV3::InternalError }
|
133
|
+
|
134
|
+
it_behaves_like 'API error is detected and raised'
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'unknown error' do
|
138
|
+
let(:error_message) { 'Something went wrong.' }
|
139
|
+
let(:error_class) { HasOffersV3::UnknownError }
|
140
|
+
|
141
|
+
it_behaves_like 'API error is detected and raised'
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
24
145
|
end
|
25
146
|
|
26
147
|
describe '#base_uri' do
|
27
|
-
|
28
148
|
let(:configuration_to_default_host) { HasOffersV3::Configuration.new }
|
29
149
|
let(:config_for_proxy) {
|
30
150
|
result = HasOffersV3::Configuration.new
|
@@ -39,7 +159,5 @@ describe HasOffersV3::Client do
|
|
39
159
|
proxy_connection = HasOffersV3::Client.new(config_for_proxy)
|
40
160
|
expect(proxy_connection.base_uri).to eq('https://api.applift.com/v3')
|
41
161
|
end
|
42
|
-
|
43
162
|
end
|
44
|
-
|
45
163
|
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'hasoffersv3/error'
|
3
|
+
|
4
|
+
describe HasOffersV3::Response do
|
5
|
+
describe '#initialize' do
|
6
|
+
it 'raises an appropriate error on malformed response body' do
|
7
|
+
expect { described_class.new('"') }.to(
|
8
|
+
raise_error(HasOffersV3::ParseError, 'Error parsing response body, examine the `cause` property for details')
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hasoffersv3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Maximilian Seifert
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2017-
|
12
|
+
date: 2017-07-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -105,12 +105,14 @@ files:
|
|
105
105
|
- lib/hasoffersv3/affiliate.rb
|
106
106
|
- lib/hasoffersv3/affiliate_billing.rb
|
107
107
|
- lib/hasoffersv3/affiliate_offer.rb
|
108
|
+
- lib/hasoffersv3/api_error.rb
|
108
109
|
- lib/hasoffersv3/application.rb
|
109
110
|
- lib/hasoffersv3/base.rb
|
110
111
|
- lib/hasoffersv3/client.rb
|
111
112
|
- lib/hasoffersv3/configuration.rb
|
112
113
|
- lib/hasoffersv3/conversion.rb
|
113
114
|
- lib/hasoffersv3/employee.rb
|
115
|
+
- lib/hasoffersv3/error.rb
|
114
116
|
- lib/hasoffersv3/logger.rb
|
115
117
|
- lib/hasoffersv3/offer.rb
|
116
118
|
- lib/hasoffersv3/offer_pixel.rb
|
@@ -137,6 +139,7 @@ files:
|
|
137
139
|
- spec/lib/offer_spec.rb
|
138
140
|
- spec/lib/raw_log_spec.rb
|
139
141
|
- spec/lib/report_spec.rb
|
142
|
+
- spec/lib/response_spec.rb
|
140
143
|
- spec/spec_helper.rb
|
141
144
|
homepage:
|
142
145
|
licenses:
|
@@ -158,7 +161,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
158
161
|
version: '0'
|
159
162
|
requirements: []
|
160
163
|
rubyforge_project:
|
161
|
-
rubygems_version: 2.6.
|
164
|
+
rubygems_version: 2.6.11
|
162
165
|
signing_key:
|
163
166
|
specification_version: 4
|
164
167
|
summary: REST Client for the HasOffers API, version 3.
|
@@ -181,4 +184,5 @@ test_files:
|
|
181
184
|
- spec/lib/offer_spec.rb
|
182
185
|
- spec/lib/raw_log_spec.rb
|
183
186
|
- spec/lib/report_spec.rb
|
187
|
+
- spec/lib/response_spec.rb
|
184
188
|
- spec/spec_helper.rb
|