api_navigator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +18 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +26 -0
  7. data/.yardopts +8 -0
  8. data/CHANGELOG.md +3 -0
  9. data/CONTRIBUTING.md +4 -0
  10. data/Dangerfile +2 -0
  11. data/Gemfile +23 -0
  12. data/Guardfile +5 -0
  13. data/LICENSE +22 -0
  14. data/README.md +5 -0
  15. data/RELEASING.md +4 -0
  16. data/Rakefile +34 -0
  17. data/UPGRADING.md +4 -0
  18. data/api_navigator.gemspec +23 -0
  19. data/bin/console +15 -0
  20. data/bin/setup +8 -0
  21. data/fixtures_2.rb +253 -0
  22. data/lib/api_navigator.rb +45 -0
  23. data/lib/api_navigator/attributes.rb +20 -0
  24. data/lib/api_navigator/collection_hash.rb +90 -0
  25. data/lib/api_navigator/curie.rb +47 -0
  26. data/lib/api_navigator/entry_point.rb +157 -0
  27. data/lib/api_navigator/link.rb +160 -0
  28. data/lib/api_navigator/link_collection.rb +63 -0
  29. data/lib/api_navigator/resource.rb +130 -0
  30. data/lib/api_navigator/resources/collection_resource.rb +41 -0
  31. data/lib/api_navigator/resources/member_resource.rb +63 -0
  32. data/lib/api_navigator/version.rb +3 -0
  33. data/lib/faraday/connection.rb +17 -0
  34. data/spec/fixtures/requests.rb +157 -0
  35. data/spec/fixtures/sample.json +108 -0
  36. data/spec/lib/api_navigator/attribute_spec.rb +36 -0
  37. data/spec/lib/api_navigator/collection_hash_spec.rb +71 -0
  38. data/spec/lib/api_navigator/entry_point_spec.rb +185 -0
  39. data/spec/lib/api_navigator/link_collection_spec.rb +77 -0
  40. data/spec/lib/api_navigator/link_spec.rb +343 -0
  41. data/spec/lib/api_navigator/resource_spec.rb +368 -0
  42. data/spec/spec_helper.rb +112 -0
  43. data/spec/support/book_resource.rb +10 -0
  44. data/spec/support/request_helper.rb +8 -0
  45. metadata +172 -0
