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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.travis.yml +16 -0
- data/.yardopts +1 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +84 -0
- data/LICENSE +21 -0
- data/README.md +272 -0
- data/Rakefile +18 -0
- data/heartland-retail.gemspec +17 -0
- data/lib/heartland-retail.rb +1 -0
- data/lib/heartland/client.rb +299 -0
- data/lib/heartland/client/body.rb +13 -0
- data/lib/heartland/client/collection.rb +121 -0
- data/lib/heartland/client/errors.rb +17 -0
- data/lib/heartland/client/resource.rb +215 -0
- data/lib/heartland/client/response.rb +84 -0
- data/lib/heartland/client/uri.rb +117 -0
- data/spec/heartland/client/body_spec.rb +31 -0
- data/spec/heartland/client/resource_spec.rb +318 -0
- data/spec/heartland/client/response_spec.rb +105 -0
- data/spec/heartland/client/uri_spec.rb +157 -0
- data/spec/heartland/client_spec.rb +361 -0
- data/spec/shared_client_context.rb +5 -0
- data/spec/spec_helper.rb +17 -0
- data/vendor/cache/addressable-2.2.8.gem +0 -0
- data/vendor/cache/codeclimate-test-reporter-1.0.8.gem +0 -0
- data/vendor/cache/coderay-1.1.0.gem +0 -0
- data/vendor/cache/coveralls-0.7.11.gem +0 -0
- data/vendor/cache/crack-0.4.2.gem +0 -0
- data/vendor/cache/diff-lcs-1.2.5.gem +0 -0
- data/vendor/cache/docile-1.1.5.gem +0 -0
- data/vendor/cache/faraday-0.17.3.gem +0 -0
- data/vendor/cache/hashie-4.1.0.gem +0 -0
- data/vendor/cache/json-2.3.0.gem +0 -0
- data/vendor/cache/method_source-0.8.2.gem +0 -0
- data/vendor/cache/mime-types-2.4.3.gem +0 -0
- data/vendor/cache/multi_json-1.11.0.gem +0 -0
- data/vendor/cache/multipart-post-2.1.1.gem +0 -0
- data/vendor/cache/netrc-0.10.3.gem +0 -0
- data/vendor/cache/pry-0.10.1.gem +0 -0
- data/vendor/cache/rake-10.4.2.gem +0 -0
- data/vendor/cache/rest-client-1.7.3.gem +0 -0
- data/vendor/cache/rspec-3.2.0.gem +0 -0
- data/vendor/cache/rspec-core-3.2.2.gem +0 -0
- data/vendor/cache/rspec-expectations-3.2.0.gem +0 -0
- data/vendor/cache/rspec-mocks-3.2.1.gem +0 -0
- data/vendor/cache/rspec-support-3.2.2.gem +0 -0
- data/vendor/cache/safe_yaml-1.0.4.gem +0 -0
- data/vendor/cache/simplecov-0.9.2.gem +0 -0
- data/vendor/cache/simplecov-html-0.9.0.gem +0 -0
- data/vendor/cache/slop-3.6.0.gem +0 -0
- data/vendor/cache/term-ansicolor-1.3.0.gem +0 -0
- data/vendor/cache/thor-0.19.1.gem +0 -0
- data/vendor/cache/tins-1.3.5.gem +0 -0
- data/vendor/cache/webmock-1.17.4.gem +0 -0
- 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
|