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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7be62208f07b3dabac64da70cd97d7ef5d5e513a363d830206ffc6ad25ebffc5
4
- data.tar.gz: ddba02ac333a132dcce3886f51b35865d58f4a999adba7f1f10a9ee3cec5e344
3
+ metadata.gz: 1100e113c9323c0fa742ece65d8a41923c060c382b1b97ad6c50ad20c550b5a9
4
+ data.tar.gz: 80535f7bea24218d958812bbd8e4ab789ce966c346e5dfa891369160fc04e536
5
5
  SHA512:
6
- metadata.gz: 02e3e8e5ae922598b1971b96ba26ba2a17c6f05cc4c2ea319bc0e3d69b75fdfc9115ee860b43d4c2965e292f80470ea17d9c07819d0d30aef03610fa1611ae85
7
- data.tar.gz: 000740a6f8cff338590329900671bf297acfd41c83f38c5197bac711706ac934b37e39e55ec5683575b6e9ffbfead3f73b6d766afb4b0d8a5e357aa6db9c6c4b
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`. To
120
- release a new version, update the version number in `version.rb`, and then run
121
- `bundle exec rake release`, which will create a git tag for the version, push
122
- git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
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:, id:, endpoint: nil, body: {})
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
- "https://#{@subdomain}.fulfil.io/api/v2/model"
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, endpoint:, **args)
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
- if response.status.ok? || response.status.created?
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 => ex
126
- raise ResponseError, "Can't process response: #{ex}"
156
+ rescue HTTP::ResponseError => e
157
+ raise ResponseError, "Can't process response: #{e}"
127
158
  []
128
159
  end
129
160
 
@@ -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 { |group_key, kv_tuples|
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fulfil
4
- VERSION = '0.4.4'
4
+ VERSION = "0.5.0"
5
5
  end
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.4
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-05-20 00:00:00.000000000 Z
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.4
154
+ rubygems_version: 3.1.6
138
155
  signing_key:
139
156
  specification_version: 4
140
157
  summary: Interact with the Fulfil.io API