heartland-retail 5.0.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +16 -0
  5. data/.yardopts +1 -0
  6. data/Gemfile +15 -0
  7. data/Gemfile.lock +84 -0
  8. data/LICENSE +21 -0
  9. data/README.md +272 -0
  10. data/Rakefile +18 -0
  11. data/heartland-retail.gemspec +17 -0
  12. data/lib/heartland-retail.rb +1 -0
  13. data/lib/heartland/client.rb +299 -0
  14. data/lib/heartland/client/body.rb +13 -0
  15. data/lib/heartland/client/collection.rb +121 -0
  16. data/lib/heartland/client/errors.rb +17 -0
  17. data/lib/heartland/client/resource.rb +215 -0
  18. data/lib/heartland/client/response.rb +84 -0
  19. data/lib/heartland/client/uri.rb +117 -0
  20. data/spec/heartland/client/body_spec.rb +31 -0
  21. data/spec/heartland/client/resource_spec.rb +318 -0
  22. data/spec/heartland/client/response_spec.rb +105 -0
  23. data/spec/heartland/client/uri_spec.rb +157 -0
  24. data/spec/heartland/client_spec.rb +361 -0
  25. data/spec/shared_client_context.rb +5 -0
  26. data/spec/spec_helper.rb +17 -0
  27. data/vendor/cache/addressable-2.2.8.gem +0 -0
  28. data/vendor/cache/codeclimate-test-reporter-1.0.8.gem +0 -0
  29. data/vendor/cache/coderay-1.1.0.gem +0 -0
  30. data/vendor/cache/coveralls-0.7.11.gem +0 -0
  31. data/vendor/cache/crack-0.4.2.gem +0 -0
  32. data/vendor/cache/diff-lcs-1.2.5.gem +0 -0
  33. data/vendor/cache/docile-1.1.5.gem +0 -0
  34. data/vendor/cache/faraday-0.17.3.gem +0 -0
  35. data/vendor/cache/hashie-4.1.0.gem +0 -0
  36. data/vendor/cache/json-2.3.0.gem +0 -0
  37. data/vendor/cache/method_source-0.8.2.gem +0 -0
  38. data/vendor/cache/mime-types-2.4.3.gem +0 -0
  39. data/vendor/cache/multi_json-1.11.0.gem +0 -0
  40. data/vendor/cache/multipart-post-2.1.1.gem +0 -0
  41. data/vendor/cache/netrc-0.10.3.gem +0 -0
  42. data/vendor/cache/pry-0.10.1.gem +0 -0
  43. data/vendor/cache/rake-10.4.2.gem +0 -0
  44. data/vendor/cache/rest-client-1.7.3.gem +0 -0
  45. data/vendor/cache/rspec-3.2.0.gem +0 -0
  46. data/vendor/cache/rspec-core-3.2.2.gem +0 -0
  47. data/vendor/cache/rspec-expectations-3.2.0.gem +0 -0
  48. data/vendor/cache/rspec-mocks-3.2.1.gem +0 -0
  49. data/vendor/cache/rspec-support-3.2.2.gem +0 -0
  50. data/vendor/cache/safe_yaml-1.0.4.gem +0 -0
  51. data/vendor/cache/simplecov-0.9.2.gem +0 -0
  52. data/vendor/cache/simplecov-html-0.9.0.gem +0 -0
  53. data/vendor/cache/slop-3.6.0.gem +0 -0
  54. data/vendor/cache/term-ansicolor-1.3.0.gem +0 -0
  55. data/vendor/cache/thor-0.19.1.gem +0 -0
  56. data/vendor/cache/tins-1.3.5.gem +0 -0
  57. data/vendor/cache/webmock-1.17.4.gem +0 -0
  58. metadata +140 -0
