frenetic 0.0.12 → 0.0.20.alpha.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.
- 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
|