fulfil-io 0.4.4 → 0.5.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/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
|