praxis 0.17.1 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +18 -0
- data/lib/api_browser/package.json +1 -1
- data/lib/praxis/action_definition.rb +119 -14
- data/lib/praxis/api_general_info.rb +21 -3
- data/lib/praxis/application.rb +1 -0
- data/lib/praxis/dispatcher.rb +18 -15
- data/lib/praxis/docs/generator.rb +208 -0
- data/lib/praxis/handlers/www_form.rb +2 -1
- data/lib/praxis/media_type.rb +1 -1
- data/lib/praxis/multipart/part.rb +58 -6
- data/lib/praxis/request_stages/action.rb +10 -8
- data/lib/praxis/request_stages/request_stage.rb +2 -2
- data/lib/praxis/resource_definition.rb +11 -3
- data/lib/praxis/response_definition.rb +49 -25
- data/lib/praxis/restful_doc_generator.rb +9 -1
- data/lib/praxis/route.rb +16 -0
- data/lib/praxis/router.rb +1 -1
- data/lib/praxis/simple_media_type.rb +2 -1
- data/lib/praxis/tasks/api_docs.rb +10 -1
- data/lib/praxis/tasks/console.rb +10 -3
- data/lib/praxis/types/media_type_common.rb +8 -1
- data/lib/praxis/types/multipart.rb +5 -0
- data/lib/praxis/types/multipart_array.rb +12 -4
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +3 -0
- data/praxis.gemspec +3 -3
- data/spec/functional_spec.rb +3 -3
- data/spec/praxis/action_definition_spec.rb +40 -0
- data/spec/praxis/api_general_info_spec.rb +10 -3
- data/spec/praxis/media_type_collection_spec.rb +11 -4
- data/spec/praxis/media_type_spec.rb +3 -1
- data/spec/praxis/request_stages/action_spec.rb +13 -6
- data/spec/praxis/resource_definition_spec.rb +1 -1
- data/spec/praxis/response_definition_spec.rb +29 -3
- data/spec/praxis/router_spec.rb +1 -1
- data/spec/praxis/types/multipart_array_spec.rb +92 -0
- data/spec/praxis/types/multipart_spec.rb +5 -0
- data/spec/spec_app/design/api.rb +1 -0
- data/spec/spec_app/design/media_types/volume.rb +1 -3
- data/spec/spec_app/design/resources/instances.rb +1 -1
- data/spec/support/spec_media_types.rb +5 -4
- metadata +10 -9
@@ -6,6 +6,8 @@ describe Praxis::MediaTypeCollection do
|
|
6
6
|
silence_warnings do
|
7
7
|
klass = Class.new(Praxis::MediaTypeCollection) do
|
8
8
|
member_type VolumeSnapshot
|
9
|
+
description 'A container for a collection of Volumes'
|
10
|
+
display_name 'Volumes Collection'
|
9
11
|
|
10
12
|
attributes do
|
11
13
|
attribute :name, String, regexp: /snapshots-(\w+)/
|
@@ -26,13 +28,13 @@ describe Praxis::MediaTypeCollection do
|
|
26
28
|
klass
|
27
29
|
end
|
28
30
|
end
|
29
|
-
|
31
|
+
|
30
32
|
context '.member_type' do
|
31
33
|
its(:member_type){ should be(VolumeSnapshot) }
|
32
34
|
its(:member_attribute){ should be_kind_of(Attributor::Attribute) }
|
33
35
|
its('member_attribute.type'){ should be(VolumeSnapshot) }
|
34
36
|
end
|
35
|
-
|
37
|
+
|
36
38
|
context '.load' do
|
37
39
|
context 'with a hash' do
|
38
40
|
let(:snapshots_data) { {name: 'snapshots', href: '/bob/snapshots' } }
|
@@ -52,7 +54,7 @@ describe Praxis::MediaTypeCollection do
|
|
52
54
|
[{id: 1, name: 'snapshot-1'},
|
53
55
|
{id: 2, name: 'snapshot-2'}]
|
54
56
|
end
|
55
|
-
|
57
|
+
|
56
58
|
let(:snapshots) { media_type_collection.load(snapshots_data) }
|
57
59
|
subject(:members) { snapshots.to_a }
|
58
60
|
|
@@ -102,7 +104,7 @@ describe Praxis::MediaTypeCollection do
|
|
102
104
|
end
|
103
105
|
|
104
106
|
context '#validate' do
|
105
|
-
|
107
|
+
|
106
108
|
|
107
109
|
context 'with a hash' do
|
108
110
|
let(:snapshots_data) { {name: 'snapshots-1', href: '/bob/snapshots' } }
|
@@ -148,4 +150,9 @@ describe Praxis::MediaTypeCollection do
|
|
148
150
|
end
|
149
151
|
end
|
150
152
|
|
153
|
+
context '#describe' do
|
154
|
+
subject(:described) { media_type_collection.describe }
|
155
|
+
its([:description]){ should be(media_type_collection.description)}
|
156
|
+
its([:display_name]){ should be(media_type_collection.display_name)}
|
157
|
+
end
|
151
158
|
end
|
@@ -144,9 +144,10 @@ describe Praxis::MediaType do
|
|
144
144
|
|
145
145
|
subject(:described){ Address.describe }
|
146
146
|
|
147
|
-
its(:keys) { should match_array( [:attributes, :description, :family, :id, :identifier, :key, :name, :views] ) }
|
147
|
+
its(:keys) { should match_array( [:attributes, :description, :display_name, :family, :id, :identifier, :key, :name, :views] ) }
|
148
148
|
its([:attributes]) { should be_kind_of(::Hash) }
|
149
149
|
its([:description]) { should be_kind_of(::String) }
|
150
|
+
its([:display_name]) { should be_kind_of(::String) }
|
150
151
|
its([:family]) { should be_kind_of(::String) }
|
151
152
|
its([:id]) { should be_kind_of(::String) }
|
152
153
|
its([:name]) { should be_kind_of(::String) }
|
@@ -155,6 +156,7 @@ describe Praxis::MediaType do
|
|
155
156
|
its([:views]) { should be_kind_of(::Hash) }
|
156
157
|
|
157
158
|
its([:description]) { should eq(Address.description) }
|
159
|
+
its([:display_name]) { should eq(Address.display_name) }
|
158
160
|
its([:family]) { should eq(Address.family) }
|
159
161
|
its([:id]) { should eq(Address.id) }
|
160
162
|
its([:name]) { should eq(Address.name) }
|
@@ -7,11 +7,11 @@ describe Praxis::RequestStages::Action do
|
|
7
7
|
include Praxis::Controller
|
8
8
|
end.new(request)
|
9
9
|
end
|
10
|
-
|
11
|
-
let(:action) { double("action", name: "foo") }
|
10
|
+
|
11
|
+
let(:action) { double("action", name: "foo") }
|
12
12
|
let(:response){ Praxis::Responses::Ok.new }
|
13
13
|
let(:app){ double("App", controller: controller, action:action, request: request)}
|
14
|
-
let(:action_stage){ Praxis::RequestStages::Action.new(action.name,app) }
|
14
|
+
let(:action_stage){ Praxis::RequestStages::Action.new(action.name,app) }
|
15
15
|
|
16
16
|
let(:request) do
|
17
17
|
env = Rack::MockRequest.env_for('/instances/1?cloud_id=1&api_version=1.0')
|
@@ -29,17 +29,24 @@ describe Praxis::RequestStages::Action do
|
|
29
29
|
expect(controller).to receive(action_stage.name).with(request.params_hash).and_return(controller_response)
|
30
30
|
end
|
31
31
|
let(:controller_response){ controller.response }
|
32
|
-
|
32
|
+
|
33
|
+
it 'always call the right controller method' do
|
33
34
|
action_stage.execute
|
34
35
|
end
|
35
|
-
|
36
|
+
|
37
|
+
it 'saves the request reference inside the response' do
|
36
38
|
action_stage.execute
|
37
39
|
expect(controller.response.request).to eq(request)
|
38
40
|
end
|
39
41
|
|
42
|
+
it 'sends the right ActiveSupport::Notification' do
|
43
|
+
expect(ActiveSupport::Notifications).to receive(:instrument).with('praxis.request_stage.execute', {controller: an_instance_of(controller.class)}).and_call_original
|
44
|
+
action_stage.execute
|
45
|
+
end
|
46
|
+
|
40
47
|
context 'if the controller method returns a string' do
|
41
48
|
let(:controller_response){ "this is the body"}
|
42
|
-
it '
|
49
|
+
it 'sets the response body with it (and save the request too)' do
|
43
50
|
action_stage.execute
|
44
51
|
expect(controller.response.body).to eq("this is the body")
|
45
52
|
end
|
@@ -17,7 +17,7 @@ describe Praxis::ResourceDefinition do
|
|
17
17
|
subject(:describe) { resource_definition.describe }
|
18
18
|
|
19
19
|
its([:description]) { should eq(resource_definition.description) }
|
20
|
-
its([:media_type]) { should eq(resource_definition.media_type.
|
20
|
+
its([:media_type]) { should eq(resource_definition.media_type.describe(true)) }
|
21
21
|
|
22
22
|
its([:actions]) { should have(2).items }
|
23
23
|
its([:metadata]) { should be_kind_of(Hash) }
|
@@ -509,6 +509,32 @@ describe Praxis::ResponseDefinition do
|
|
509
509
|
response.headers(headers) if headers
|
510
510
|
end
|
511
511
|
|
512
|
+
context 'for a definition with a media type' do
|
513
|
+
let(:media_type) { Instance }
|
514
|
+
subject(:payload) { output[:payload] }
|
515
|
+
|
516
|
+
before do
|
517
|
+
response.media_type Instance
|
518
|
+
end
|
519
|
+
|
520
|
+
its([:name]) { should eq 'Instance' }
|
521
|
+
context 'examples' do
|
522
|
+
subject(:examples) { payload[:examples] }
|
523
|
+
its(['json', :content_type]) { 'application/vnd.acme.instance+json '}
|
524
|
+
its(['xml', :content_type]) { 'application/vnd.acme.instance+xml' }
|
525
|
+
|
526
|
+
it 'properly encodes the example bodies' do
|
527
|
+
json = Praxis::Application.instance.handlers['json'].parse(examples['json'][:body])
|
528
|
+
xml = Praxis::Application.instance.handlers['xml'].parse(examples['xml'][:body])
|
529
|
+
expect(json).to eq xml
|
530
|
+
end
|
531
|
+
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
|
512
538
|
context 'for a definition without parts' do
|
513
539
|
it{ should be_kind_of(::Hash) }
|
514
540
|
its([:description]){ should be(description) }
|
@@ -530,7 +556,7 @@ describe Praxis::ResponseDefinition do
|
|
530
556
|
end
|
531
557
|
|
532
558
|
it{ should be_kind_of(::Hash) }
|
533
|
-
its([:
|
559
|
+
its([:payload]){ should == { identifier: 'foobar'} }
|
534
560
|
its([:status]){ should == 200 }
|
535
561
|
end
|
536
562
|
context 'using a full response definition block' do
|
@@ -546,8 +572,8 @@ describe Praxis::ResponseDefinition do
|
|
546
572
|
end
|
547
573
|
|
548
574
|
it{ should be_kind_of(::Hash) }
|
549
|
-
its([:
|
550
|
-
its([:status]){ should == 234 }
|
575
|
+
its([:payload]) { should == { identifier: 'custom_media'} }
|
576
|
+
its([:status]) { should == 234 }
|
551
577
|
end
|
552
578
|
end
|
553
579
|
end
|
data/spec/praxis/router_spec.rb
CHANGED
@@ -230,7 +230,7 @@ describe Praxis::Router do
|
|
230
230
|
code, headers, body = router.call(request)
|
231
231
|
expect(code).to eq(404)
|
232
232
|
expect(headers['Content-Type']).to eq('text/plain')
|
233
|
-
expect(body.first).to eq("NotFound. Your request
|
233
|
+
expect(body.first).to eq("NotFound. Your request specified API version = \"#{request_version}\". Available versions = \"1.0\", \"2.0\".")
|
234
234
|
end
|
235
235
|
end
|
236
236
|
end
|
@@ -102,6 +102,34 @@ describe Praxis::Types::MultipartArray do
|
|
102
102
|
|
103
103
|
it do
|
104
104
|
expect(payload.part('blah').payload['sub_hash']).to eq('key' => 'value')
|
105
|
+
|
106
|
+
# The "reader" functions should work
|
107
|
+
expect(payload.payload_type).to eq Attributor::Hash
|
108
|
+
expect(payload.class.payload_type).to eq Attributor::Hash
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with simple payload_type block defined' do
|
113
|
+
let(:type) do
|
114
|
+
Class.new(Praxis::Types::MultipartArray) do
|
115
|
+
name_type String
|
116
|
+
payload_type do
|
117
|
+
attribute :attr, String
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
let(:json_payload) { {attr: 'value'}.to_json }
|
123
|
+
let(:body) { StringIO.new("--boundary\r\nContent-Disposition: form-data; name=blah\r\n\r\n#{json_payload}\r\n--boundary--") }
|
124
|
+
let(:content_type) { "multipart/form-data; boundary=boundary" }
|
125
|
+
|
126
|
+
it do
|
127
|
+
expect(payload.part('blah').payload.class.ancestors).to include(Attributor::Struct)
|
128
|
+
expect(payload.part('blah').payload.attr).to eq('value')
|
129
|
+
|
130
|
+
# The "reader" functions should work
|
131
|
+
expect(payload.payload_type).to eq Attributor::Struct
|
132
|
+
expect(payload.class.payload_type).to eq Attributor::Struct
|
105
133
|
end
|
106
134
|
end
|
107
135
|
|
@@ -208,6 +236,70 @@ describe Praxis::Types::MultipartArray do
|
|
208
236
|
loaded = type.load(dumped, content_type: payload.content_type)
|
209
237
|
end
|
210
238
|
end
|
239
|
+
|
240
|
+
|
241
|
+
context 'with default_format' do
|
242
|
+
let(:type) do
|
243
|
+
Class.new(Praxis::Types::MultipartArray) do
|
244
|
+
part 'title', String, required: true
|
245
|
+
part 'thing' do
|
246
|
+
payload do
|
247
|
+
attribute :name, String
|
248
|
+
attribute :value, String
|
249
|
+
end
|
250
|
+
end
|
251
|
+
part 'stuff' do
|
252
|
+
header 'Content-Type', 'application/json'
|
253
|
+
payload Hash do
|
254
|
+
key :foo, String
|
255
|
+
key :bar, DateTime
|
256
|
+
end
|
257
|
+
end
|
258
|
+
part 'instances', multiple: true do
|
259
|
+
header 'Content-Type', 'application/vnd.acme.instance'
|
260
|
+
payload Instance
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
let(:example) { type.example }
|
266
|
+
|
267
|
+
let(:default_format) { 'xml' }
|
268
|
+
|
269
|
+
let(:output) { example.dump(default_format: default_format) }
|
270
|
+
|
271
|
+
let(:parts) { Praxis::MultipartParser.parse({'Content-Type'=>example.content_type}, output).last }
|
272
|
+
|
273
|
+
it 'dumps the parts with the proper handler' do
|
274
|
+
json_handler = Praxis::Application.instance.handlers['json']
|
275
|
+
xml_handler = Praxis::Application.instance.handlers['xml']
|
276
|
+
|
277
|
+
# title is simple string, so should keep text/plain
|
278
|
+
title = parts.find { |part| part.name == 'title' }
|
279
|
+
expect(title.content_type.to_s).to eq 'text/plain'
|
280
|
+
expect(title.payload).to eq example.part('title').payload
|
281
|
+
|
282
|
+
# things are structs with no content-type header defined
|
283
|
+
thing = parts.find { |part| part.name == 'thing' }
|
284
|
+
expect(thing.content_type.to_s).to eq 'application/xml'
|
285
|
+
expect(thing.payload).to eq xml_handler.generate(example.part('thing').payload.dump)
|
286
|
+
|
287
|
+
# stuff has hardcoded 'application/json' content-type, and should remain such
|
288
|
+
stuff = parts.find { |part| part.name == 'stuff' }
|
289
|
+
expect(stuff.content_type.to_s).to eq 'application/json'
|
290
|
+
expect(stuff.payload).to eq json_handler.generate(example.part('stuff').payload.dump)
|
291
|
+
|
292
|
+
# instances just specify 'application/vnd.acme.instance', and should
|
293
|
+
# have xml suffix appended
|
294
|
+
instances = parts.select { |part| part.name == 'instances' }
|
295
|
+
instances.each_with_index do |instance, i|
|
296
|
+
expect(instance.content_type.to_s).to eq 'application/vnd.acme.instance+xml'
|
297
|
+
expect(instance.payload).to eq xml_handler.generate(example.part('instances')[i].payload.dump)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
end
|
302
|
+
|
211
303
|
end
|
212
304
|
|
213
305
|
context 'with errors' do
|
data/spec/spec_app/design/api.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
class Person < Praxis::MediaType
|
2
|
-
attributes do
|
2
|
+
attributes do
|
3
3
|
attribute :id, Integer
|
4
4
|
attribute :name, String, example: /[:name:]/
|
5
5
|
attribute :href, String, example: proc { |person| "/people/#{person.id}" }
|
6
|
-
attribute :links, silence_warnings { Praxis::Collection.of(String) }
|
6
|
+
attribute :links, silence_warnings { Praxis::Collection.of(String) }
|
7
7
|
end
|
8
8
|
|
9
9
|
view :default do
|
@@ -37,6 +37,7 @@ class Address < Praxis::MediaType
|
|
37
37
|
identifier 'application/json'
|
38
38
|
|
39
39
|
description 'Address MediaType'
|
40
|
+
display_name 'The Address'
|
40
41
|
|
41
42
|
attributes do
|
42
43
|
attribute :id, Integer
|
@@ -44,8 +45,8 @@ class Address < Praxis::MediaType
|
|
44
45
|
|
45
46
|
attribute :owner, Person
|
46
47
|
attribute :custodian, Person
|
47
|
-
|
48
|
-
attribute :residents, Praxis::Collection.of(Person)
|
48
|
+
|
49
|
+
attribute :residents, Praxis::Collection.of(Person)
|
49
50
|
attribute :residents_summary, Person::CollectionSummary
|
50
51
|
|
51
52
|
links do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.18.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2015-
|
12
|
+
date: 2015-09-18 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
@@ -73,42 +73,42 @@ dependencies:
|
|
73
73
|
requirements:
|
74
74
|
- - ">="
|
75
75
|
- !ruby/object:Gem::Version
|
76
|
-
version: 4.
|
76
|
+
version: '4.1'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
81
|
- - ">="
|
82
82
|
- !ruby/object:Gem::Version
|
83
|
-
version: 4.
|
83
|
+
version: '4.1'
|
84
84
|
- !ruby/object:Gem::Dependency
|
85
85
|
name: praxis-blueprints
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - ">="
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: 2.
|
90
|
+
version: '2.2'
|
91
91
|
type: :runtime
|
92
92
|
prerelease: false
|
93
93
|
version_requirements: !ruby/object:Gem::Requirement
|
94
94
|
requirements:
|
95
95
|
- - ">="
|
96
96
|
- !ruby/object:Gem::Version
|
97
|
-
version: 2.
|
97
|
+
version: '2.2'
|
98
98
|
- !ruby/object:Gem::Dependency
|
99
99
|
name: attributor
|
100
100
|
requirement: !ruby/object:Gem::Requirement
|
101
101
|
requirements:
|
102
102
|
- - ">="
|
103
103
|
- !ruby/object:Gem::Version
|
104
|
-
version: 4.0.
|
104
|
+
version: 4.0.1
|
105
105
|
type: :runtime
|
106
106
|
prerelease: false
|
107
107
|
version_requirements: !ruby/object:Gem::Requirement
|
108
108
|
requirements:
|
109
109
|
- - ">="
|
110
110
|
- !ruby/object:Gem::Version
|
111
|
-
version: 4.0.
|
111
|
+
version: 4.0.1
|
112
112
|
- !ruby/object:Gem::Dependency
|
113
113
|
name: thor
|
114
114
|
requirement: !ruby/object:Gem::Requirement
|
@@ -728,6 +728,7 @@ files:
|
|
728
728
|
- lib/praxis/config.rb
|
729
729
|
- lib/praxis/controller.rb
|
730
730
|
- lib/praxis/dispatcher.rb
|
731
|
+
- lib/praxis/docs/generator.rb
|
731
732
|
- lib/praxis/error_handler.rb
|
732
733
|
- lib/praxis/exception.rb
|
733
734
|
- lib/praxis/exceptions/config.rb
|
@@ -920,7 +921,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
920
921
|
version: '0'
|
921
922
|
requirements: []
|
922
923
|
rubyforge_project:
|
923
|
-
rubygems_version: 2.
|
924
|
+
rubygems_version: 2.4.5.1
|
924
925
|
signing_key:
|
925
926
|
specification_version: 4
|
926
927
|
summary: Building APIs the way you want it.
|