praxis 0.20.1 → 0.21

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +2 -2
  4. data/CHANGELOG.md +36 -0
  5. data/lib/api_browser/Gruntfile.js +33 -3
  6. data/lib/api_browser/app/index.html +3 -0
  7. data/lib/api_browser/app/js/factories/Example.js +4 -0
  8. data/lib/api_browser/app/js/factories/template_for.js +14 -0
  9. data/lib/api_browser/app/js/filters/attribute_name.js +3 -2
  10. data/lib/api_browser/app/js/filters/header_info.js +9 -0
  11. data/lib/api_browser/app/views/action.html +11 -2
  12. data/lib/api_browser/app/views/controller.html +5 -5
  13. data/lib/api_browser/app/views/menu.html +1 -1
  14. data/lib/api_browser/app/views/type.html +4 -23
  15. data/lib/api_browser/app/views/type/details.html +7 -4
  16. data/lib/api_browser/app/views/types/main/array.html +22 -0
  17. data/lib/api_browser/app/views/types/main/default.html +23 -0
  18. data/lib/api_browser/app/views/types/main/hash.html +23 -0
  19. data/lib/praxis.rb +2 -0
  20. data/lib/praxis/action_definition.rb +0 -8
  21. data/lib/praxis/api_definition.rb +11 -6
  22. data/lib/praxis/api_general_info.rb +13 -0
  23. data/lib/praxis/bootloader.rb +11 -5
  24. data/lib/praxis/docs/generator.rb +31 -11
  25. data/lib/praxis/docs/link_builder.rb +30 -0
  26. data/lib/praxis/extensions/field_expansion.rb +2 -2
  27. data/lib/praxis/media_type.rb +1 -1
  28. data/lib/praxis/middleware_app.rb +30 -0
  29. data/lib/praxis/resource_definition.rb +24 -2
  30. data/lib/praxis/response.rb +2 -1
  31. data/lib/praxis/response_definition.rb +2 -2
  32. data/lib/praxis/responses/http.rb +28 -92
  33. data/lib/praxis/responses/validation_error.rb +4 -1
  34. data/lib/praxis/tasks/api_docs.rb +11 -24
  35. data/lib/praxis/trait.rb +12 -7
  36. data/lib/praxis/validation_handler.rb +2 -1
  37. data/lib/praxis/version.rb +1 -1
  38. data/praxis.gemspec +11 -7
  39. data/spec/api_browser/filters/attribute_name_spec.js +2 -2
  40. data/spec/praxis/action_definition_spec.rb +23 -1
  41. data/spec/praxis/bootloader_spec.rb +28 -0
  42. data/spec/praxis/extensions/field_expansion_spec.rb +10 -0
  43. data/spec/praxis/middleware_app_spec.rb +55 -0
  44. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +8 -3
  45. data/spec/praxis/resource_definition_spec.rb +51 -2
  46. data/spec/praxis/response_definition_spec.rb +16 -4
  47. data/spec/praxis/response_spec.rb +1 -1
  48. data/spec/praxis/trait_spec.rb +13 -0
  49. data/spec/spec_app/config/environment.rb +11 -1
  50. metadata +30 -25
  51. data/lib/praxis/restful_doc_generator.rb +0 -439
@@ -12,7 +12,7 @@ module Praxis
12
12
  @other = []
13
13
 
14
14
  @attribute_groups = Hash.new do |h,k|
15
- h[k] = Trait.new
15
+ h[k] = []
16
16
  end
17
17
 
18
18
  if block_given?
@@ -34,7 +34,7 @@ module Praxis
34
34
  end
35
35
 
36
36
  def create_group(name, &block)
37
- @attribute_groups[name] = block
37
+ @attribute_groups[name] << block
38
38
  end
39
39
 
40
40
  def headers(*args, &block)
@@ -68,7 +68,7 @@ module Praxis
68
68
  desc[:routing] = ConfigHash.new(&@routing).to_hash
69
69
  end
70
70
 
71
- @attribute_groups.each_with_object(desc) do |(name, block), hash|
71
+ @attribute_groups.each_with_object(desc) do |(name, blocks), hash|
72
72
  type_class = if name == :headers
73
73
  # Headers are special:
74
74
  # Keys are strings, they have a special DSL, and are case insensitive
@@ -76,9 +76,12 @@ module Praxis
76
76
  dsl_compiler: ActionDefinition::HeadersDSLCompiler,
