frenetic 0.0.12 → 0.0.20.alpha.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/.irbrc +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +0 -1
- data/Gemfile +1 -3
- data/README.md +138 -125
- data/frenetic.gemspec +5 -6
- data/lib/frenetic.rb +31 -43
- data/lib/frenetic/concerns/collection_rest_methods.rb +13 -0
- data/lib/frenetic/concerns/configurable.rb +59 -0
- data/lib/frenetic/concerns/hal_linked.rb +59 -0
- data/lib/frenetic/concerns/member_rest_methods.rb +15 -0
- data/lib/frenetic/concerns/structured.rb +53 -0
- data/lib/frenetic/configuration.rb +40 -76
- data/lib/frenetic/middleware/hal_json.rb +23 -0
- data/lib/frenetic/resource.rb +77 -62
- data/lib/frenetic/resource_collection.rb +46 -0
- data/lib/frenetic/version.rb +2 -2
- data/spec/concerns/configurable_spec.rb +50 -0
- data/spec/concerns/hal_linked_spec.rb +116 -0
- data/spec/concerns/member_rest_methods_spec.rb +41 -0
- data/spec/concerns/structured_spec.rb +214 -0
- data/spec/configuration_spec.rb +99 -0
- data/spec/fixtures/test_api_requests.rb +88 -0
- data/spec/frenetic_spec.rb +137 -0
- data/spec/middleware/hal_json_spec.rb +83 -0
- data/spec/resource_collection_spec.rb +80 -0
- data/spec/resource_spec.rb +211 -0
- data/spec/spec_helper.rb +4 -13
- data/spec/support/rspec.rb +5 -0
- data/spec/support/webmock.rb +3 -0
- metadata +59 -57
- data/.rvmrc +0 -1
- data/lib/frenetic/hal_json.rb +0 -23
- data/lib/frenetic/hal_json/response_wrapper.rb +0 -43
- data/lib/recursive_open_struct.rb +0 -79
- data/spec/fixtures/vcr_cassettes/description_error_unauthorized.yml +0 -36
- data/spec/fixtures/vcr_cassettes/description_success.yml +0 -38
- data/spec/lib/frenetic/configuration_spec.rb +0 -189
- data/spec/lib/frenetic/hal_json/response_wrapper_spec.rb +0 -70
- data/spec/lib/frenetic/hal_json_spec.rb +0 -68
- data/spec/lib/frenetic/resource_spec.rb +0 -182
- data/spec/lib/frenetic_spec.rb +0 -129
data/.rvmrc
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
rvm --create use ruby-1.9.3-p194@frenetic > /dev/null
|
data/lib/frenetic/hal_json.rb
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'recursive_open_struct'
|
3
|
-
require 'frenetic/hal_json/response_wrapper'
|
4
|
-
|
5
|
-
class Frenetic
|
6
|
-
|
7
|
-
class HalJson < Faraday::Middleware
|
8
|
-
def call( environment )
|
9
|
-
@app.call(environment).on_complete { |env| on_complete(env) }
|
10
|
-
end
|
11
|
-
|
12
|
-
def on_complete( env )
|
13
|
-
if success? env
|
14
|
-
env[:body] = ResponseWrapper.new( JSON.parse(env[:body]) )
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def success?( env )
|
19
|
-
(200..201) === env[:status]
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
end
|
@@ -1,43 +0,0 @@
|
|
1
|
-
class Frenetic
|
2
|
-
class HalJson < Faraday::Middleware
|
3
|
-
# TODO: The API for this differs greatly from the `inspect` output.
|
4
|
-
# Perhaps the Hash keys should be normalized and then aliased back to the original keys?
|
5
|
-
class ResponseWrapper < RecursiveOpenStruct
|
6
|
-
include Enumerable
|
7
|
-
|
8
|
-
def []( key )
|
9
|
-
self.send(key)
|
10
|
-
end
|
11
|
-
|
12
|
-
def members
|
13
|
-
methods(false).grep(%r{_as_a_hash}).map { |m| m[0...-10] }
|
14
|
-
end
|
15
|
-
alias_method :keys, :members
|
16
|
-
|
17
|
-
def each
|
18
|
-
members.each do |method|
|
19
|
-
yield method, send(method)
|
20
|
-
end
|
21
|
-
|
22
|
-
self
|
23
|
-
end
|
24
|
-
|
25
|
-
class << self
|
26
|
-
# Do not define setters
|
27
|
-
def define_setter( * ); end
|
28
|
-
|
29
|
-
def define_getter( method_name, hash_key )
|
30
|
-
method_name = case method_name
|
31
|
-
when :_embedded then :resources
|
32
|
-
when :_links then :links
|
33
|
-
when :href then :url
|
34
|
-
else method_name
|
35
|
-
end
|
36
|
-
|
37
|
-
super
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
end
|
42
|
-
end
|
43
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# Stolen from https://github.com/aetherknight/recursive-open-struct/blob/master/lib/recursive_open_struct.rb
|
2
|
-
require 'ostruct'
|
3
|
-
|
4
|
-
class RecursiveOpenStruct < OpenStruct
|
5
|
-
|
6
|
-
def new_ostruct_member(name)
|
7
|
-
name = name.to_sym
|
8
|
-
unless self.respond_to?(name)
|
9
|
-
class << self; self; end.class_eval { define_accessors name, name }
|
10
|
-
end
|
11
|
-
name
|
12
|
-
end
|
13
|
-
|
14
|
-
def keys
|
15
|
-
@table.keys
|
16
|
-
end
|
17
|
-
|
18
|
-
###
|
19
|
-
|
20
|
-
def self.define_accessors( *args )
|
21
|
-
define_getter *args
|
22
|
-
define_setter *args
|
23
|
-
define_getter_as_a_hash *args
|
24
|
-
end
|
25
|
-
|
26
|
-
def self.define_getter( method_name, hash_key )
|
27
|
-
define_method( method_name ) do
|
28
|
-
v = @table[hash_key]
|
29
|
-
v.is_a?(Hash) ? self.class.new(v) : v
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def self.define_setter( method_name, hash_key )
|
34
|
-
define_method("#{method_name}=") { |x| modifiable[hash_key] = x }
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.define_getter_as_a_hash( method_name, hash_key )
|
38
|
-
define_method("#{method_name}_as_a_hash") { @table[hash_key] }
|
39
|
-
end
|
40
|
-
|
41
|
-
###
|
42
|
-
|
43
|
-
def debug_inspect(indent_level = 0, recursion_limit = 12)
|
44
|
-
display_recursive_open_struct(@table, indent_level, recursion_limit)
|
45
|
-
end
|
46
|
-
|
47
|
-
def display_recursive_open_struct(ostrct_or_hash, indent_level, recursion_limit)
|
48
|
-
|
49
|
-
if recursion_limit <= 0 then
|
50
|
-
# protection against recursive structure (like in the tests)
|
51
|
-
puts ' '*indent_level + '(recursion limit reached)'
|
52
|
-
else
|
53
|
-
#puts ostrct_or_hash.inspect
|
54
|
-
if ostrct_or_hash.is_a?(self.class) then
|
55
|
-
ostrct_or_hash = ostrct_or_hash.marshal_dump
|
56
|
-
end
|
57
|
-
|
58
|
-
# We'll display the key values like this : key = value
|
59
|
-
# to align display, we look for the maximum key length of the data that will be displayed
|
60
|
-
# (everything except hashes)
|
61
|
-
data_indent = ostrct_or_hash
|
62
|
-
.reject { |k, v| v.is_a?(self.class) || v.is_a?(Hash) }
|
63
|
-
.max {|a,b| a[0].to_s.length <=> b[0].to_s.length}[0].length
|
64
|
-
# puts "max length = #{data_indent}"
|
65
|
-
|
66
|
-
ostrct_or_hash.each do |key, value|
|
67
|
-
if (value.is_a?(self.class) || value.is_a?(Hash)) then
|
68
|
-
puts ' '*indent_level + key.to_s + '.'
|
69
|
-
display_recursive_open_struct(value, indent_level + 1, recursion_limit - 1)
|
70
|
-
else
|
71
|
-
puts ' '*indent_level + key.to_s + ' '*(data_indent - key.to_s.length) + ' = ' + value.inspect
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
true
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|
@@ -1,36 +0,0 @@
|
|
1
|
-
---
|
2
|
-
http_interactions:
|
3
|
-
- request:
|
4
|
-
method: get
|
5
|
-
uri: http://1234567890:@example.org:5447/api
|
6
|
-
body:
|
7
|
-
encoding: US-ASCII
|
8
|
-
string: ''
|
9
|
-
headers:
|
10
|
-
Accept-Encoding:
|
11
|
-
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
-
Accept:
|
13
|
-
- ! '*/*'
|
14
|
-
User-Agent:
|
15
|
-
- Ruby
|
16
|
-
response:
|
17
|
-
status:
|
18
|
-
code: 401
|
19
|
-
message: ! 'Unauthorized '
|
20
|
-
headers:
|
21
|
-
Content-Type:
|
22
|
-
- application/json
|
23
|
-
Content-Length:
|
24
|
-
- '32'
|
25
|
-
Server:
|
26
|
-
- WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09)
|
27
|
-
Date:
|
28
|
-
- Sun, 08 Apr 2012 02:09:53 GMT
|
29
|
-
Connection:
|
30
|
-
- Keep-Alive
|
31
|
-
body:
|
32
|
-
encoding: US-ASCII
|
33
|
-
string: ! '{\"error\":\"401 Unauthorized\"}'
|
34
|
-
http_version: !!null
|
35
|
-
recorded_at: Sun, 08 Apr 2012 02:09:53 GMT
|
36
|
-
recorded_with: VCR 2.0.1
|
@@ -1,38 +0,0 @@
|
|
1
|
-
---
|
2
|
-
http_interactions:
|
3
|
-
- request:
|
4
|
-
method: get
|
5
|
-
uri: http://1234567890:@example.org:5447/api
|
6
|
-
body:
|
7
|
-
encoding: US-ASCII
|
8
|
-
string: ''
|
9
|
-
headers:
|
10
|
-
Accept-Encoding:
|
11
|
-
- gzip;q=1.0,deflate;q=0.6,identity;q=0.3
|
12
|
-
Accept:
|
13
|
-
- ! '*/*'
|
14
|
-
User-Agent:
|
15
|
-
- Ruby
|
16
|
-
response:
|
17
|
-
status:
|
18
|
-
code: 200
|
19
|
-
message: ! 'OK '
|
20
|
-
headers:
|
21
|
-
Content-Type:
|
22
|
-
- application/json
|
23
|
-
Content-Length:
|
24
|
-
- '620'
|
25
|
-
Server:
|
26
|
-
- WEBrick/1.3.1 (Ruby/1.9.2/2011-07-09)
|
27
|
-
Date:
|
28
|
-
- Sun, 08 Apr 2012 02:09:53 GMT
|
29
|
-
Connection:
|
30
|
-
- Keep-Alive
|
31
|
-
body:
|
32
|
-
encoding: US-ASCII
|
33
|
-
string: ! '{"_links":{"self":{"href":"/api/"},"inkers":{"href":"/api/inkers"},"inker":{"href":"/api/inker/{id}"},"sessions":{"href":"/api/sessions"}},"_embedded":{"schema":{"_links":{"self":{"href":"/api/schema"}},"mediaType":"application/vnd.customink-inker_directory-v1+json","inker":{"description":"A
|
34
|
-
CustomInk employee","type":"object","properties":{"first_name":{"type":"string"},"last_name":{"type":"string"},"city":{"type":"string"},"images":{"type":"array","items":{"type":"object"}}}},"session":{"description":"A
|
35
|
-
gatewat for generating an authenticated session.","type":"object","properties":{"st-ticket-FOO":"string"}}}}}'
|
36
|
-
http_version: !!null
|
37
|
-
recorded_at: Sun, 08 Apr 2012 02:09:53 GMT
|
38
|
-
recorded_with: VCR 2.0.1
|
@@ -1,189 +0,0 @@
|
|
1
|
-
describe Frenetic::Configuration do
|
2
|
-
let(:config) { { url:'http://example.org' } }
|
3
|
-
|
4
|
-
let(:cache_cfg) do
|
5
|
-
{
|
6
|
-
metastore: 'foo',
|
7
|
-
entitystore: 'bar'
|
8
|
-
}
|
9
|
-
end
|
10
|
-
|
11
|
-
let(:instance) { described_class.new( config ) }
|
12
|
-
|
13
|
-
subject { instance }
|
14
|
-
|
15
|
-
it { should respond_to(:adapter) }
|
16
|
-
it { should respond_to(:cache) }
|
17
|
-
it { should respond_to(:url) }
|
18
|
-
it { should respond_to(:username) }
|
19
|
-
it { should respond_to(:password) }
|
20
|
-
it { should respond_to(:headers) }
|
21
|
-
it { should respond_to(:request) }
|
22
|
-
it { should respond_to(:response) }
|
23
|
-
it { should respond_to(:middleware) }
|
24
|
-
|
25
|
-
describe '#attributes' do
|
26
|
-
before { instance.use 'MyMiddleware' }
|
27
|
-
|
28
|
-
subject { instance.attributes }
|
29
|
-
|
30
|
-
it { should include(:adapter) }
|
31
|
-
it { should include(:cache) }
|
32
|
-
it { should include(:url) }
|
33
|
-
it { should include(:username) }
|
34
|
-
it { should include(:password) }
|
35
|
-
it { should include(:headers) }
|
36
|
-
it { should include(:request) }
|
37
|
-
it { should include(:response) }
|
38
|
-
it { should_not include(:middleware) }
|
39
|
-
|
40
|
-
it 'should validate the configuration' do
|
41
|
-
instance.should_receive :validate!
|
42
|
-
|
43
|
-
subject
|
44
|
-
end
|
45
|
-
|
46
|
-
context 'with string keys' do
|
47
|
-
let(:config) { {'url' => 'https://example.org'} }
|
48
|
-
|
49
|
-
it 'should symbolize the keys' do
|
50
|
-
subject[:url].should == 'https://example.org'
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
describe '#headers' do
|
56
|
-
let(:attrs) { instance.headers }
|
57
|
-
|
58
|
-
context 'Accepts' do
|
59
|
-
subject { attrs[:accept] }
|
60
|
-
|
61
|
-
it { should == 'application/hal+json' }
|
62
|
-
|
63
|
-
context 'with other specific headers' do
|
64
|
-
before { config.merge!(headers:{foo:123} )}
|
65
|
-
|
66
|
-
it { should == 'application/hal+json' }
|
67
|
-
end
|
68
|
-
|
69
|
-
context 'with a custom Accepts header' do
|
70
|
-
before { config.merge!(headers:{'accept' => 'application/vnd.yoursite-v1.hal+json'} )}
|
71
|
-
|
72
|
-
it { should == 'application/vnd.yoursite-v1.hal+json' }
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context 'User-Agent' do
|
77
|
-
subject { attrs[:user_agent] }
|
78
|
-
|
79
|
-
it { should match %r{Frenetic v\d+\.\d+\.\d+; .+\Z} }
|
80
|
-
|
81
|
-
context 'with a custom value' do
|
82
|
-
before { config.merge!( headers:{user_agent:'MyApp'}) }
|
83
|
-
|
84
|
-
it { should match %r{\AMyApp \(Frenetic v\d+\.\d+\.\d+; .+\)\Z} }
|
85
|
-
end
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
describe '#cache' do
|
90
|
-
before { config.merge!(cache:cache_cfg) }
|
91
|
-
|
92
|
-
subject { instance.cache }
|
93
|
-
|
94
|
-
it { should include(metastore:'foo') }
|
95
|
-
it { should include(entitystore:'bar') }
|
96
|
-
it { should include(ignore_headers:%w[Set-Cookie X-Content-Digest]) }
|
97
|
-
|
98
|
-
context 'with custom ignore headers' do
|
99
|
-
before { cache_cfg.merge!(ignore_headers:%w{Set-Cookie X-My-Header}) }
|
100
|
-
|
101
|
-
it { should include(ignore_headers:%w[Set-Cookie X-My-Header X-Content-Digest]) }
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
describe '#username' do
|
106
|
-
subject { instance.username }
|
107
|
-
|
108
|
-
before { config.merge!(api_key:'api_key') }
|
109
|
-
|
110
|
-
it { should == 'api_key' }
|
111
|
-
|
112
|
-
context 'and an App ID' do
|
113
|
-
before { config.merge!(app_id:'app_id') }
|
114
|
-
|
115
|
-
it { should == 'app_id' }
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
describe '#password' do
|
120
|
-
subject { instance.password }
|
121
|
-
|
122
|
-
before { config.merge!(api_key:'api_key') }
|
123
|
-
|
124
|
-
it { should be_nil }
|
125
|
-
|
126
|
-
context 'and an App ID' do
|
127
|
-
before { config.merge!(app_id:'app_id') }
|
128
|
-
|
129
|
-
it { should == 'api_key' }
|
130
|
-
end
|
131
|
-
end
|
132
|
-
|
133
|
-
describe '#initialize' do
|
134
|
-
subject { instance.attributes }
|
135
|
-
|
136
|
-
it { should be_a Hash }
|
137
|
-
it { should_not be_empty }
|
138
|
-
end
|
139
|
-
|
140
|
-
describe '#validate!' do
|
141
|
-
subject { instance.validate! }
|
142
|
-
|
143
|
-
shared_examples_for 'a misconfigured instance' do
|
144
|
-
it 'by raising an error when empty' do
|
145
|
-
expect{ subject }.to raise_error Frenetic::ConfigurationError
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
context ':url' do
|
150
|
-
before { config.delete :url }
|
151
|
-
|
152
|
-
it_should_behave_like 'a misconfigured instance'
|
153
|
-
end
|
154
|
-
|
155
|
-
context ':cache' do
|
156
|
-
context 'with a missing :metastore' do
|
157
|
-
before { config.merge!(cache:{}) }
|
158
|
-
|
159
|
-
it_should_behave_like 'a misconfigured instance'
|
160
|
-
end
|
161
|
-
|
162
|
-
context 'with a missing :entitystore' do
|
163
|
-
before { config.merge!(cache:{metastore:'store'}) }
|
164
|
-
|
165
|
-
it_should_behave_like 'a misconfigured instance'
|
166
|
-
end
|
167
|
-
|
168
|
-
context 'with no missing properties' do
|
169
|
-
before { config.merge!(cache:{metastore:'mstore',entitystore:'estore'}) }
|
170
|
-
|
171
|
-
it 'should not raise an error' do
|
172
|
-
expect{ subject }.to_not raise_error
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
describe '#use' do
|
179
|
-
before do
|
180
|
-
stub_const 'MyMiddleware', Class.new
|
181
|
-
|
182
|
-
instance.use MyMiddleware, foo:123
|
183
|
-
end
|
184
|
-
|
185
|
-
it 'should use the middleware' do
|
186
|
-
subject.middleware.should include [ MyMiddleware, {foo:123} ]
|
187
|
-
end
|
188
|
-
end
|
189
|
-
end
|
@@ -1,70 +0,0 @@
|
|
1
|
-
describe Frenetic::HalJson::ResponseWrapper do
|
2
|
-
let(:properties) do
|
3
|
-
{ 'a' => 1, 'b' => 2 }
|
4
|
-
end
|
5
|
-
let(:wrapper) { Frenetic::HalJson::ResponseWrapper.new( properties ) }
|
6
|
-
|
7
|
-
subject { wrapper }
|
8
|
-
|
9
|
-
describe "#members" do
|
10
|
-
subject { wrapper.members }
|
11
|
-
|
12
|
-
its(:size) { should == 2 }
|
13
|
-
its(:first) { should == 'a' }
|
14
|
-
its(:last) { should == 'b' }
|
15
|
-
end
|
16
|
-
|
17
|
-
describe "#keys" do
|
18
|
-
subject { wrapper.keys }
|
19
|
-
|
20
|
-
it { should == wrapper.members }
|
21
|
-
end
|
22
|
-
|
23
|
-
describe "#each" do
|
24
|
-
before do
|
25
|
-
@items = []
|
26
|
-
wrapper.each do |*args|
|
27
|
-
@items << args
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
it { should be_a Frenetic::HalJson::ResponseWrapper }
|
32
|
-
it "should iterate over each getter" do
|
33
|
-
@items.should == [ ['a',1], ['b',2] ]
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
describe ".define_setter" do
|
38
|
-
subject { wrapper.methods(false) }
|
39
|
-
|
40
|
-
it "should not create setters" do
|
41
|
-
subject.none? { |name| name.to_s =~ %r{=} }.should be_true
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
describe ".define_getter" do
|
46
|
-
context "with a :_links property" do
|
47
|
-
let(:properties) { { '_links' => 'foo' } }
|
48
|
-
|
49
|
-
it "should create a :links property" do
|
50
|
-
wrapper.links.should == 'foo'
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
context "with a :_embedded property" do
|
55
|
-
let(:properties) { { '_embedded' => 'foo' } }
|
56
|
-
|
57
|
-
it "should create a :resources property" do
|
58
|
-
wrapper.resources.should == 'foo'
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
context "with a :_embedded property" do
|
63
|
-
let(:properties) { { 'href' => 'foo' } }
|
64
|
-
|
65
|
-
it "should create a :url property" do
|
66
|
-
wrapper.url.should == 'foo'
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|