eipiai 0.6.0 → 0.7.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -4
  3. data/.rubocop.yml +3 -0
  4. data/.ruby-version +1 -0
  5. data/.wercker.yml +8 -1
  6. data/CHANGELOG.md +7 -0
  7. data/Gemfile +1 -0
  8. data/Rakefile +1 -0
  9. data/eipiai.gemspec +1 -0
  10. data/features/resources/api.feature +82 -0
  11. data/features/resources/collection.feature +60 -0
  12. data/features/resources/collection/get.feature +189 -0
  13. data/features/resources/collection/post.feature +230 -0
  14. data/features/resources/health.feature +27 -0
  15. data/features/resources/singular.feature +86 -0
  16. data/features/resources/singular/delete.feature +69 -0
  17. data/features/resources/singular/get.feature +146 -0
  18. data/features/resources/singular/post.feature +235 -0
  19. data/features/resources/singular/put.feature +164 -0
  20. data/features/step_definitions/steps.rb +10 -0
  21. data/features/step_definitions/webmachine_steps.rb +58 -0
  22. data/features/support/app.rb +14 -26
  23. data/features/support/env.rb +1 -0
  24. data/features/validation.feature +1 -0
  25. data/features/webmachine.feature +1 -56
  26. data/lib/eipiai.rb +1 -0
  27. data/lib/eipiai/configuration.rb +1 -0
  28. data/lib/eipiai/models.rb +1 -0
  29. data/lib/eipiai/models/collection.rb +1 -0
  30. data/lib/eipiai/models/representable.rb +6 -5
  31. data/lib/eipiai/roar.rb +1 -0
  32. data/lib/eipiai/roar/ext/hal.rb +3 -0
  33. data/lib/eipiai/roar/representers/api.rb +17 -6
  34. data/lib/eipiai/roar/representers/base.rb +1 -0
  35. data/lib/eipiai/validation.rb +1 -0
  36. data/lib/eipiai/validation/concerns/formatted_errors.rb +1 -0
  37. data/lib/eipiai/validation/validators/base.rb +2 -1
  38. data/lib/eipiai/validation/validators/sequel.rb +1 -0
  39. data/lib/eipiai/version.rb +2 -1
  40. data/lib/eipiai/webmachine.rb +2 -0
  41. data/lib/eipiai/webmachine/ext/decision.rb +1 -0
  42. data/lib/eipiai/webmachine/ext/request.rb +2 -1
  43. data/lib/eipiai/webmachine/resources/api.rb +7 -3
  44. data/lib/eipiai/webmachine/resources/base.rb +96 -19
  45. data/lib/eipiai/webmachine/resources/collection.rb +4 -18
  46. data/lib/eipiai/webmachine/resources/concerns/objectifiable.rb +1 -0
  47. data/lib/eipiai/webmachine/resources/concerns/representable.rb +69 -0
  48. data/lib/eipiai/webmachine/resources/health.rb +16 -10
  49. data/lib/eipiai/webmachine/resources/singular.rb +45 -10
  50. metadata +16 -4
  51. data/features/support/db.rb +0 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 34136d9503bd1cfed3341367129e8c7ff9ccfb9d
4
- data.tar.gz: 1ec6acd5d189963b9cd0efe1d8649d4c282edca2
3
+ metadata.gz: 6ea1d2843bc6b4a1b1ed2e268f3a287cdb6c591d
4
+ data.tar.gz: 72f7c6c496142c5dc5caed1faf5a5c83b1bb810f
5
5
  SHA512:
6
- metadata.gz: 3adf462831a8f4c9287acbea3bb93d66b31dc8c3e862052d152a7ba091a8611989da4130d35d5f0f2623d115ebefa05d64df378545933e98e552a93b8cb6edfb
7
- data.tar.gz: 3a8ab95d17d35671725ea6edbebbd94f599126ce5f073e3350fc2733bbf3fc53935ea9febd22361d5a53b94d1769c250dd643b6c0acaac68a29f88bda379f72e
6
+ metadata.gz: 88c91bbeb2dc81e6337069fd9053c8c5c262a0a43fd7b70a865c544dedb67061c58648ab2ec14cb93982e6bb1d1fa23df259ce2f5fe97c033fc19fbc04560747
7
+ data.tar.gz: 5a109aac02025614a11feb80a853f12ad980f6c0537e8fef5b77ea3acae12555b09df0d2fc455ab86d70d49298bd147151f4d3bdda479860ab1aa0b0f00c3c8b
data/.gitignore CHANGED
@@ -1,9 +1,6 @@
1
- _builds
2
- _cache
3
- _projects
4
- _steps
5
1
  .bundle