77
77
  case_insensitive_load: true
78
78
  }
79
- Attributor::Hash.of(key: String).construct(block, hash_opts)
79
+ Attributor::Hash.of(key: String).construct(Proc.new {}, hash_opts)
80
80
  else
81
- Attributor::Hash.construct(block)
81
+ Attributor::Hash.construct(Proc.new {})
82
+ end
83
+ blocks.each do |block|
84
+ type_class.construct(block)
82
85
  end
83
86
  hash[name] = type_class.describe[:attributes]
84
87
  end
@@ -88,8 +91,10 @@ module Praxis
88
91
 
89
92
 
90
93
  def apply!(target)
91
- @attribute_groups.each do |name, block|
92
- target.send(name, &block)
94
+ @attribute_groups.each do |name, blocks|
95
+ blocks.each do |block|
96
+ target.send(name, &block)
97
+ end
93
98
  end
94
99
 
95
100
  if @routing
@@ -3,7 +3,8 @@ module Praxis
3
3
 
4
4
  # Should return the Response to send back
5
5
  def handle!(summary:, request:, stage:, errors: nil, exception: nil, **opts)
6
- Responses::ValidationError.new(summary: summary, errors: errors, exception: exception, **opts)
6
+ documentation = Docs::LinkBuilder.instance.for_request request
7
+ Responses::ValidationError.new(summary: summary, errors: errors, exception: exception, documentation: documentation, **opts)
7
8
  end
8
9
 
9
10
  end
@@ -1,3 +1,3 @@
1
1
  module Praxis
2
- VERSION = '0.20.1'
2
+ VERSION = '0.21'
3
3
  end
@@ -24,9 +24,9 @@ Gem::Specification.new do |spec|
24
24
  spec.add_dependency 'mustermann', '~> 0'
25
25
  spec.add_dependency 'activesupport', '>= 3'
26
26
  spec.add_dependency 'mime', '~> 0'
27
- spec.add_dependency 'praxis-mapper', '~> 4.2'
28
- spec.add_dependency 'praxis-blueprints', '~> 3.2'
29
- spec.add_dependency 'attributor', '~> 5.0.2'
27
+ spec.add_dependency 'praxis-mapper', '~> 4.3'
28
+ spec.add_dependency 'praxis-blueprints', '~> 3.3'
29
+ spec.add_dependency 'attributor', '~> 5.1'
30
30
  spec.add_dependency 'thor', '~> 0.18'
31
31
  spec.add_dependency 'terminal-table', '~> 1.4'
32
32
  spec.add_dependency 'harness', '~> 2'
@@ -34,9 +34,14 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency 'bundler', '~> 1.6'
35
35
  spec.add_development_dependency 'rake', '~> 0.9'
36
36
  spec.add_development_dependency 'rake-notes', '~> 0'
37
- spec.add_development_dependency 'pry', '~> 0'
38
- spec.add_development_dependency 'pry-byebug', '~> 1'
39
- spec.add_development_dependency 'pry-stack_explorer', '~> 0'
37
+ if RUBY_PLATFORM !~ /java/
38
+ spec.add_development_dependency 'pry', '~> 0'
39
+ spec.add_development_dependency 'pry-byebug', '~> 1'
40
+ spec.add_development_dependency 'pry-stack_explorer', '~> 0'
41
+ spec.add_development_dependency 'sqlite3', '~> 1'
42
+ else
43
+ spec.add_development_dependency 'jdbc-sqlite3'
44
+ end
40
45
  spec.add_development_dependency 'rspec', '~> 3'
41
46
  spec.add_development_dependency 'rspec-its', '~> 1'
42
47
  spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
@@ -47,6 +52,5 @@ Gem::Specification.new do |spec|
47
52
  spec.add_development_dependency 'simplecov', '~> 0'
48
53
  spec.add_development_dependency 'fuubar', '~> 2'
49
54
  spec.add_development_dependency 'yard', '~> 0'
50
- spec.add_development_dependency 'sqlite3', '~> 1'
51
55
  spec.add_development_dependency 'coveralls'
52
56
  end
@@ -8,10 +8,10 @@ describe('attributeName filter', function() {
8
8
  };
9
9
  }));
10
10
 
