api_navigator 0.0.1

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.
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