praxis 0.17.1 → 0.18.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.
- 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.
|