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