@@ -0,0 +1,17 @@
1
+ module HeartlandRetail
2
+ class Client
3
+ ##
4
+ # API request failure
5
+ class RequestFailed < RuntimeError
6
+ attr_accessor :response
7
+ end
8
+
9
+ ##
10
+ # Authorization failure
11
+ class AuthFailed < RequestFailed; end
12
+
13
+ ##
14
+ # Error parsing the response body
15
+ class BodyError < RequestFailed; end
16
+ end
17
+ end
@@ -0,0 +1,215 @@
1
+ require_relative 'collection'
2
+
3
+ module HeartlandRetail
4
+ class Client
5
+ ##
6
+ # An representation of an API resource identified by a URI. Allows
7
+ # triggering API calls via HTTP methods. Allows constructing new resources
8
+ # in a chained style by calling the methods that manipulate the URI and
9
+ # return a new resource.
10
+ #
11
+ # @example Chaining
12
+ # new_resource = resource.
13
+ # filter(:field => 'value').
14
+ # sort(:id).
15
+ # embed(:related_resource)
16
+ #
17
+ # Resources are usually constructed via the Client#[] method.
18
+ class Resource
19
+ ##
20
+ # The resource's URI.
21
+ #
22
+ # @return [Addressable::URI]
23
+ attr_reader :uri
24
+
25
+ ##
26
+ # The underlying HeartlandRetail Client.
27
+ #
28
+ # @return [Client]
29
+ attr_reader :client
30
+
31
+ ##
32
+ # @param [HeartlandRetail::Client] client
33
+ # @param [Addressable::URI, #to_s] uri
34
+ def initialize(client, uri_or_path)
35
+ @client = client
36
+ @uri = normalize_uri(uri_or_path)
37
+ end
38
+
39
+ ##
40
+ # Performs a HEAD request against the resource's URI and returns the Response.
41
+ #
42
+ # @return [Response]
43
+ def head(headers=false); call_client(:head, headers); end
44
+
45
+ ##
46
+ # Performs a HEAD request against the resource's URI. Returns the Response
47
+ # on success and raises a RequestFailed on failure..
48
+ #
49
+ # @raise [RequestFailed] On error response
50
+ #
51
+ # @return [Response]
52
+ def head!(headers=false); call_client(:head!, headers); end
53
+
54
+ ##
55
+ # Performs a GET request against the resource's URI and returns the Response.
56
+ #
57
+ # @return [Response]
58
+ def get(headers=false); call_client(:get, headers); end
59
+
60
+ ##
61
+ # Performs a GET request against the resource's URI. Returns the Response
62
+ # on success and raises a RequestFailed on failure.
63
+ #
64
+ # @raise [RequestFailed] On error response
65
+ #
66
+ # @return [Response]
67
+ def get!(headers=false); call_client(:get!, headers); end
68
+
69
+ ##
70
+ # Performs a DELETE request against the resource's URI and returns the Response.
71
+ #
72
+ # @return [Response]
73
+ def delete(headers=false); call_client(:delete, headers); end
74
+
75
+ ##
76
+ # Performs a DELETE request against the resource's URI. Returns the Response
77
+ # on success and raises a RequestFailed on failure.
78
+ #
79
+ # @raise [RequestFailed] On error response
80
+ #
81
+ # @return [Response]
82
+ def delete!(headers=false); call_client(:delete!, headers); end
83
+
84
+ ##
85
+ # Performs a PUT request against the resource's URI and returns the Response.
86
+ #
87
+ # @return [Response]
88
+ def put(body, headers=false); call_client(:put, body, headers); end
89
+
90
+ ##
91
+ # Performs a PUT request against the resource's URI. Returns the Response
92
+ # on success and raises a RequestFailed on failure.
93
+ #
94
+ # @raise [RequestFailed] On error response
95
+ #
96
+ # @return [Response]
97
+ def put!(body, headers=false); call_client(:put!, body, headers); end
98
+
99
+ ##
100
+ # Performs a POST request against the resource's URI and returns the Response.
101
+ #
102
+ # @return [Response]
103
+ def post(body, headers=false); call_client(:post, body, headers); end
104
+
105
+ ##
106
+ # Performs a POST request against the resource's URI. Returns the Response
107
+ # on success and raises a RequestFailed on failure.
108
+ #
109
+ # @raise [RequestFailed] On error response
110
+ #
111
+ # @return [Response]
112
+ def post!(body, headers=false); call_client(:post!, body, headers); end
113
+
114
+ ##
115
+ # Returns a new subordinate resource with the given sub-path.
116
+ #
117
+ # @return [Resource]
118
+ def [](uri)
119
+ clone(self.uri.subpath(uri))
120
+ end
121
+
122
+ ##
123
+ # If called with +params+ as a +Hash+:
124
+ #
125
+ # Returns a new resource where the given query hash
126
+ # is merged with the existing query string parameters.
127
+ #
128
+ # If called with no arguments:
129
+ #
130
+ # Returns the resource's current query string parameters and values
131
+ # as a hash.
132
+ #
133
+ # @return [Resource, Hash]
134
+
135
+ ##
136
+ # @overload query(params)
137
+ # Returns a new resource where the given +params+ hash of parameter
138
+ # names and values is merged with the existing query string parameters.
139
+ #
140
+ # @param [Hash] params New query string parameters
141
+ # @return [Resource]
142
+ #
143
+ # @overload query()
144
+ # Returns the resource's current query string parameters and values
145
+ # as a hash.
146
+ #
147
+ # @return [Hash]
148
+ def query(params=nil)
149
+ if params
150
+ uri = self.uri.dup
151
+ uri.merge_query_values!(params)
152
+ clone(uri)
153
+ else
154
+ self.uri.query_values || {}
155
+ end
156
+ end
157
+
158
+ alias params query
159
+
160
+ ##
161
+ # Returns a cloned copy of the resource with the same URI.
162
+ #
163
+ # @return [Resource]
164
+ def clone(uri=nil)
165
+ self.class.new(client, uri ? uri : self.uri)
166
+ end
167
+
168
+ ##
169
+ # Returns a new resource with the given embeds added to the query string
170
+ # (via _include params).
171
+ #
172
+ # @return [Resource]
173
+ def embed(*embeds)
174
+ embeds = (query['_include'] || []) + embeds
175
+ query('_include' => embeds)
176
+ end
177
+
178
+ ##
179
+ # Returns true if a HEAD request to the resource returns a successful response,
180
+ # false if it returns 404, otherwise raises an exception.
181
+ #
182
+ # @raise [RequestFailed] If response is not success or 404
183
+ #
184
+ # @return [Boolean]
185
+ def exists?
186
+ response = head
187
+ return true if response.success?
188
+ return false if response.status == 404
189
+ error = RequestFailed.new "Request during call to 'exists?' resulted in non-404 error."
190
+ error.response = response
191
+ raise error
192
+ end
193
+
194
+ include Collection
195
+
196
+ private
197
+
198
+ ##
199
+ # Normalizes the URI or path given to a URI
200
+ def normalize_uri(uri_or_path)
201
+ uri = URI.parse(uri_or_path)
202
+ return uri if uri.to_s.start_with?(client.base_uri.to_s)
203
+ path = uri_or_path.to_s.start_with?('/') ? uri_or_path : "/#{uri_or_path}"
204
+ path.to_s.gsub!(/^#{client.base_uri.to_s}|^#{client.base_uri.path}/, '')
205
+ URI.parse("#{client.base_uri}#{path}")
206
+ end
207
+
208
+ ##
209
+ # Calls a client method, passing the URI as the first argument.
210
+ def call_client(method, *args, &block)
211
+ client.__send__(method, *args.unshift(uri), &block)
212
+ end
213
+ end
214
+ end
215
+ end
@@ -0,0 +1,84 @@
1
+ module HeartlandRetail
2
+ class Client
3
+ ##
4
+ # An API response including body, headers, and status information.
5
+ class Response
6
+ ##
7
+ # @param [Response] response
8
+ # @param [Client] client
9
+ def initialize(response, client)
10
+ @response = response
11
+ @client = client
12
+ end
13
+
14
+ ##
15
+ # Returns the corresponding key from "body".
16
+ def [](key)
17
+ body[key]
18
+ end
19
+
20
+ ##
21
+ # Returns the raw response body as a String.
22
+ #
23
+ # @return [String] The raw response body
24
+ def raw_body
25
+ @response.body
26
+ end
27
+
28
+ ##
29
+ # Returns the parsed response body as a Body object.
30
+ #
31
+ # @raise [BodyError] If the body is not parseable
32
+ #
33
+ # @return [Body] The parsed response body
34
+ def body
35
+ @data ||= parse_body
36
+ end
37
+
38
+ ##
39
+ # Returns true if the request was successful, else false.
40
+ #
41
+ # @return [Boolean]
42
+ def success?
43
+ status < 400
44
+ end
45
+
46
+ ##
47
+ # Delegates missing methods to the underlying Faraday::Response.
48
+ #
49
+ # @see https://www.rubydoc.info/gems/faraday/Faraday/Response Faraday::Response docs
50
+ def method_missing(method, *args, &block)
51
+ @response.respond_to?(method) ? @response.__send__(method, *args, &block) : super
52
+ end
53
+
54
+ ##
55
+ # If the response included a 'Location' header, returns a new Resource with
56
+ # a URI set to its value, else nil.
57
+ #
58
+ # @return [Resource]
59
+ def resource
60
+ if location = headers['Location']
61
+ @client[headers['Location']]
62
+ else
63
+ nil
64
+ end
65
+ end
66
+
67
+ protected
68
+
69
+ def parse_body
70
+ if @response.body.empty?
71
+ raise BodyError,
72
+ "Response body is empty. (Hint: If you just created a new resource, try: response.resource.get)"
73
+ end
74
+
75
+ begin
76
+ data = JSON.parse(@response.body)
77
+ Body.new data
78
+ rescue JSON::ParserError => e
79
+ raise BodyError, "Can't parse response body. (Hint: Try the raw_body method.)"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,117 @@
1
+ require 'uri'
2
+
3
+ module HeartlandRetail
4
+ class Client
5
+ ##
6
+ # A wrapper around URI
7
+ class URI
8
+ ##
9
+ # Returns a URI object based on the parsed string.
10
+ #
11
+ # @return [URI]
12
+ def self.parse(value)
13
+ return value.dup if value.is_a?(self)
14
+ new(::URI.parse(value.to_s))
15
+ end
16
+
17
+ ##
18
+ # Creates a new URI object from an Addressable::URI
19
+ #
20
+ # @return [URI]
21
+ def initialize(uri)
22
+ @uri = uri
23
+ end
24
+
25
+ ##
26
+ # Clones the URI object
27
+ #
28
+ # @return [URI]
29
+ def dup
30
+ self.class.new(@uri.dup)
31
+ end
32
+
33
+ ##
34
+ # Returns a new URI with the given subpath appended to it. Ensures a single
35
+ # forward slash between the URI's path and the given subpath.
36
+ #
37
+ # @return [URI]
38
+ def subpath(subpath)
39
+ uri = dup
40
+ uri.path = "#{path}/" unless path.end_with?('/')
41
+ uri.path = uri.path + ::URI.encode(subpath.to_s.gsub(/^\//, ''))
42
+ uri
43
+ end
44
+
45
+ ##
46
+ # Merges the given hash of query string parameters and values with the URI's
47
+ # existing query string parameters (if any).
48
+ def merge_query_values!(values)
49
+ old_query_values = self.query_values || {}
50
+ self.query_values = old_query_values.merge(normalize_query_hash(values))
51
+ end
52
+
53
+ ##
54
+ # Checks if supplied URI matches current URI
55
+ #
56
+ # @return [boolean]
57
+ def ==(other_uri)
58
+ return false unless other_uri.is_a?(self.class)
59
+ uri == other_uri.__send__(:uri)
60
+ end
61
+
62
+ ##
63
+ # Overwrites the query using the supplied query values
64
+ def query_values=(values)
65
+ self.query = ::URI.encode_www_form(normalize_query_hash(values).sort)
66
+ end
67
+
68
+ ##
69
+ # Returns a hash of query string parameters and values
70
+ #
71
+ # @return [hash]
72
+ def query_values
73
+ return nil if query.nil?
74
+ ::URI.decode_www_form(query).each_with_object({}) do |(k, v), hash|
75
+ if k.end_with?('[]')
76
+ k.gsub!(/\[\]$/, '')
77
+ hash[k] = Array(hash[k]) + [v]
78
+ else
79
+ hash[k] = v
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ attr_reader :uri
87
+
88
+ def self.delegate_and_wrap(*methods)
89
+ methods.each do |method|
90
+ define_method(method) do |*args, &block|
91
+ @uri.__send__(method, *args, &block)
92
+ end
93
+ end
94
+ end
95
+
96
+ delegate_and_wrap(
97
+ :path, :path=, :to_s, :query, :query=
98
+ )
99
+
100
+ def normalize_query_hash(hash)
101
+ hash.inject({}) do |copy, (k, v)|
102
+ k = "#{k}[]" if v.is_a?(Array) && !k.to_s.end_with?('[]')
103
+ copy[k.to_s] = normalize_query_value(v)
104
+ copy
105
+ end
106
+ end
107
+
108
+ def normalize_query_value(value)
109
+ case value
110
+ when Hash then normalize_query_hash(value)
111
+ when true, false then value.to_s
112
+ when Array then value.uniq
113
+ else value end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe HeartlandRetail::Client::Body do
4
+ let(:hash) { {"key1" => "val1", "key2" => {"subkey1" => "subval1"}} }
5
+ let(:body) { HeartlandRetail::Client::Body.new(hash)}
6
+
7
+ describe "[]" do
8
+ it "should support string keys" do
9
+ expect(body["key1"]).to eq("val1")
10
+ end
11
+
12
+ it "should support symbol keys" do
13
+ expect(body[:key1]).to eq("val1")
14
+ end
15
+ end
16
+
17
+ describe "nested hashes" do
18
+ it "should support nested indifferent access" do
19
+ expect(body[:key2][:subkey1]).to eq("subval1")
20
+ expect(body['key2']['subkey1']).to eq("subval1")
21
+ expect(body[:key2]['subkey1']).to eq("subval1")
22
+ expect(body['key2'][:subkey1]).to eq("subval1")
23
+ end
24
+ end
25
+
26
+ describe "to_hash" do
27
+ it "should return the original hash" do
28
+ expect(body.to_hash).to be === hash
29
+ end
30
+ end
31
+ end