praxis 0.17.1 → 0.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +18 -0
  4. data/lib/api_browser/package.json +1 -1
  5. data/lib/praxis/action_definition.rb +119 -14
  6. data/lib/praxis/api_general_info.rb +21 -3
  7. data/lib/praxis/application.rb +1 -0
  8. data/lib/praxis/dispatcher.rb +18 -15
  9. data/lib/praxis/docs/generator.rb +208 -0
  10. data/lib/praxis/handlers/www_form.rb +2 -1
  11. data/lib/praxis/media_type.rb +1 -1
  12. data/lib/praxis/multipart/part.rb +58 -6
  13. data/lib/praxis/request_stages/action.rb +10 -8
  14. data/lib/praxis/request_stages/request_stage.rb +2 -2
  15. data/lib/praxis/resource_definition.rb +11 -3
  16. data/lib/praxis/response_definition.rb +49 -25
  17. data/lib/praxis/restful_doc_generator.rb +9 -1
  18. data/lib/praxis/route.rb +16 -0
  19. data/lib/praxis/router.rb +1 -1
  20. data/lib/praxis/simple_media_type.rb +2 -1
  21. data/lib/praxis/tasks/api_docs.rb +10 -1
  22. data/lib/praxis/tasks/console.rb +10 -3
  23. data/lib/praxis/types/media_type_common.rb +8 -1
  24. data/lib/praxis/types/multipart.rb +5 -0
  25. data/lib/praxis/types/multipart_array.rb +12 -4
  26. data/lib/praxis/version.rb +1 -1
  27. data/lib/praxis.rb +3 -0
  28. data/praxis.gemspec +3 -3
  29. data/spec/functional_spec.rb +3 -3
  30. data/spec/praxis/action_definition_spec.rb +40 -0
  31. data/spec/praxis/api_general_info_spec.rb +10 -3
  32. data/spec/praxis/media_type_collection_spec.rb +11 -4
  33. data/spec/praxis/media_type_spec.rb +3 -1
  34. data/spec/praxis/request_stages/action_spec.rb +13 -6
  35. data/spec/praxis/resource_definition_spec.rb +1 -1
  36. data/spec/praxis/response_definition_spec.rb +29 -3
  37. data/spec/praxis/router_spec.rb +1 -1
  38. data/spec/praxis/types/multipart_array_spec.rb +92 -0
  39. data/spec/praxis/types/multipart_spec.rb +5 -0
  40. data/spec/spec_app/design/api.rb +1 -0
  41. data/spec/spec_app/design/media_types/volume.rb +1 -3
  42. data/spec/spec_app/design/resources/instances.rb +1 -1
  43. data/spec/support/spec_media_types.rb +5 -4
  44. 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
- it 'should always call the right controller method' do
32
+
33
+ it 'always call the right controller method' do
33
34
  action_stage.execute
34
35
  end
35
- it 'should save the request reference inside the response' do
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 'set the response body with it (and save the request too)' do
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.name) }
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([:media_type]){ should == { identifier: 'foobar'} }
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([:media_type]){ should == { identifier: 'custom_media'} }
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
@@ -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 speficied API version = \"#{request_version}\". Available versions = \"1.0\", \"2.0\".")
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
@@ -104,4 +104,9 @@ describe Praxis::Multipart do
104
104
 
105
105
  pending 'complete the load tests'
106
106
  end
107
+
108
+ context '#describe' do
109
+ subject(:described) { type.describe }
110
+ its([:family]){ should eq('multipart')}
111
+ end
107
112
  end
@@ -24,6 +24,7 @@ Praxis::ApiDefinition.define do
24
24
  description "This example API should really be replaced by a set of more full-fledged example apps in the future"
25
25
 
26
26
  base_path "/api"
27
+ produces 'json','xml'
27
28
  #version_with :path
28
29
  #base_path "/v:api_version"
29
30
  end
@@ -22,7 +22,7 @@ class Volume < Praxis::MediaType
22
22
  attribute :name
23
23
  attribute :source
24
24
  attribute :snapshots
25
-
25
+
26
26
  attribute :links
27
27
  end
28
28
 
@@ -37,5 +37,3 @@ class Volume < Praxis::MediaType
37
37
  end
38
38
 
39
39
  end
40
-
41
-
@@ -57,7 +57,7 @@ module ApiResources
57
57
  attribute :fail_filter, Attributor::Boolean, default: false
58
58
  end
59
59
 
60
- payload do
60
+ payload required: false do
61
61
  attribute :something, String
62
62
  attribute :optional, String, default: "not given"
63
63
  end
@@ -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.17.1
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-08-03 00:00:00.000000000 Z
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.0.0
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.0.0
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.0.0
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.0.0
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.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.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.2.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.