fulfil-io 0.4.4 → 0.5.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 +21 -0
- data/README.md +55 -4
- data/lib/fulfil/client.rb +46 -15
- data/lib/fulfil/error.rb +27 -0
- data/lib/fulfil/interactive_report.rb +40 -0
- data/lib/fulfil/response_handler.rb +52 -0
- data/lib/fulfil/response_parser.rb +2 -3
- data/lib/fulfil/version.rb +1 -1
- data/lib/fulfil.rb +5 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1100e113c9323c0fa742ece65d8a41923c060c382b1b97ad6c50ad20c550b5a9
|
4
|
+
data.tar.gz: 80535f7bea24218d958812bbd8e4ab789ce966c346e5dfa891369160fc04e536
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b1ea6e403ab80bb108a90dff5f7ad58c39c228ed610ce497c631b5ae45a3ef5d2912100acd95ee33d7f2aa7c5609b71300c7a2322e1b2efcec688646573bf8da
|
7
|
+
data.tar.gz: d63d23350f0775bac73f28fdbabd0645798ed28a0466e7851ade38328a9f17e7a75982bc6bada6c1d951e4eb3ed0a4ffd872c79a6dc3c848f4308ddcfd11db16
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,24 @@
|
|
1
|
+
## 0.4.9
|
2
|
+
|
3
|
+
* Add client tests and stub with Webmock.
|
4
|
+
|
5
|
+
## 0.4.8
|
6
|
+
|
7
|
+
* Feature: Allow more params with InteractiveReport.
|
8
|
+
|
9
|
+
## 0.4.7
|
10
|
+
|
11
|
+
* Bugfix: Accidentally removed the model parameter from the URL.
|
12
|
+
|
13
|
+
## 0.4.6
|
14
|
+
|
15
|
+
* Add InteractiveReport support.
|
16
|
+
|
17
|
+
## 0.4.5
|
18
|
+
|
19
|
+
* Add #delete to client.
|
20
|
+
* Set up Dependabot on GitHub.
|
21
|
+
|
1
22
|
## 0.4.4
|
2
23
|
|
3
24
|
* Pin http dependency to ~> 4.4.0. 5.0+ introduces a frozen string error.
|
data/README.md
CHANGED
@@ -109,6 +109,19 @@ sale['channel'] = 4
|
|
109
109
|
|
110
110
|
fulfil.put(model: sale_model, body: sale)
|
111
111
|
```
|
112
|
+
### Interactive Reports
|
113
|
+
|
114
|
+
As of v0.4.6, interactive report support exists in a basic form.
|
115
|
+
You're able to execute reports with basic params. Responses are
|
116
|
+
transformed to JSON structures.
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
fulfil = Fulfil::Client.new
|
120
|
+
|
121
|
+
report = Fulfil::Report.new(client: fulfil, report_name: 'account.tax.summary.ireport')
|
122
|
+
|
123
|
+
report.execute(start_date: Date.new(2020, 12, 1), end_date: Date.new(2020, 12, 31))
|
124
|
+
```
|
112
125
|
|
113
126
|
## Development
|
114
127
|
|
@@ -116,10 +129,48 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
116
129
|
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
117
130
|
prompt that will allow you to experiment.
|
118
131
|
|
119
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
120
|
-
|
121
|
-
|
122
|
-
|
132
|
+
To install this gem onto your local machine, run `bundle exec rake install`.
|
133
|
+
|
134
|
+
### Release a new version
|
135
|
+
|
136
|
+
We're following semver for the release process of this gem. Make sure to apply the correct semver version for a new release.
|
137
|
+
|
138
|
+
To release a new version, run the `bin/release x.x.x`. That's it.
|
139
|
+
|
140
|
+
> **NOTE:** You don't have to add a v to the version you want to release. The release script will handle that for you.
|
141
|
+
|
142
|
+
### Testing
|
143
|
+
|
144
|
+
For non-client tests, create the test class or case.
|
145
|
+
|
146
|
+
For client tests, you'll need to add a couple steps. If running against a real
|
147
|
+
backend, you'll need to provide a couple of environment variables:
|
148
|
+
`FULFIL_SUBDOMAIN` and `FULFIL_TOKEN`. Additionally, pass `debug: true` to the
|
149
|
+
client instance in the test. This will output the response body. Webmock will
|
150
|
+
probably complain that real requests aren't allowed at this point, offering you
|
151
|
+
the stub. We don't need most of that.
|
152
|
+
|
153
|
+
We will need to capture the response body as JSON and store it in the
|
154
|
+
`test/fixtures` directory. Formatted for readability, please. You'll also need
|
155
|
+
to make note of the path and body of the request. Once you have that, you can
|
156
|
+
generate your stub.
|
157
|
+
|
158
|
+
To stub a request, use (or create) the helper method based on the verb. For
|
159
|
+
example, to stub a `GET` request, use `stub_fulfil_get`. Here's an example:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
def test_find_one
|
163
|
+
stub_fulfil_get('sale.sale/213112', 'sale_sale')
|
164
|
+
|
165
|
+
client = Fulfil::Client.new
|
166
|
+
response = client.find_one(model: 'sale.sale', id: 213_112)
|
167
|
+
|
168
|
+
assert_equal 213_112, response['id']
|
169
|
+
end
|
170
|
+
```
|
171
|
+
|
172
|
+
`stub_fulfil_get` takes two arguments: the URL path (after `/api/v2/model/`)
|
173
|
+
and the fixture file name to be returned.
|
123
174
|
|
124
175
|
## Contributing
|
125
176
|
|
data/lib/fulfil/client.rb
CHANGED
@@ -10,9 +10,18 @@ module Fulfil
|
|
10
10
|
OAUTH_TOKEN = ENV['FULFIL_TOKEN']
|
11
11
|
|
12
12
|
class Client
|
13
|
+
class InvalidClientError < StandardError
|
14
|
+
def message
|
15
|
+
'Client is not configured correctly.'
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
13
19
|
class NotAuthorizedError < StandardError; end
|
20
|
+
|
14
21
|
class UnknownHTTPError < StandardError; end
|
22
|
+
|
15
23
|
class ConnectionError < StandardError; end
|
24
|
+
|
16
25
|
class ResponseError < StandardError; end
|
17
26
|
|
18
27
|
def initialize(subdomain: SUBDOMAIN, token: OAUTH_TOKEN, headers: { 'X-API-KEY' => API_KEY }, debug: false)
|
@@ -21,6 +30,16 @@ module Fulfil
|
|
21
30
|
@debug = debug
|
22
31
|
@headers = headers
|
23
32
|
@headers.delete('X-API-KEY') if @token
|
33
|
+
|
34
|
+
raise InvalidClientError if invalid?
|
35
|
+
end
|
36
|
+
|
37
|
+
def invalid?
|
38
|
+
@subdomain.nil? || @subdomain.empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def valid?
|
42
|
+
!invalid?
|
24
43
|
end
|
25
44
|
|
26
45
|
def find(model:, ids: [], id: nil, fields: %w[id rec_name])
|
@@ -71,13 +90,26 @@ module Fulfil
|
|
71
90
|
parse(results: results)
|
72
91
|
end
|
73
92
|
|
74
|
-
def put(model
|
93
|
+
def put(model: nil, id: nil, endpoint: nil, body: {})
|
75
94
|
uri = URI(model_url(model: model, id: id, endpoint: endpoint))
|
76
95
|
|
77
96
|
result = request(verb: :put, endpoint: uri, json: body)
|
78
97
|
parse(result: result)
|
79
98
|
end
|
80
99
|
|
100
|
+
def delete(model:, id:)
|
101
|
+
uri = URI(model_url(model: model, id: id))
|
102
|
+
|
103
|
+
result = request(verb: :delete, endpoint: uri)
|
104
|
+
parse(result: result)
|
105
|
+
end
|
106
|
+
|
107
|
+
def interactive_report(endpoint:, body: nil)
|
108
|
+
uri = URI("#{base_url}/model/#{endpoint}")
|
109
|
+
result = request(verb: :put, endpoint: uri, json: body)
|
110
|
+
parse(result: result)
|
111
|
+
end
|
112
|
+
|
81
113
|
private
|
82
114
|
|
83
115
|
def parse(result: nil, results: [])
|
@@ -96,34 +128,33 @@ module Fulfil
|
|
96
128
|
results.map { |result| Fulfil::ResponseParser.parse(item: result) }
|
97
129
|
end
|
98
130
|
|
131
|
+
def domain
|
132
|
+
"https://#{@subdomain}.fulfil.io"
|
133
|
+
end
|
134
|
+
|
99
135
|
def base_url
|
100
|
-
|
136
|
+
[domain, 'api', 'v2'].join('/')
|
101
137
|
end
|
102
138
|
|
103
139
|
def model_url(model:, id: nil, endpoint: nil)
|
104
|
-
[base_url, model, id, endpoint].compact.join('/')
|
140
|
+
[base_url, 'model', model, id, endpoint].compact.join('/')
|
105
141
|
end
|
106
142
|
|
107
|
-
def request(verb: :get,
|
143
|
+
def request(endpoint:, verb: :get, **args)
|
144
|
+
raise InvalidClientError if invalid?
|
145
|
+
|
108
146
|
response = client.request(verb, endpoint, args)
|
147
|
+
Fulfil::ResponseHandler.new(response).verify!
|
109
148
|
|
110
|
-
|
111
|
-
response.parse
|
112
|
-
elsif response.code == 401
|
113
|
-
error = response.parse
|
114
|
-
raise NotAuthorizedError, "Not authorized: #{error['error']}: #{error['error_description']}"
|
115
|
-
else
|
116
|
-
puts response.body.to_s
|
117
|
-
raise Error, 'Error encountered while processing response:'
|
118
|
-
end
|
149
|
+
response.parse
|
119
150
|
rescue HTTP::Error => e
|
120
151
|
puts e
|
121
152
|
raise UnknownHTTPError, 'Unhandled HTTP error encountered'
|
122
153
|
rescue HTTP::ConnectionError => e
|
123
154
|
puts "Couldn't connect"
|
124
155
|
raise ConnectionError, "Can't connect to #{base_url}"
|
125
|
-
rescue HTTP::ResponseError =>
|
126
|
-
raise ResponseError, "Can't process response: #{
|
156
|
+
rescue HTTP::ResponseError => e
|
157
|
+
raise ResponseError, "Can't process response: #{e}"
|
127
158
|
[]
|
128
159
|
end
|
129
160
|
|
data/lib/fulfil/error.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fulfil
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
# The `Fulfil::HttpError` is raised whenever an API request returns a 400+ HTTP status code.
|
7
|
+
# See `Fulfil::ResponseHandler` for more information.
|
8
|
+
class HttpError < Error
|
9
|
+
attr_reader :metadata
|
10
|
+
|
11
|
+
def initialize(message, metadata = {})
|
12
|
+
@metadata = metadata
|
13
|
+
super(message)
|
14
|
+
end
|
15
|
+
|
16
|
+
class BadRequest < HttpError; end
|
17
|
+
class AuthorizationRequired < HttpError; end
|
18
|
+
class PaymentRequired < HttpError; end
|
19
|
+
class Forbidden < HttpError; end
|
20
|
+
class NotFound < HttpError; end
|
21
|
+
class MethodNotAllowed < HttpError; end
|
22
|
+
class NotAccepted < HttpError; end
|
23
|
+
class UnprocessableEntity < HttpError; end
|
24
|
+
class TooManyRequests < HttpError; end
|
25
|
+
class InternalServerError < HttpError; end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Fulfil
|
2
|
+
class InteractiveReport
|
3
|
+
def initialize(client:, report:)
|
4
|
+
@client = client
|
5
|
+
@report = report
|
6
|
+
end
|
7
|
+
|
8
|
+
def execute(**params)
|
9
|
+
body = {}
|
10
|
+
|
11
|
+
params.each do |key, value|
|
12
|
+
body[key] = if value.is_a?(Date)
|
13
|
+
serialize_date(value)
|
14
|
+
else
|
15
|
+
value
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
@client.interactive_report(
|
20
|
+
endpoint: report_url,
|
21
|
+
body: [body]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def report_url
|
28
|
+
"#{@report}/execute"
|
29
|
+
end
|
30
|
+
|
31
|
+
def serialize_date(date)
|
32
|
+
{
|
33
|
+
__class__: 'date',
|
34
|
+
year: date.year,
|
35
|
+
month: date.month,
|
36
|
+
day: date.day
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Fulfil
|
4
|
+
# The `Fulfil::ResponseHandler` is parses the HTTP response from Fulfil. If it
|
5
|
+
# encounters an HTTP status code that indicates an error, it will raise an internal
|
6
|
+
# exception that the consumer can catch.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# Fulfil::ResponseHandler.new(@response).verify!
|
10
|
+
# => true
|
11
|
+
#
|
12
|
+
# Fulfil::ResponseHandler.new(@response).verify!
|
13
|
+
# => Fulfil::Error::BadRequest
|
14
|
+
class ResponseHandler
|
15
|
+
HTTP_ERROR_CODES = {
|
16
|
+
400 => Fulfil::HttpError::BadRequest,
|
17
|
+
401 => Fulfil::HttpError::AuthorizationRequired,
|
18
|
+
402 => Fulfil::HttpError::PaymentRequired,
|
19
|
+
403 => Fulfil::HttpError::Forbidden,
|
20
|
+
404 => Fulfil::HttpError::NotFound,
|
21
|
+
405 => Fulfil::HttpError::MethodNotAllowed,
|
22
|
+
406 => Fulfil::HttpError::NotAccepted,
|
23
|
+
422 => Fulfil::HttpError::UnprocessableEntity,
|
24
|
+
429 => Fulfil::HttpError::TooManyRequests,
|
25
|
+
500 => Fulfil::HttpError::InternalServerError
|
26
|
+
}.freeze
|
27
|
+
|
28
|
+
def initialize(response)
|
29
|
+
@response = response
|
30
|
+
@status_code = response.code
|
31
|
+
end
|
32
|
+
|
33
|
+
def verify!
|
34
|
+
return true unless @status_code >= 400
|
35
|
+
|
36
|
+
raise HTTP_ERROR_CODES.fetch(@status_code, Fulfil::HttpError).new(
|
37
|
+
response_body['error_description'],
|
38
|
+
{
|
39
|
+
body: @response.body,
|
40
|
+
headers: @response.headers,
|
41
|
+
status: @response.status
|
42
|
+
}
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def response_body
|
49
|
+
@response_body ||= @response.parse
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -41,7 +41,7 @@ module Fulfil
|
|
41
41
|
def self.group(key_value_tuples)
|
42
42
|
key_value_tuples
|
43
43
|
.group_by { |kv_tuple| kv_tuple[0][0] }
|
44
|
-
.map
|
44
|
+
.map do |group_key, kv_tuples|
|
45
45
|
if kv_tuples.length == 1
|
46
46
|
[group_key, mapped_value_field(value: kv_tuples[0][1])]
|
47
47
|
else
|
@@ -49,7 +49,7 @@ module Fulfil
|
|
49
49
|
attrs = kv_tuples[1..-1].map { |tuple| [tuple[0][1..-1], tuple[1]] }
|
50
50
|
[group_key, [['id', id[1]]].concat(group(attrs)).to_h]
|
51
51
|
end
|
52
|
-
|
52
|
+
end
|
53
53
|
end
|
54
54
|
|
55
55
|
def self.parse(item:)
|
@@ -57,5 +57,4 @@ module Fulfil
|
|
57
57
|
group(key_value_tuples).to_h
|
58
58
|
end
|
59
59
|
end
|
60
|
-
|
61
60
|
end
|
data/lib/fulfil/version.rb
CHANGED
data/lib/fulfil.rb
CHANGED
@@ -1,8 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fulfil/version'
|
4
|
+
require 'fulfil/error'
|
2
5
|
require 'fulfil/client'
|
3
6
|
require 'fulfil/model'
|
7
|
+
require 'fulfil/interactive_report'
|
8
|
+
require 'fulfil/response_handler'
|
4
9
|
require 'fulfil/response_parser'
|
5
10
|
|
6
11
|
module Fulfil
|
7
|
-
class Error < StandardError; end
|
8
12
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fulfil-io
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chris Moore
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-12-26 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: http
|
@@ -95,6 +95,20 @@ dependencies:
|
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
97
|
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: webmock
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
98
112
|
description:
|
99
113
|
email:
|
100
114
|
- chris@knowndecimal.com
|
@@ -111,8 +125,11 @@ files:
|
|
111
125
|
- Rakefile
|
112
126
|
- lib/fulfil.rb
|
113
127
|
- lib/fulfil/client.rb
|
128
|
+
- lib/fulfil/error.rb
|
129
|
+
- lib/fulfil/interactive_report.rb
|
114
130
|
- lib/fulfil/model.rb
|
115
131
|
- lib/fulfil/query.rb
|
132
|
+
- lib/fulfil/response_handler.rb
|
116
133
|
- lib/fulfil/response_parser.rb
|
117
134
|
- lib/fulfil/version.rb
|
118
135
|
homepage: https://github.com/knowndecimal/fulfil
|
@@ -134,7 +151,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
151
|
- !ruby/object:Gem::Version
|
135
152
|
version: '0'
|
136
153
|
requirements: []
|
137
|
-
rubygems_version: 3.1.
|
154
|
+
rubygems_version: 3.1.6
|
138
155
|
signing_key:
|
139
156
|
specification_version: 4
|
140
157
|
summary: Interact with the Fulfil.io API
|