11
- it('only modifies input with one dot', function() {
11
+ it('only modifies input with one or more dots', function() {
12
12
  expect(function(str) {
13
13
  var dotsCount = str.split('.').length - 1;
14
- if (dotsCount !== 1) {
14
+ if (dotsCount === 0) {
15
15
  return str === filter(str);
16
16
  }
17
17
  }).forAll(qc.string);
@@ -2,10 +2,15 @@ require 'spec_helper'
2
2
 
3
3
  describe Praxis::ActionDefinition do
4
4
  class SpecMediaType < Praxis::MediaType
5
+ identifier 'application/json'
6
+
5
7
  attributes do
6
8
  attribute :one, String
7
9
  attribute :two, Integer
8
10
  end
11
+ view :default do
12
+ attribute :one
13
+ end
9
14
  end
10
15
 
11
16
  let(:resource_definition) do
@@ -28,11 +33,22 @@ describe Praxis::ActionDefinition do
28
33
  end
29
34
 
30
35
  subject(:action) do
36
+
37
+ Praxis::ApiDefinition.define do |api|
38
+ api.response_template :ok do |media_type: , location: nil, headers: nil, description: nil |
39
+ status 200
40
+
41
+ media_type media_type
42
+ location location
43
+ headers headers if headers
44
+ end
45
+ end
31
46
  Praxis::ActionDefinition.new(:foo, resource_definition) do
32
47
  routing { get '/:one' }
33
48
  payload { attribute :two, String }
34
49
  headers { header "X_REQUESTED_WITH", 'XMLHttpRequest' }
35
50
  params { attribute :one, String }
51
+ response :ok, headers: { "Foo" => "Bar"}, location: %r{/some/thing}
36
52
  end
37
53
  end
38
54
 
@@ -53,11 +69,13 @@ describe Praxis::ActionDefinition do
53
69
  before do
54
70
  action.response :ok
55
71
  action.response :internal_server_error
72
+ action.response :created, location: 'foobar'
56
73
  end
57
74
 
58
75
  it { should be_kind_of Hash }
59
76
  it { should include :ok }
60
77
  it { should include :internal_server_error }
78
+ it { should include :created }
61
79
  end
62
80
 
63
81
  describe '#allowed_responses' do
@@ -252,6 +270,10 @@ describe Praxis::ActionDefinition do
252
270
  end
253
271
  end
254
272
 
273
+ context 'responses' do
274
+ subject(:response_description) { describe[:responses] }
275
+ its(:keys) { should include(:ok) }
276
+ end
255
277
  end
256
278
 
257
279
  context 'href generation' do
@@ -318,7 +340,7 @@ describe Praxis::ActionDefinition do
318
340
  end
319
341
 
320
342
  its('routes.first.path.to_s') { should eq '/apps/:app_name/foobars/hello_world/:one' }
321
- its('params.attributes.keys') { should eq [:inherited, :app_name, :one]}
343
+ its('params.attributes.keys') { should match_array [:inherited, :app_name, :one]}
322
344
 
323
345
  context 'where the action overrides a base_param' do
324
346
 
@@ -70,5 +70,33 @@ describe Praxis::Bootloader do
70
70
  bootloader.use(plugin)
71
71
  expect(bootloader.application.plugins[:foo].class).to be(plugin)
72
72
  end
73
+
74
+ it "complains if a plugin with same name already registered" do
75
+ bootloader.use(plugin)
76
+ expect do
77
+ bootloader.use(plugin)
78
+ end.to raise_error /another plugin (.*) is already registered with key: foo/
79
+ end
80
+ context "defaults config_key" do
81
+ let(:plugin_two) do
82
+ Class.new(Praxis::Plugin) do
83
+ def self.name
84
+ "Two" # Need this to avoid creating a true named class.
85
+ end
86
+ end
87
+ end
88
+
89
+ it "to the class name" do
90
+ bootloader.use(plugin_two)
91
+ expect(bootloader.application.plugins[:two].class).to be(plugin_two)
92
+ end
93
+
94
+ it 'but raises if class is anonymous' do
95
+ plugin_anon = Class.new(Praxis::Plugin) {}
96
+ expect do
97
+ bootloader.use(plugin_anon)
98
+ end.to raise_error(/It does not have a config_key defined, and its class does not have a name/)
99
+ end
100
+ end
73
101
  end
74
102
  end
@@ -91,6 +91,16 @@ describe Praxis::Extensions::FieldExpansion do
91
91
  end
92
92
  end
93
93
  end
94
+
95
+
96
+ context 'with an action with no params' do
97
+ let(:test_params) { nil }
98
+ let(:fields){ nil }
99
+ let(:view){ nil }
100
+ it 'ignores incoming parameters and expands for the default view' do
101
+ expect(expansion).to eq({id: true, name: true, links: [true]})
102
+ end
103
+ end
94
104
  end
95
105
 
96
106
  end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+
3
+ describe Praxis::MiddlewareApp do
4
+
5
+ context '.for' do
6
+ let(:init_args){ { root: 'here'} }
7
+ subject(:middleware) { Praxis::MiddlewareApp.for( init_args ) }
8
+
9
+ it 'initializes the application singletone with the passed parameters' do
10
+ expect( Praxis::Application.instance ).to receive(:setup).with( init_args ).once
11
+ subject
12
+ end
13
+ it 'returns its class' do
14
+ expect( subject ).to be( Praxis::MiddlewareApp )
15
+ end
16
+ end
17
+
18
+ context 'instantiated' do
19
+ let(:target_response){ [201,{}] }
20
+ let(:target){ double("target app", call: target_response) }
21
+ subject(:instance){ Praxis::MiddlewareApp.new(target)}
22
+ it 'saves the target app' do
23
+ expect(subject.target).to be(target)
24
+ end
25
+ context '.call' do
26
+ let(:env){ {} }
27
+ let(:praxis_response){ [200,{}] }
28
+ subject(:response){ Praxis::MiddlewareApp.new(target).call(env) }
29
+ before do
30
+ # always invokes the praxis app
31
+ expect( Praxis::Application.instance ).to receive(:call).with( env ).once.and_return(praxis_response)
32
+ end
33
+
34
+ context 'properly handled (non-404 and 405) responses from praxis' do
35
+ it 'are returned straight through' do
36
+ expect( response ).to be(praxis_response)
37
+ end
38
+ end
39
+
40
+ context '404/405 responses with X-Cascade = pass' do
41
+ let(:praxis_response){ [404, {'X-Cascade' => 'pass'}]}
42
+ it 'are forwarded to the target app' do
43
+ expect( response ).to be(target_response)
44
+ end
45
+ end
46
+
47
+ context '404/405 responses without X-Cascade = pass' do
48
+ let(:praxis_response){ [404, {}]}
49
+ it 'returned straight through' do
50
+ expect( response ).to be(praxis_response)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -4,7 +4,6 @@ describe Praxis::Plugins::PraxisMapperPlugin do
4
4
 
5
5
  subject(:plugin) { Praxis::Plugins::PraxisMapperPlugin::Plugin.instance }
6
6
  let(:config) { plugin.config }
7
-
8
7
  context 'Plugin' do
9
8
  context 'configuration' do
10
9
  subject { config }
@@ -15,8 +14,14 @@ describe Praxis::Plugins::PraxisMapperPlugin do
15
14
  context 'default repository' do
16
15
  subject(:default) { config.repositories['default'] }
17
16
  its(['type']) { should eq 'sequel' }
18
- its(['connection_settings']) do
19
- should eq('adapter' => 'sqlite','database' => ':memory:')
17
+ it 'has the right connection settings' do
18
+ if RUBY_PLATFORM !~ /java/
19
+ expect(subject['connection_settings'].dump).to eq( 'adapter' => 'sqlite','database' => ':memory:' )
20
+ else
21
+ expect(subject['connection_settings'].dump).to eq( 'adapter' => 'jdbc', 'uri' => 'jdbc:sqlite::memory:' )
22
+ end
23
+
24
+
20
25
  end
21
26
  end
22
27
 
@@ -26,7 +26,7 @@ describe Praxis::ResourceDefinition do
26
26
 
27
27
  context 'for a resource with a parent' do
28
28
  let(:resource_definition) { ApiResources::VolumeSnapshots}
29
-
29
+
30
30
  its([:parent]) { should eq ApiResources::Volumes.id }
31
31
  end
32
32
 
@@ -78,6 +78,7 @@ describe Praxis::ResourceDefinition do
78
78
  include Praxis::ResourceDefinition
79
79
  media_type Person
80
80
 
81
+ version '1.0'
81
82
  def self.name
82
83
  'FooBar'
83
84
  end
@@ -101,12 +102,60 @@ describe Praxis::ResourceDefinition do
101
102
  end
102
103
  end
103
104
 
105
+ let(:non_singleton_api) do
106
+ api_def=Praxis::ApiDefinition.__send__(:new)
107
+ api_def.instance_eval do |api|
108
+
109
+ api.info do
110
+ base_path '/api/:base_param'
111
+ base_params do
112
+ attribute :base_param, String
113
+ attribute :grouped_params do
114
+ attribute :nested_param, String
115
+ end
116
+ end
117
+ end
118
+
119
+ api.info '1.0' do
120
+ base_params do
121
+ attribute :app_name, String
122
+ end
123
+ end
124
+ api.info '2.0' do
125
+ base_params do
126
+ attribute :v2_param, String
127
+ end
128
+ end
129
+ end
130
+ api_def
131
+ end
132
+
133
+ before do
134
+ allow(Praxis::ApiDefinition).to receive(:instance).and_return(non_singleton_api)
135
+ end
136
+
104
137
  it 'are applied to actions' do
105
138
  action = resource_definition.actions[:show]
106
139
  expect(action.params.attributes).to have_key(:id)
107
- expect(action.routes.first.path.to_s).to eq '/api/people/:id'
140
+ expect(action.routes.first.path.to_s).to eq '/api/:base_param/people/:id'
108
141
  end
109
142
 
143
+ context 'includes base_params from the APIDefinition' do
144
+ let(:show_action_params){ resource_definition.actions[:show].params }
145
+
146
+ it 'including globally defined' do
147
+ expect(show_action_params.attributes).to have_key(:base_param)
148
+ expect(show_action_params.attributes).to have_key(:grouped_params)
149
+ grouped = show_action_params.attributes[:grouped_params]
150
+ expect(grouped.type.ancestors).to include(::Attributor::Struct)
151
+ expect(grouped.type.attributes.keys).to eq([:nested_param])
152
+ end
153
+ it 'including the ones defined for its own version' do
154
+ expect(show_action_params.attributes).to have_key(:app_name)
155
+ expect(show_action_params.attributes).to_not have_key(:v2_param)
156
+ end
157
+
158
+ end
110
159
  end
111
160
 
112
161
  context 'setting other values' do
@@ -448,7 +448,7 @@ describe Praxis::ResponseDefinition do
448
448
  context 'with legacy multipart response' do
449
449
  subject(:response) { Praxis::Responses::Ok.new(status: response_status, headers: response_headers) }
450
450
 
451
- let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, 'Content-Type' => 'application/special'}) }
451
+ let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, 'Content-Type' => 'application/special'},{}) }
452
452
 
