harpy 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +2 -1
- data/.travis.yml +4 -6
- data/README.md +5 -5
- data/harpy.gemspec +6 -5
- data/lib/harpy.rb +3 -2
- data/lib/harpy/gem_version.rb +14 -0
- data/lib/harpy/resource.rb +109 -93
- data/lib/harpy/version.rb +9 -0
- data/spec/harpy/client_spec.rb +27 -20
- data/spec/harpy/entry_point_spec.rb +14 -14
- data/spec/harpy/resource_spec.rb +224 -224
- data/spec/harpy_spec.rb +20 -20
- metadata +52 -52
- data/Gemfile.lock +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f6437acd381d436843e94394aa9d1c6df605c4ca43ca5fb3e32746a4f6d805ed
|
4
|
+
data.tar.gz: b03c709ef5b4297a059280926773fb376b5f9feb77ab6656ea9f7d169cd69c6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 074b8e393b71cb510d5f8acf96aa2473395689458d4b7b1ca58965b44a1a902be536f77f452a0bd1d5d675b456244d52cf0937b1c266abdc019f16eaed71b475
|
7
|
+
data.tar.gz: 96996c71087173593a8eaed967fdd500a505ab92b6ddcf0e2025cb62ab87735a29748e9e8958ed37573e74ea96c3bdfb8c4afabe5de4865fd5316598c19b705d
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -9,11 +9,11 @@ Client for REST API with HATEOAS
|
|
9
9
|
Dependencies
|
10
10
|
------------
|
11
11
|
|
12
|
-
* Ruby
|
13
|
-
* gem "typhoeus", "
|
14
|
-
* gem "activesupport", ">=
|
15
|
-
* gem "activemodel", ">=
|
16
|
-
* gem "hash-deep-merge", "
|
12
|
+
* Ruby >= 2.4.
|
13
|
+
* gem "typhoeus", ">= 0.6.5"
|
14
|
+
* gem "activesupport", ">= 5.2.0"
|
15
|
+
* gem "activemodel", ">= 5.2.0"
|
16
|
+
* gem "hash-deep-merge", ">= 0.1.1"
|
17
17
|
|
18
18
|
Usage
|
19
19
|
-----
|
data/harpy.gemspec
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "harpy"
|
3
4
|
|
4
5
|
Gem::Specification.new do |s|
|
5
6
|
s.name = "harpy"
|
6
|
-
s.version =
|
7
|
+
s.version = Harpy::VERSION::STRING
|
7
8
|
s.platform = Gem::Platform::RUBY
|
8
9
|
s.authors = ["Joseph HALTER", "Jonathan TRON"]
|
9
10
|
s.email = ["joseph.halter@thetalentbox.com", "jonathan.tron@thetalentbox.com"]
|
@@ -17,10 +18,10 @@ Gem::Specification.new do |s|
|
|
17
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
19
|
s.require_paths = ["lib"]
|
19
20
|
|
20
|
-
s.add_runtime_dependency "typhoeus", "
|
21
|
-
s.add_runtime_dependency "activesupport", [">=
|
22
|
-
s.add_runtime_dependency "activemodel", [">=
|
23
|
-
s.add_runtime_dependency "hash-deep-merge", ["
|
21
|
+
s.add_runtime_dependency "typhoeus", ">= 0.6.5"
|
22
|
+
s.add_runtime_dependency "activesupport", [">= 5.2.0"]
|
23
|
+
s.add_runtime_dependency "activemodel", [">= 5.2.0"]
|
24
|
+
s.add_runtime_dependency "hash-deep-merge", [">= 0.1.1"]
|
24
25
|
|
25
26
|
s.add_development_dependency "rake", [">= 0.8.7"]
|
26
27
|
s.add_development_dependency "rspec"
|
data/lib/harpy.rb
CHANGED
@@ -14,6 +14,7 @@ module Harpy
|
|
14
14
|
autoload :Resource, "harpy/resource"
|
15
15
|
autoload :BodyToBig, "harpy/resource"
|
16
16
|
autoload :UnknownResponseCode, "harpy/resource"
|
17
|
+
autoload :VERSION, "harpy/gem_version"
|
17
18
|
|
18
19
|
def self.client=(new_client)
|
19
20
|
@client = new_client
|
@@ -38,10 +39,10 @@ module Harpy
|
|
38
39
|
def self.entry_point
|
39
40
|
@entry_point || raise(EntryPointRequired, 'you can setup one with Harpy.entry_point_url = "http://localhost"')
|
40
41
|
end
|
41
|
-
|
42
|
+
|
42
43
|
def self.reset
|
43
44
|
@client = nil
|
44
45
|
@entry_point = nil
|
45
46
|
end
|
46
47
|
|
47
|
-
end
|
48
|
+
end
|
data/lib/harpy/resource.rb
CHANGED
@@ -16,6 +16,7 @@ module Harpy
|
|
16
16
|
base.send :include, ActiveModel::Validations
|
17
17
|
base.send :include, ActiveModel::Validations::Callbacks
|
18
18
|
base.define_model_callbacks :save, :create, :update, :destroy, :only => [:before, :after]
|
19
|
+
base.send :include, InstanceMethods
|
19
20
|
end
|
20
21
|
|
21
22
|
def self.from_url(hash)
|
@@ -93,6 +94,14 @@ module Harpy
|
|
93
94
|
delete_from_urn urn id
|
94
95
|
end
|
95
96
|
|
97
|
+
def uman_attribute_name(attr, options = {})
|
98
|
+
attr
|
99
|
+
end
|
100
|
+
|
101
|
+
def lookup_ancestors
|
102
|
+
[self]
|
103
|
+
end
|
104
|
+
|
96
105
|
private
|
97
106
|
|
98
107
|
def url
|
@@ -122,128 +131,135 @@ module Harpy
|
|
122
131
|
end
|
123
132
|
end
|
124
133
|
|
125
|
-
|
126
|
-
@attrs = {}
|
127
|
-
self.attributes = attrs || {}
|
128
|
-
end
|
134
|
+
module InstanceMethods
|
129
135
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
+
def initialize(attrs = nil)
|
137
|
+
@attrs = {}
|
138
|
+
self.attributes = attrs || {}
|
139
|
+
end
|
140
|
+
|
141
|
+
def attributes=(attrs)
|
142
|
+
attrs.each do |key, value|
|
143
|
+
if respond_to? "#{key}="
|
144
|
+
send "#{key}=", value
|
145
|
+
else
|
146
|
+
@attrs[key] = value
|
147
|
+
end
|
136
148
|
end
|
137
149
|
end
|
138
|
-
end
|
139
150
|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
151
|
+
def as_json(*args)
|
152
|
+
hash = @attrs.dup
|
153
|
+
hash.delete "link"
|
154
|
+
hash.delete "urn"
|
155
|
+
hash
|
156
|
+
end
|
146
157
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
158
|
+
def save
|
159
|
+
if valid?
|
160
|
+
run_callbacks :save do
|
161
|
+
json = JSON.generate as_json
|
162
|
+
raise BodyToBig, "Size: #{json.bytesize} bytes (max 1MB)" if json.bytesize > 1.megabyte
|
163
|
+
persisted? ? update(json) : create(json)
|
164
|
+
end
|
165
|
+
else
|
166
|
+
false
|
153
167
|
end
|
154
|
-
else
|
155
|
-
false
|
156
168
|
end
|
157
|
-
end
|
158
169
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
170
|
+
def destroy
|
171
|
+
raise Harpy::UrlRequired unless url
|
172
|
+
run_callbacks :destroy do
|
173
|
+
process_response Harpy.client.delete(url), :destroy
|
174
|
+
end
|
163
175
|
end
|
164
|
-
end
|
165
176
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
177
|
+
def link(rel)
|
178
|
+
link = (@attrs["link"]||[]).detect{|l| l["rel"]==rel.to_s}
|
179
|
+
link["href"] if link
|
180
|
+
end
|
170
181
|
|
171
|
-
|
172
|
-
|
173
|
-
|
182
|
+
def url
|
183
|
+
link "self"
|
184
|
+
end
|
174
185
|
|
175
|
-
|
176
|
-
|
177
|
-
|
186
|
+
def url_collection
|
187
|
+
Harpy.entry_point.resource_url self.class.resource_name
|
188
|
+
end
|
178
189
|
|
179
|
-
|
180
|
-
|
181
|
-
|
190
|
+
def id
|
191
|
+
@attrs["urn"].split(":").last if @attrs["urn"]
|
192
|
+
end
|
182
193
|
|
183
|
-
|
184
|
-
|
185
|
-
|
194
|
+
def persisted?
|
195
|
+
@attrs["urn"].present?
|
196
|
+
end
|
186
197
|
|
187
|
-
|
188
|
-
|
189
|
-
|
198
|
+
def inspect
|
199
|
+
"<#{self.class.name} @attrs:#{@attrs.inspect} @errors:#{errors.full_messages.inspect} persisted:#{persisted?}>"
|
200
|
+
end
|
190
201
|
|
191
|
-
|
192
|
-
|
193
|
-
|
202
|
+
def has_key?(key)
|
203
|
+
@attrs.has_key? key.to_s
|
204
|
+
end
|
194
205
|
|
195
|
-
|
196
|
-
|
197
|
-
|
206
|
+
def hash
|
207
|
+
urn.hash
|
208
|
+
end
|
198
209
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
210
|
+
def ==(other)
|
211
|
+
other.equal?(self) || (urn && other.instance_of?(self.class) && other.urn == urn)
|
212
|
+
end
|
213
|
+
alias_method :eql?, :==
|
203
214
|
|
204
|
-
|
215
|
+
private
|
205
216
|
|
206
|
-
|
207
|
-
|
208
|
-
|
217
|
+
def create(json)
|
218
|
+
run_callbacks :create do
|
219
|
+
process_response Harpy.client.post(url_collection, :body => json), :create
|
220
|
+
end
|
209
221
|
end
|
210
|
-
end
|
211
222
|
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
223
|
+
def update(json)
|
224
|
+
run_callbacks :update do
|
225
|
+
raise Harpy::UrlRequired unless url
|
226
|
+
process_response Harpy.client.put(url, :body => json), :update
|
227
|
+
end
|
216
228
|
end
|
217
|
-
end
|
218
229
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
230
|
+
def process_response(response, context)
|
231
|
+
case response.code
|
232
|
+
when 200, 201, 302
|
233
|
+
@attrs.merge! JSON.parse(response.body)
|
234
|
+
true
|
235
|
+
when 204
|
236
|
+
context==:create ? Harpy.client.invalid_code(response) : true
|
237
|
+
when 401
|
238
|
+
raise Harpy::Unauthorized, "Server returned a 401 response code"
|
239
|
+
when 422
|
240
|
+
JSON.parse(response.body)["errors"].each do |attr, attr_errors|
|
241
|
+
attr_errors.each{|attr_error| errors.add(attr, :invalid, message: attr_error) }
|
242
|
+
end
|
243
|
+
false
|
244
|
+
else
|
245
|
+
Harpy.client.invalid_code response
|
231
246
|
end
|
232
|
-
false
|
233
|
-
else
|
234
|
-
Harpy.client.invalid_code response
|
235
247
|
end
|
236
|
-
end
|
237
248
|
|
238
|
-
|
239
|
-
key = method.to_s
|
240
|
-
if persisted? && !@attrs.has_key?(key)
|
241
|
-
super
|
242
|
-
elsif key=~/[\]=]\z/
|
243
|
-
super
|
244
|
-
else
|
249
|
+
def read_attribute_for_validation(key)
|
245
250
|
@attrs[key]
|
246
251
|
end
|
252
|
+
|
253
|
+
def method_missing(method, *args)
|
254
|
+
key = method.to_s
|
255
|
+
if persisted? && !@attrs.has_key?(key)
|
256
|
+
super
|
257
|
+
elsif key=~/[\]=]\z/
|
258
|
+
super
|
259
|
+
else
|
260
|
+
@attrs[key]
|
261
|
+
end
|
262
|
+
end
|
247
263
|
end
|
248
264
|
end
|
249
265
|
end
|
data/spec/harpy/client_spec.rb
CHANGED
@@ -5,13 +5,20 @@ describe Harpy::Client do
|
|
5
5
|
let(:users_url) { "http://localhost/users" }
|
6
6
|
|
7
7
|
context "by default" do
|
8
|
-
|
8
|
+
describe '#options' do
|
9
|
+
subject { super().options }
|
10
|
+
it { is_expected.to be_empty }
|
11
|
+
end
|
9
12
|
end
|
10
13
|
|
11
14
|
context "initialized with options" do
|
12
15
|
let(:options) { {:username => "harpy", :password => "spec"} }
|
13
16
|
subject { Harpy::Client.new(options) }
|
14
|
-
|
17
|
+
|
18
|
+
describe '#options' do
|
19
|
+
subject { super().options }
|
20
|
+
it { is_expected.to eq(options) }
|
21
|
+
end
|
15
22
|
end
|
16
23
|
|
17
24
|
[:get, :head, :post, :put, :patch, :delete].each do |method|
|
@@ -22,32 +29,32 @@ describe Harpy::Client do
|
|
22
29
|
Typhoeus.stub(entry_url, method: method){@expected}
|
23
30
|
end
|
24
31
|
it "sends a #{method.to_s.upcase} to the url" do
|
25
|
-
subject.send(method, entry_url).
|
32
|
+
expect(subject.send(method, entry_url)).to eq(@expected)
|
26
33
|
end
|
27
34
|
it "merges options" do
|
28
35
|
client = Harpy::Client.new :headers => {"Authorization" => "spec"}
|
29
36
|
Typhoeus.stub(entry_url, method: method){Typhoeus::Response.new :code => 200}
|
30
37
|
response = client.send method, entry_url, :headers => {"X-Files" => "Harpy"}
|
31
|
-
response.request.options[:headers].
|
38
|
+
expect(response.request.options[:headers]).to include({"X-Files" => "Harpy", "Authorization" => "spec"})
|
32
39
|
end
|
33
40
|
end
|
34
41
|
context "with multiple urls" do
|
35
42
|
it "does not execute requests" do
|
36
|
-
|
43
|
+
expect {
|
37
44
|
subject.send method, [entry_url, users_url]
|
38
|
-
}.
|
45
|
+
}.not_to raise_error
|
39
46
|
end
|
40
47
|
it "returns one requests per url" do
|
41
48
|
requests = subject.send method, [entry_url, users_url]
|
42
|
-
requests.size.
|
43
|
-
requests.collect{|r| r.options[:method]}.
|
44
|
-
requests.collect(&:url).
|
49
|
+
expect(requests.size).to eq(2)
|
50
|
+
expect(requests.collect{|r| r.options[:method]}).to match_array([method, method])
|
51
|
+
expect(requests.collect(&:url)).to match_array([entry_url, users_url])
|
45
52
|
end
|
46
53
|
it "merges options" do
|
47
54
|
client = Harpy::Client.new :headers => {"Authorization" => "spec"}
|
48
55
|
requests = client.send method, [entry_url, users_url], :headers => {"X-Files" => "Harpy"}
|
49
56
|
requests.each do |request|
|
50
|
-
request.options[:headers].
|
57
|
+
expect(request.options[:headers]).to include({"X-Files" => "Harpy", "Authorization" => "spec"})
|
51
58
|
end
|
52
59
|
end
|
53
60
|
end
|
@@ -62,35 +69,35 @@ describe Harpy::Client do
|
|
62
69
|
Typhoeus.stub(users_url, method: :get){ @users_response }
|
63
70
|
end
|
64
71
|
it "executes requests in parallel" do
|
65
|
-
Typhoeus::Hydra.hydra.
|
72
|
+
expect(Typhoeus::Hydra.hydra).to receive(:run).once
|
66
73
|
subject.run subject.get([entry_url, users_url])
|
67
74
|
end
|
68
75
|
it "returns responses" do
|
69
76
|
responses = subject.run subject.get([entry_url, users_url])
|
70
|
-
responses.
|
77
|
+
expect(responses).to match_array([@entry_response, @users_response])
|
71
78
|
end
|
72
79
|
it "requests response is filled in" do
|
73
80
|
requests = subject.get([entry_url, users_url])
|
74
81
|
subject.run requests
|
75
|
-
requests[0].response.
|
76
|
-
requests[1].response.
|
82
|
+
expect(requests[0].response).to eq(@entry_response)
|
83
|
+
expect(requests[1].response).to eq(@users_response)
|
77
84
|
end
|
78
85
|
end
|
79
86
|
describe "#invalid_code(response)" do
|
80
87
|
it "raises Harpy::ClientTimeout on request timeout" do
|
81
|
-
|
88
|
+
expect {
|
82
89
|
subject.invalid_code double("Response", :timed_out? => true)
|
83
|
-
}.
|
90
|
+
}.to raise_error Harpy::ClientTimeout
|
84
91
|
end
|
85
92
|
it "raises Harpy::ClientError on code 0" do
|
86
|
-
|
93
|
+
expect {
|
87
94
|
subject.invalid_code double("Response", :timed_out? => false, :code => 0, :return_message => "Could not connect to server")
|
88
|
-
}.
|
95
|
+
}.to raise_error Harpy::ClientError, "Could not connect to server"
|
89
96
|
end
|
90
97
|
it "raises Harpy::InvalidResponseCode with code otherwise" do
|
91
|
-
|
98
|
+
expect {
|
92
99
|
subject.invalid_code double("Response", :timed_out? => false, :code => 404)
|
93
|
-
}.
|
100
|
+
}.to raise_error Harpy::InvalidResponseCode, "404"
|
94
101
|
end
|
95
102
|
end
|
96
103
|
end
|