6
2
  .DS_Store
3
+ .wercker
7
4
  .yardoc
8
5
  *.gem
9
6
  doc/
data/.rubocop.yml CHANGED
@@ -1,3 +1,6 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.3
3
+
1
4
  Metrics/LineLength:
2
5
  Max: 100
3
6
 
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.3.1
data/.wercker.yml CHANGED
@@ -1,9 +1,16 @@
1
- box: ruby
1
+ box: ruby:2.3
2
2
 
3
3
  build:
4
4
  steps:
5
+ - script:
6
+ name: fix Bundler issue
7
+ code: |
8
+ gem update --system 2.6.1
9
+ gem install bundler --version $BUNDLER_VERSION
5
10
  - bundle-install:
11
+ path: vendor/bundle
6
12
  jobs: "8"
13
+ clean: true
7
14
  - script:
8
15
  name: bootstrap
9
16
  code: script/bootstrap
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## [v0.6.0](https://github.com/blendle/eipiai/tree/v0.6.0) (2015-12-18)
4
+ [Full Changelog](https://github.com/blendle/eipiai/compare/v0.5.1...v0.6.0)
5
+
6
+ **Merged pull requests:**
7
+
8
+ - introduce representable and collection models [\#10](https://github.com/blendle/eipiai/pull/10) ([JeanMertz](https://github.com/JeanMertz))
9
+
3
10
  ## [v0.5.1](https://github.com/blendle/eipiai/tree/v0.5.1) (2015-12-14)
4
11
  [Full Changelog](https://github.com/blendle/eipiai/compare/v0.5.0...v0.5.1)
5
12
 
data/Gemfile CHANGED
@@ -1,2 +1,3 @@
1
+ # frozen_string_literal: true
1
2
  source 'https://rubygems.org'
2
3
  gemspec
data/Rakefile CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'bundler/gem_tasks'
2
3
  require 'cucumber/rake/task'
3
4
  require 'rubocop/rake_task'
data/eipiai.gemspec CHANGED
@@ -1,4 +1,5 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
2
3
  lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'eipiai/version'
@@ -0,0 +1,82 @@
1
+ Feature: Request entrypoint of API
2
+
3
+ When a collection of resources exist
4
+ I want to use Webmachine to provide API access to those resource
5
+ So that the resources can easily be retrieved in one request
6
+
7
+ Scenario: List main API endpoint
8
+ Given the following resource at "/item/:item_uid":
9
+ """ruby
10
+ class ItemResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def top_level_relation?
14
+ true
15
+ end
16
+
17
+ def query_keys
18
+ %w(available)
19
+ end
20
+ end
21
+ """
22
+ And the following resource at "/items":
23
+ """ruby
24
+ class ItemsResource < Webmachine::Resource
25
+ include Eipiai::Resource
26
+
27
+ def top_level_relation?
28
+ true
29
+ end
30
+
31
+ def query_keys
32
+ %w(category weight purchased)
33
+ end
34
+ end
35
+ """
36
+ And the following resource at "/user/:user_uid":
37
+ """ruby
38
+ class UserResource < Webmachine::Resource
39
+ include Eipiai::Resource
40
+
41
+ def top_level_relation?
42
+ true
43
+ end
44
+ end
45
+ """
46
+ And the following resource at "/user/:user_uid/orders":
47
+ """ruby
48
+ class UserOrdersResource < Webmachine::Resource
49
+ include Eipiai::Resource
50
+ end
51
+ """
52
+
53
+ When the client does a GET request to "/api"
54
+ Then the status code should be "200" (OK)
55
+ And the response should be HAL/JSON:
56
+ """json
57
+ {
58
+ "_links": {
59
+ "self": {
60
+ "href": "https://example.org/api"
61
+ },
62
+ "api": {
63
+ "href": "https://example.org/api"
64
+ },
65
+ "health": {
66
+ "href": "https://example.org/health"
67
+ },
68
+ "item": {
69
+ "href": "https://example.org/item/{item_uid}{?available}",
70
+ "templated": true
71
+ },
72
+ "items": {
73
+ "href": "https://example.org/items{?category,weight,purchased}",
74
+ "templated": true
75
+ },
76
+ "user": {
77
+ "href": "https://example.org/user/{user_uid}",
78
+ "templated": true
79
+ }
80
+ }
81
+ }
82
+ """
@@ -0,0 +1,60 @@
1
+ Feature: Request a collection of resources
2
+
3
+ When a collection of resources exist
4
+ I want to use Webmachine to provide API access to those resource
5
+ So that the resources can easily be retrieved in one request
6
+
7
+ Scenario: GET
8
+ Given the following top-level resource at "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
15
+ end
16
+ end
17
+ """
18
+ When the client provides the header "Accept: application/json"
19
+ And the client does a GET request to the "items" resource
20
+ Then the status code should be "200" (OK)
21
+ And the response should be JSON:
22
+ """json
23
+ {
24
+ "count": 2,
25
+ "items": [
26
+ {
27
+ "hello": "world"
28
+ },
29
+ {
30
+ "hello": "universe"
31
+ }
32
+ ]
33
+ }
34
+ """
35
+
36
+ Scenario: POST
37
+ Given the following top-level resource at "/items":
38
+ """ruby
39
+ class ItemsResource < Webmachine::Resource
40
+ include Eipiai::Resource
41
+
42
+ def create_path
43
+ '/item/' + params['uid']
44
+ end
45
+
46
+ def new_object
47
+ OpenStruct.new(params)
48
+ end
49
+ end
50
+ """
51
+ When the client provides the header "Content-Type: application/json"
52
+ And the client does a POST request to the "items" resource with the following content:
53
+ """json
54
+ {
55
+ "uid": "hello",
56
+ "hello": "galaxy"
57
+ }
58
+ """
59
+ Then the status code should be "201" (Created)
60
+ And the response should contain the header "Location" with value "https://example.org/item/hello"
@@ -0,0 +1,189 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: Accept header "application/json" returns "200 OK" and a body of type "application/json"
8
+ Given the following top-level resource at "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def object
14
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
15
+ end
16
+ end
17
+ """
18
+
19
+ When the client provides the header "Accept: application/json"
20
+ And the client does a GET request to the "items" resource
21
+ Then the status code should be "200" (OK)
22
+ And the response should be JSON:
23
+ """json
24
+ {
25
+ "count": 2,
26
+ "items": [
27
+ {
28
+ "hello": "world"
29
+ },
30
+ {
31
+ "hello": "universe"
32
+ }
33
+ ]
34
+ }
35
+ """
36
+
37
+ Scenario: Accept header "application/hal+json" returns "200 OK" and a body of type "application/hal+json"
38
+ Given the following top-level resource at "/items":
39
+ """ruby
40
+ class ItemsResource < Webmachine::Resource
41
+ include Eipiai::Resource
42
+
43
+ def to_hal_json
44
+ to_h.merge(_links: { self: { href: request.uri.path } }).to_json
45
+ end
46
+
47
+ def object
48
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
49
+ end
50
+ end
51
+ """
52
+
53
+ When the client provides the header "Accept: application/hal+json"
54
+ And the client does a GET request to the "items" resource
55
+ Then the status code should be "200" (OK)
56
+ And the response should be HAL/JSON:
57
+ """json
58
+ {
59
+ "_links": {
60
+ "self": {
61
+ "href": "/items"
62
+ }
63
+ },
64
+ "count": 2,
65
+ "items": [
66
+ {
67
+ "hello": "world"
68
+ },
69
+ {
70
+ "hello": "universe"
71
+ }
72
+ ]
73
+ }
74
+ """
75
+
76
+ Scenario: Accept header "text/xml" returns "200 OK" and a body of type "text/html"
77
+ Given the following top-level resource at "/items":
78
+ """ruby
79
+ class ItemsResource < Webmachine::Resource
80
+ require 'active_support/core_ext/hash/conversions'
81
+
82
+ include Eipiai::Resource
83
+
84
+ def content_types_provided
85
+ [['text/xml', :to_xml]]
86
+ end
87
+
88
+ def to_xml
89
+ to_h.to_xml(root: 'items')
90
+ end
91
+
92
+ def object
93
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
94
+ end
95
+ end
96
+ """
97
+
98
+ When the client provides the header "Accept: text/xml"
99
+ And the client does a GET request to the "items" resource
100
+ Then the status code should be "200" (OK)
101
+ And the response should be XML:
102
+ """xml
103
+ <?xml version="1.0" encoding="UTF-8"?>
104
+ <items>
105
+ <count type="integer">2</count>
106
+ <items type="array">
107
+ <item>
108
+ <hello>world</hello>
109
+ </item>
110
+ <item>
111
+ <hello>universe</hello>
112
+ </item>
113
+ </items>
114
+ </items>
115
+ """
116
+
117
+ Scenario: Not providing the Accept header returns "200 OK" and uses the first in the list of acceptable response content types
118
+ Given the following top-level resource at "/items":
119
+ """ruby
120
+ class ItemsResource < Webmachine::Resource
121
+ include Eipiai::Resource
122
+
123
+ def to_hal_json
124
+ to_h.merge(_links: { self: { href: request.uri.path } }).to_json
125
+ end
126
+
127
+ def object
128
+ OpenStruct.new(count: 2, items: [{ hello: 'world' }, { hello: 'universe' }])
129
+ end
130
+ end
131
+ """
132
+
133
+ When the client does a GET request to the "items" resource
134
+ Then the status code should be "200" (OK)
135
+ And the response should be HAL/JSON:
136
+ """json
137
+ {
138
+ "_links": {
139
+ "self": {
140
+ "href": "/items"
141
+ }
142
+ },
143
+ "count": 2,
144
+ "items": [
145
+ {
146
+ "hello": "world"
147
+ },
148
+ {
149
+ "hello": "universe"
150
+ }
151
+ ]
152
+ }
153
+ """
154
+
155
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
156
+ Given the following top-level resource at "/items":
157
+ """ruby
158
+ class ItemsResource < Webmachine::Resource
159
+ include Eipiai::Resource
160
+ end
161
+ """
162
+
163
+ When the client provides the header "Accept: application/unknown"
164
+ And the client provides the header "Content-Type: application/json"
165
+ And the client does a GET request to the "items" resource
166
+ Then the status code should be "406" (Not Acceptable)
167
+
168
+ Scenario: An empty collection returns "200 OK" with an empty array
169
+ Given the following top-level resource at "/items":
170
+ """ruby
171
+ class ItemsResource < Webmachine::Resource
172
+ include Eipiai::Resource
173
+
174
+ def object
175
+ OpenStruct.new(count: 0, items: [])
176
+ end
177
+ end
178
+ """
179
+
180
+ When the client provides the header "Accept: application/json"
181
+ And the client does a GET request to the "items" resource
182
+ Then the status code should be "200" (OK)
183
+ And the response should be JSON:
184
+ """json
185
+ {
186
+ "count": 0,
187
+ "items": []
188
+ }
189
+ """
@@ -0,0 +1,230 @@
1
+ Feature: Working with singular resources (GET, POST, PUT, DELETE)
2
+
3
+ When a single resource exists
4
+ I want to use Webmachine to provide API access to that resource
5
+ So that the resource can easily be worked with
6
+
7
+ Scenario: Accept header "application/json" returns "201 Created" and a body of type "application/json"
8
+ Given the following top-level resource at "/items":
9
+ """ruby
10
+ class ItemsResource < Webmachine::Resource
11
+ include Eipiai::Resource
12
+
13
+ def create_path
14
+ '/item/' + params['uid']
15
+ end
16
+
17
+ def new_object
18
+ OpenStruct.new(params)
19
+ end
20
+ end
21
+ """
22
+
23
+ When the client provides the header "Accept: application/json"
24
+ And the client provides the header "Content-Type: application/json"
25
+ And the client does a POST request to the "items" resource with the following content:
26
+ """json
27
+ {
28
+ "uid": "hello",
29
+ "hello": "galaxy"
30
+ }
31
+ """
32
+ Then the status code should be "201" (Created)
33
+ And the response should be JSON:
34
+ """json
35
+ {
36
+ "uid": "hello",
37
+ "hello": "galaxy"
38
+ }
39
+ """
40
+
41
+ Scenario: Accept header "application/hal+json" returns "201 Created" and a body of type "application/hal+json"
42
+ Given the following top-level resource at "/items":
43
+ """ruby
44
+ class ItemsResource < Webmachine::Resource
45
+ include Eipiai::Resource
46
+
47
+ def to_hal_json
48
+ to_h.merge(_links: { self: { href: create_uri } }).to_json
49
+ end
50
+
51
+ def create_path
52
+ '/item/' + params['uid']
53
+ end
54
+
55
+ def new_object
56
+ OpenStruct.new(params)
57
+ end
58
+ end
59
+ """
60
+
61
+ When the client provides the header "Accept: application/hal+json"
62
+ And the client provides the header "Content-Type: application/json"
63
+ And the client does a POST request to the "items" resource with the following content:
64
+ """json
65
+ {
66
+ "uid": "goodbye",
67
+ "hello": "galaxy"
68
+ }
69
+ """
70
+ Then the status code should be "201" (Created)
71
+ And the response should be HAL/JSON:
72
+ """json
73
+ {
74
+ "_links": {
75
+ "self": {
76
+ "href": "https://example.org/item/goodbye"
77
+ }
78
+ },
79
+ "uid": "goodbye",
80
+ "hello": "galaxy"
81
+ }
82
+ """
83
+
84
+ Scenario: Accept header "text/xml" returns "201 Created" and a body of type "text/html"
85
+ Given the following top-level resource at "/items":
86
+ """ruby
87
+ class ItemsResource < Webmachine::Resource
88
+ require 'active_support/core_ext/hash/conversions'
89
+
90
+ include Eipiai::Resource
91
+
92
+ def content_types_provided
93
+ [['text/xml', :to_xml]]
94
+ end
95
+
96
+ def to_xml
97
+ to_h.to_xml(root: 'item')
98
+ end
99
+
100
+ def create_path
101
+ '/item/' + params['uid']
102
+ end
103
+
104
+ def new_object
105
+ OpenStruct.new(params)
106
+ end
107
+ end
108
+ """
109
+
110
+ When the client provides the header "Accept: text/xml"
111
+ And the client provides the header "Content-Type: application/json"
112
+ And the client does a POST request to the "items" resource with the following content:
113
+ """json
114
+ {
115
+ "uid": "hello",
116
+ "hello": "galaxy"
117
+ }
118
+ """
119
+ Then the status code should be "201" (Created)
120
+ And the response should be XML:
121
+ """xml
122
+ <?xml version="1.0" encoding="UTF-8"?>
123
+ <item>
124
+ <uid>hello</uid>
125
+ <hello>galaxy</hello>
126
+ </item>
127
+ """
128
+
129
+ Scenario: Not providing the Accept header returns "201 Created" and a "Location" header
130
+ Given the following top-level resource at "/items":
131
+ """ruby
132
+ class ItemsResource < Webmachine::Resource
133
+ include Eipiai::Resource
134
+
135
+ def create_path
136
+ '/item/' + params['uid']
137
+ end
138
+
139
+ def new_object
140
+ OpenStruct.new(params)
141
+ end
142
+ end
143
+ """
144
+
145
+ When the client provides the header "Content-Type: application/json"
146
+ And the client does a POST request to the "items" resource with the following content:
147
+ """json
148
+ {
149
+ "uid": "hi",
150
+ "hello": "galaxy"
151
+ }
152
+ """
153
+ Then the status code should be "201" (Created)
154
+ And the response should contain the header "Location" with value "https://example.org/item/hi"
155
+
156
+ Scenario: An unknown Accept header returns "406 Not Acceptable"
157
+ Given the following top-level resource at "/items":
158
+ """ruby
159
+ class ItemsResource < Webmachine::Resource
160
+ include Eipiai::Resource
161
+ end
162
+ """
163
+
164
+ When the client provides the header "Accept: application/unknown"
165
+ And the client provides the header "Content-Type: application/json"
166
+ And the client does a POST request to the "items" resource with the following content:
167
+ """json
168
+ {
169
+ "hello": "galaxy"
170
+ }
171
+ """
172
+ Then the status code should be "406" (Not Acceptable)
173
+
174
+ Scenario: Not providing the Content-Type header returns "415 Unsupported Media Type"
175
+ Given the following top-level resource at "/items":
176
+ """ruby
177
+ class ItemsResource < Webmachine::Resource
178
+ include Eipiai::Resource
179
+
180
+ def create_path
181
+ '/item' + params['uid']
182
+ end
183
+
184
+ def new_object
185
+ OpenStruct.new(params)
186
+ end
187
+ end
188
+ """
189
+
190
+ When the client provides the header "Accept: application/json"
191
+ And the client does a POST request to the "items" resource with the following content:
192
+ """json
193
+ {
194
+ "uid": "bye",
195
+ "hello": "galaxy"
196
+ }
197
+ """
198
+ Then the status code should be "415" (Unsupported Media Type)
199
+
200
+ Scenario: Content-Type as JSON but body is not JSON returns "400 Bad Request"
201
+ Given the following top-level resource at "/items":
202
+ """ruby
203
+ class ItemsResource < Webmachine::Resource
204
+ include Eipiai::Resource
205
+ end
206
+ """
207
+
208
+ When the client provides the header "Accept: application/json"
209
+ And the client provides the header "Content-Type: application/json"
210
+ And the client does a POST request to the "items" resource with the following content:
211
+ """json
212
+ {
213
+ "hello": INVALID
214
+ }
215
+ """
216
+ Then the status code should be "400" (Bad Request)
217
+ And the response should be JSON:
218
+ """json
219
+ {
220
+ "_errors": [
221
+ {
222
+ "id": "InvalidJson",
223
+ "message": "invalid json"
224
+ }
225
+ ]
226
+ }
227
+ """
228
+
229
+ @todo
230
+ Scenario: Providing valid JSON with invalid/unknown attributes returns "422 Unprocessable Entity"