453
453
  before do
454
454
  response_definition.parts like: :ok, media_type: 'application/special'
@@ -463,7 +463,7 @@ describe Praxis::ResponseDefinition do
463
463
  end
464
464
 
465
465
  context 'with invalid part' do
466
- let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, "Location" => "somewhere"}) }
466
+ let(:part) { Praxis::MultipartPart.new('done', {'Status' => 200, "Location" => "somewhere"},{}) }
467
467
 
468
468
  it 'validates' do
469
469
  expect {
@@ -520,8 +520,8 @@ describe Praxis::ResponseDefinition do
520
520
  its([:name]) { should eq 'Instance' }
521
521
  context 'examples' do
522
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' }
523
+ its(['json', :content_type]) { should eq('application/vnd.acme.instance+json') }
524
+ its(['xml', :content_type]) { should eq('application/vnd.acme.instance+xml') }
525
525
 
526
526
  it 'properly encodes the example bodies' do
527
527
  json = Praxis::Application.instance.handlers['json'].parse(examples['json'][:body])
@@ -531,6 +531,18 @@ describe Praxis::ResponseDefinition do
531
531
 
532
532
  end
533
533
 
534
+ context 'which does not have a identifier' do
535
+ subject(:examples) { payload[:examples] }
536
+ before do
537
+ allow(response.media_type).to receive(:identifier).and_return(nil)
538
+ end
539
+
540
+ it 'still renders examples but as pure handler types for contents' do
541
+ expect(subject['json'][:content_type]).to eq('application/json')
542
+ expect(subject['xml'][:content_type]).to eq('application/xml')
543
+ end
544
+ end
545
+
534
546
 
535
547
  end
536
548