@@ -0,0 +1,108 @@
1
+ # root
2
+
3
+ data: {},
4
+ links: {},
5
+ meta: {},
6
+ errors*: []
7
+
8
+
9
+ # singular
10
+
11
+ data: {
12
+ atr1:
13
+ atr2:
14
+ nested: {
15
+
16
+ }
17
+ },
18
+ links: {
19
+ self: "self-url"
20
+ }
21
+
22
+
23
+ # multiple
24
+
25
+ data: [
26
+
27
+ ]
28
+ },
29
+ links: {
30
+ self: "self-url"
31
+ }
32
+
33
+ # http://localhost:3000/sample/book/1?include=[publisher,authors]&render=compact
34
+ data: {
35
+ title: 'Book 1',
36
+ body: 'Book 1 Body',
37
+ year: 1999,
38
+ publisher: {
39
+ data: {
40
+ name: 'Manning Publisher',
41
+ },
42
+ _links: {
43
+ self: {
44
+ href: "http://localhost:3000/sample/publisher/1",
45
+ }
46
+ },
47
+ _meta: { type: "book" }
48
+ },
49
+ authors: {
50
+ data: [
51
+ # author1,
52
+ # author2
53
+ ],
54
+ _links: {},
55
+ _meta: {}
56
+ }
57
+ },
58
+ _links: {
59
+ self: {
60
+ href: "http://localhost:3000/sample/books/1",
61
+ },
62
+ authors: {
63
+ href: "http://localhost:3000/sample/book/1/authors",
64
+ },
65
+ publisher: {
66
+ href: "http://localhost:3000/sample/book/1/publisher",
67
+ },
68
+ },
69
+ _meta: { type: "book" }
70
+
71
+
72
+ data: {
73
+ title: 'Book 1',
74
+ body: 'Book 1 Body',
75
+ year: 1999,
76
+ _relations: {
77
+ publisher: {
78
+ data: {
79
+ name: 'Manning Publisher',
80
+ },
81
+ _links: {
82
+ self: {
83
+ href: "http://localhost:3000/sample/publisher/1",
84
+ }
85
+ },
86
+ _meta: { type: "book" }
87
+ },
88
+ authors: {
89
+ data: [
90
+ # author1,
91
+ # author2
92
+ ],
93
+ _links: {},
94
+ _meta: {}
95
+ }
96
+ },
97
+ _links: {
98
+ self: {
99
+ href: "http://localhost:3000/sample/books/1",
100
+ },
101
+ authors: {
102
+ href: "http://localhost:3000/sample/book/1/authors",
103
+ },
104
+ publisher: {
105
+ href: "http://localhost:3000/sample/book/1/publisher",
106
+ },
107
+ },
108
+ _meta: { type: "book" }
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiNavigator
4
+ describe Attributes do
5
+ let(:representation) do
6
+ JSON.parse(Fixtures::Requests.book_response)
7
+ end
8
+
9
+ let(:attributes) do
10
+ Attributes.new(representation.fetch('data'))
11
+ end
12
+
13
+ it 'does not set _links as an attribute' do
14
+ expect(attributes).not_to respond_to :_links
15
+ end
16
+
17
+ it 'does not set _meta as an attribute' do
18
+ expect(attributes).not_to respond_to :_meta
19
+ end
20
+
21
+ it 'sets normal attributes' do
22
+ expect(attributes).to respond_to :title
23
+ expect(attributes.title).to be == "Book 1"
24
+
25
+ expect(attributes).to respond_to :body
26
+ expect(attributes.body).to be == 'Book 1 Body'
27
+
28
+ expect(attributes).to respond_to :year
29
+ expect(attributes.year).to be == 1999
30
+ end
31
+
32
+ it 'is a collection' do
33
+ expect(Attributes.ancestors).to include(CollectionHash)
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiNavigator
4
+ describe CollectionHash do
5
+ let(:representation) do
6
+ JSON.parse(Fixtures::Requests.book_response)
7
+ end
8
+
9
+ let(:collection) do
10
+ CollectionHash.new(representation)
11
+ end
12
+
13
+ it 'exposes the collection as methods' do
14
+ expect(collection.data['title']).to be == 'Book 1'
15
+ expect(collection.data).to be_kind_of Hash
16
+ end
17
+
18
+ it 'exposes collection as a hash' do
19
+ expect(collection['data']['body']).to be == 'Book 1 Body'
20
+ expect(collection['data']).to be_kind_of Hash
21
+ end
22
+
23
+ it 'correctly responds to methods' do
24
+ expect(collection).to respond_to :data
25
+ end
26
+
27
+ it 'acts as enumerable' do
28
+ names = collection.map do |name, _value|
29
+ name
30
+ end
31
+ expect(names).to include(*%w[data _links _meta])
32
+ end
33
+
34
+ describe '#to_hash' do
35
+ it 'returns the wrapped collection as a hash' do
36
+ expect(collection.to_hash).to be_kind_of Hash
37
+ end
38
+ end
39
+
40
+ describe 'include?' do
41
+ it 'returns true for keys that exist' do
42
+ expect(collection.include?('_links')).to be_truthy
43
+ end
44
+
45
+ it 'returns false for missing keys' do
46
+ expect(collection.include?('missing key')).to be_falsey
47
+ end
48
+ end
49
+
50
+ describe '#fetch' do
51
+ it 'returns the value for keys that exist' do
52
+ expect(collection.fetch('data')).to be == representation['data']
53
+ expect(collection.fetch('data').fetch('title')).to be == 'Book 1'
54
+ end
55
+
56
+ it 'raises an error for missing keys' do
57
+ expect { collection.fetch('missing key') }.to raise_error KeyError
58
+ end
59
+
60
+ describe 'with a default value' do
61
+ it 'returns the value for keys that exist' do
62
+ expect(collection.fetch('data', 'a_string')).to be_kind_of Hash
63
+ end
64
+
65
+ it 'returns the default value for missing keys' do
66
+ expect(collection.fetch('missing key', 'default')).to be == 'default'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,185 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiNavigator
4
+ describe EntryPoint do
5
+ describe 'default' do
6
+ let(:entry_point) do
7
+ EntryPoint.new 'http://my.api.org'
8
+ end
9
+
10
+ describe 'connection' do
11
+ it 'creates a Faraday connection with the entry point url' do
12
+ expect(entry_point.connection.url_prefix.to_s).to be == 'http://my.api.org/'
13
+ end
14
+
15
+ it 'creates a Faraday connection with the default headers' do
16
+ expect(entry_point.headers['Content-Type']).to be == 'application/hal+json'
17
+ expect(entry_point.headers['Accept']).to be == 'application/hal+json,application/json'
18
+ end
19
+
20
+ it 'can update headers after a connection has been constructed' do
21
+ expect(entry_point.connection).to be_kind_of(Faraday::Connection)
22
+ entry_point.headers.update('Content-Type' => 'application/foobar')
23
+ expect(entry_point.headers['Content-Type']).to be == 'application/foobar'
24
+ end
25
+
26
+ it 'can insert additional middleware after a connection has been constructed' do
27
+ expect(entry_point.connection).to be_kind_of(Faraday::Connection)
28
+ entry_point.connection.use :instrumentation
29
+ handlers = entry_point.connection.builder.handlers
30
+ expect(handlers).to include FaradayMiddleware::Instrumentation
31
+ end
32
+
33
+ it 'creates a Faraday connection with the default block' do
34
+ handlers = entry_point.connection.builder.handlers
35
+
36
+ expect(handlers).to include Faraday::Response::RaiseError
37
+ expect(handlers).to include FaradayMiddleware::FollowRedirects
38
+ expect(handlers).to include FaradayMiddleware::EncodeHalJson
39
+ expect(handlers).to include FaradayMiddleware::ParseHalJson
40
+ expect(handlers).to include Faraday::Adapter::NetHttp
41
+
42
+ expect(entry_point.connection.options.params_encoder).to be == Faraday::FlatParamsEncoder
43
+ end
44
+
45
+ it 'raises a ConnectionAlreadyInitializedError if attempting to modify headers' do
46
+ expect(entry_point.connection).to be_kind_of Faraday::Connection
47
+ expect{ entry_point.headers = {} }.to raise_error ConnectionAlreadyInitializedError
48
+ end
49
+
50
+ it 'raises a ConnectionAlreadyInitializedError if attempting to modify the faraday block' do
51
+ expect(entry_point.connection).to be_kind_of Faraday::Connection
52
+ expect{ entry_point.connection {} }.to raise_error ConnectionAlreadyInitializedError
53
+ end
54
+ end
55
+
56
+ describe 'initialize' do
57
+ it 'sets a Link with the entry point url' do
58
+ expect(entry_point._url).to be == 'http://my.api.org'
59
+ end
60
+ end
61
+ end
62
+
63
+ describe 'faraday_options' do
64
+ let(:entry_point) do
65
+ EntryPoint.new 'http://my.api.org' do |entry_point|
66
+ entry_point.faraday_options = { proxy: 'http://my.proxy:8080' }
67
+ end
68
+ end
69
+
70
+ describe 'connection' do
71
+ it 'creates a Faraday connection with the entry point url' do
72
+ expect(entry_point.connection.url_prefix.to_s).to be == 'http://my.api.org/'
73
+ end
74
+
75
+ it 'creates a Faraday connection with the default headers' do
76
+ expect(entry_point.headers['Content-Type']).to be == 'application/hal+json'
77
+ expect(entry_point.headers['Accept']).to be == 'application/hal+json,application/json'
78
+ end
79
+
80
+ it 'creates a Faraday connection with options' do
81
+ expect(entry_point.connection.proxy).to be_kind_of Faraday::ProxyOptions
82
+ expect(entry_point.connection.proxy.uri.to_s).to be == 'http://my.proxy:8080'
83
+ end
84
+ end
85
+ end
86
+
87
+ describe 'options' do
88
+ let(:entry_point) do
89
+ EntryPoint.new 'http://my.api.org' do |entry_point|
90
+ entry_point.connection(proxy: 'http://my.proxy:8080')
91
+ end
92
+ end
93
+
94
+ describe 'connection' do
95
+ it 'creates a Faraday connection with the entry point url' do
96
+ expect(entry_point.connection.url_prefix.to_s).to be == 'http://my.api.org/'
97
+ end
98
+
99
+ it 'creates a Faraday connection with the default headers' do
100
+ expect(entry_point.headers['Content-Type']).to be == 'application/hal+json'
101
+ expect(entry_point.headers['Accept']).to be == 'application/hal+json,application/json'
102
+ end
103
+
104
+ it 'creates a Faraday connection with options' do
105
+ expect(entry_point.connection.proxy).to be_kind_of Faraday::ProxyOptions
106
+ expect(entry_point.connection.proxy.uri.to_s).to be == 'http://my.proxy:8080'
107
+ end
108
+ end
109
+ end
110
+
111
+ describe 'custom' do
112
+ let(:entry_point) do
113
+ EntryPoint.new 'http://my.api.org' do |entry_point|
114
+ entry_point.connection(default: false) do |conn|
115
+ conn.request :json
116
+ conn.response :json, content_type: /\bjson$/
117
+ conn.adapter :net_http
118
+ end
119
+
120
+ entry_point.headers = {
121
+ 'Content-Type' => 'application/foobar',
122
+ 'Accept' => 'application/foobar'
123
+ }
124
+ end
125
+ end
126
+
127
+ describe 'connection' do
128
+ it 'creates a Faraday connection with the entry point url' do
129
+ expect(entry_point.connection.url_prefix.to_s).to be == 'http://my.api.org/'
130
+ end
131
+
132
+ it 'creates a Faraday connection with non-default headers' do
133
+ expect(entry_point.headers['Content-Type']).to be == 'application/foobar'
134
+ expect(entry_point.headers['Accept']).to be == 'application/foobar'
135
+ end
136
+
137
+ it 'creates a Faraday connection with the default block' do
138
+ handlers = entry_point.connection.builder.handlers
139
+ # TODO - we are below not included?
140
+ # expect(handlers).to include Faraday::Response::RaiseError
141
+ # expect(handlers).to include FaradayMiddleware::FollowRedirects
142
+ expect(handlers).to include FaradayMiddleware::EncodeJson
143
+ expect(handlers).to include FaradayMiddleware::ParseJson
144
+ expect(handlers).to include Faraday::Adapter::NetHttp
145
+ end
146
+ end
147
+ end
148
+
149
+ describe 'inherited' do
150
+ let(:entry_point) do
151
+ EntryPoint.new 'http://my.api.org' do |entry_point|
152
+ entry_point.connection do |conn|
153
+ conn.use Faraday::Request::OAuth
154
+ end
155
+ entry_point.headers['Access-Token'] = 'token'
156
+ end
157
+ end
158
+
159
+ describe 'connection' do
160
+ it 'creates a Faraday connection with the default and additional headers' do
161
+ expect(entry_point.headers['Content-Type']).to be == 'application/hal+json'
162
+ expect(entry_point.headers['Accept']).to be == 'application/hal+json,application/json'
163
+ expect(entry_point.headers['Access-Token']).to be == 'token'
164
+ end
165
+
166
+ it 'creates a Faraday connection with the entry point url' do
167
+ expect(entry_point.connection.url_prefix.to_s).to be == 'http://my.api.org/'
168
+ end
169
+
170
+ it 'creates a Faraday connection with the default block plus any additional handlers' do
171
+ handlers = entry_point.connection.builder.handlers
172
+
173
+ expect(handlers).to include Faraday::Request::OAuth
174
+ expect(handlers).to include Faraday::Response::RaiseError
175
+ expect(handlers).to include FaradayMiddleware::FollowRedirects
176
+ expect(handlers).to include FaradayMiddleware::EncodeHalJson
177
+ expect(handlers).to include FaradayMiddleware::ParseHalJson
178
+ expect(handlers).to include Faraday::Adapter::NetHttp
179
+
180
+ expect(entry_point.connection.options.params_encoder).to be == Faraday::FlatParamsEncoder
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ module ApiNavigator
4
+ describe LinkCollection do
5
+ let(:entry_point) { double('EntryPoint', config: { base_uri: '/' }) }
6
+
7
+ let(:representation) do
8
+ JSON.parse(Fixtures::Requests.root_response)
9
+ end
10
+
11
+ let(:links) do
12
+ LinkCollection.new(representation['_links'], representation['_links']['curies'], entry_point)
13
+ end
14
+
15
+ it 'is a CollectionHash' do
16
+ expect(LinkCollection.ancestors).to include CollectionHash
17
+ end
18
+
19
+ it 'initializes the collection with links' do
20
+ expect(links).to respond_to(:books)
21
+ end
22
+
23
+ it 'returns link objects for each link' do
24
+ expect(links.books).to be_kind_of Link
25
+ expect(links['self']).to be_kind_of Link
26
+
27
+ expect(links.two_links).to be_kind_of Array
28
+ expect(links['two_links']).to be_kind_of Array
29
+ end
30
+
31
+ describe 'plain link' do
32
+ let(:plain_link) { links.books }
33
+ it 'must be correct' do
34
+ expect(plain_link._url).to be == '/books'
35
+ end
36
+ end
37
+
38
+ describe 'templated link' do
39
+ let(:templated_link) { links.filter }
40
+ it 'must expand' do
41
+ expect(templated_link._expand(filter: 'gizmos')._url).to be == '/productions/1?categories=gizmos'
42
+ end
43
+ end
44
+
45
+ describe 'curied link collection' do
46
+ let(:curied_link) { links['api:authors'] }
47
+ let(:curie) { links._curies['api'] }
48
+ it 'must expand' do
49
+ expect(curied_link._expand(rel: 'assoc')._url).to be == '/api/authors'
50
+ end
51
+ it 'exposes curie' do
52
+ expect(curie.expand('authors')).to be == '/docs/resources/authors'
53
+ end
54
+ end
55
+
56
+ describe 'array of links' do
57
+ let(:two_links) { links.two_links }
58
+
59
+ it 'should have 2 items' do
60
+ expect(two_links.length).to be == 2
61
+ end
62
+
63
+ it 'must be an array of Links' do
64
+ two_links.each do |link|
65
+ expect(link).to be_kind_of Link
66
+ end
67
+ end
68
+ end
69
+
70
+ describe 'null link value' do
71
+ let(:null_link) { links.null_link }
72
+ it 'must be nil' do
73
+ expect(null_link).to be_nil
74
+ end
75
+ end
76
+ end
77
+ end