praxis 0.9 → 0.10.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/CHANGELOG.md +51 -0
  4. data/CONTRIBUTING.md +3 -2
  5. data/Gemfile +1 -1
  6. data/README.md +5 -3
  7. data/lib/api_browser/app/css/main.css +4 -0
  8. data/lib/api_browser/app/js/directives/attribute_description.js +17 -14
  9. data/lib/api_browser/app/js/directives/type_label.js +7 -7
  10. data/lib/api_browser/app/sass/modules/_body.scss +8 -1
  11. data/lib/api_browser/app/views/directives/attribute_description/_example.html +9 -4
  12. data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +4 -3
  13. data/lib/api_browser/app/views/resource/_actions.html +11 -5
  14. data/lib/praxis/action_definition/headers_dsl_compiler.rb +6 -6
  15. data/lib/praxis/action_definition.rb +19 -4
  16. data/lib/praxis/api_definition.rb +3 -3
  17. data/lib/praxis/application.rb +1 -0
  18. data/lib/praxis/bootloader_stages/routing.rb +3 -3
  19. data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +1 -0
  20. data/lib/praxis/callbacks.rb +35 -0
  21. data/lib/praxis/controller.rb +18 -35
  22. data/lib/praxis/file_group.rb +12 -7
  23. data/lib/praxis/links.rb +4 -0
  24. data/lib/praxis/media_type_collection.rb +21 -15
  25. data/lib/praxis/request.rb +2 -7
  26. data/lib/praxis/request_stages/action.rb +10 -7
  27. data/lib/praxis/request_stages/request_stage.rb +45 -10
  28. data/lib/praxis/request_stages/validate.rb +7 -3
  29. data/lib/praxis/responses/http.rb +0 -24
  30. data/lib/praxis/responses/internal_server_error.rb +40 -0
  31. data/lib/praxis/responses/validation_error.rb +1 -0
  32. data/lib/praxis/version.rb +1 -1
  33. data/lib/praxis.rb +5 -24
  34. data/praxis.gemspec +3 -3
  35. data/spec/functional_spec.rb +62 -17
  36. data/spec/praxis/action_definition_spec.rb +14 -6
  37. data/spec/praxis/callbacks_spec.rb +107 -0
  38. data/spec/praxis/controller_spec.rb +0 -38
  39. data/spec/praxis/file_group_spec.rb +5 -0
  40. data/spec/praxis/media_type_collection_spec.rb +20 -7
  41. data/spec/praxis/request_spec.rb +15 -1
  42. data/spec/praxis/request_stage_spec.rb +93 -0
  43. data/spec/praxis/request_stages_action_spec.rb +61 -0
  44. data/spec/praxis/request_stages_validate_spec.rb +1 -1
  45. data/spec/praxis/responses/internal_server_error_spec.rb +51 -0
  46. data/spec/praxis/responses/validation_error_spec.rb +66 -0
  47. data/spec/praxis/router_spec.rb +5 -1
  48. data/spec/spec_app/app/concerns/authenticated.rb +15 -0
  49. data/spec/spec_app/app/concerns/basic_api.rb +12 -0
  50. data/spec/spec_app/app/concerns/log_wrapper.rb +14 -0
  51. data/spec/spec_app/app/controllers/base_class.rb +14 -0
  52. data/spec/spec_app/app/controllers/instances.rb +31 -5
  53. data/spec/spec_app/app/controllers/volumes.rb +17 -0
  54. data/spec/spec_app/config/environment.rb +1 -0
  55. data/spec/spec_app/design/api.rb +3 -3
  56. data/spec/spec_app/design/media_types/volume.rb +1 -1
  57. data/spec/spec_app/design/media_types/volume_snapshot.rb +1 -0
  58. data/spec/spec_app/design/resources/instances.rb +10 -3
  59. data/spec/spec_app/design/resources/volumes.rb +27 -0
  60. data/spec/support/spec_resource_definitions.rb +32 -0
  61. data/tasks/praxis_app_generator.thor +2 -2
  62. metadata +23 -9
@@ -7,6 +7,18 @@ describe Praxis::MediaTypeCollection do
7
7
 
8
8
  let(:snapshots) { example.snapshots }
9
9
 
10
+ subject(:media_type_collection) do
11
+ Class.new(Praxis::MediaTypeCollection) do
12
+ member_type Volume
13
+ end
14
+ end
15
+
16
+ context '.member_type' do
17
+ its(:member_type){ should be(Volume) }
18
+ its(:member_attribute){ should be_kind_of(Attributor::Attribute) }
19
+ its('member_attribute.type'){ should be(Volume) }
20
+ end
21
+
10
22
  context '.load' do
11
23
  let(:volume_data) do
12
24
  {
@@ -66,13 +78,7 @@ describe Praxis::MediaTypeCollection do
66
78
 
67
79
  context '#render' do
68
80
 
69
- context 'for views that are not defined on on the collection' do
70
- subject(:output) { snapshots.render(:default) }
71
-
72
- it { should eq(snapshots.collect(&:render)) }
73
- end
74
-
75
- context 'for defined views' do
81
+ context 'for standard views' do
76
82
  subject(:output) { snapshots.render(:link) }
77
83
 
78
84
  its([:name]) { should eq(snapshots.name)}
@@ -80,6 +86,13 @@ describe Praxis::MediaTypeCollection do
80
86
  its([:href]) { should eq(snapshots.href)}
81
87
  end
82
88
 
89
+ context 'for member views' do
90
+ subject(:output) { snapshots.render(:default) }
91
+
92
+ it { should eq(snapshots.collect(&:render)) }
93
+ end
94
+
95
+
83
96
  end
84
97
 
85
98
  context '#validate' do
@@ -7,10 +7,11 @@ describe Praxis::Request do
7
7
  env['rack.input'] = rack_input
8
8
  env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
9
9
  env['HTTP_VERSION'] = 'HTTP/1.1'
10
+ env['HTTP_AUTHORIZATION'] = 'Secret'
10
11
  env
11
12
  end
12
13
 
13
- let(:action) { Instances.actions[:show] }
14
+ let(:action) { Instances.definition.actions[:show] }
14
15
 
15
16
  let(:context) do
16
17
  {
@@ -63,6 +64,19 @@ describe Praxis::Request do
63
64
 
64
65
  end
65
66
 
67
+ context '#load_headers' do
68
+
69
+ it 'is done preserving the original case' do
70
+ request.load_headers(context[:headers])
71
+ expect(request.headers).to match({"Authorization" => "Secret"})
72
+ end
73
+
74
+ it 'performs it using the memoized rack keys from the action (Hacky but...performance is important)' do
75
+ expect(action).to receive(:precomputed_header_keys_for_rack).and_call_original
76
+ request.load_headers(context[:headers])
77
+ end
78
+ end
79
+
66
80
  context "performs request validation" do
67
81
  before(:each) do
68
82
  request.load_headers(context[:headers])
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe Praxis::RequestStages::RequestStage do
4
+ # Ugly, but clear and easy to test the shortcuts
5
+ $before_controller_callback = Proc.new { nil }
6
+ $after_controller_callback = Proc.new { nil }
7
+ $before_show_controller_callback = Proc.new { Praxis::Responses::Unauthorized.new }
8
+
9
+ class MyController
10
+ include Praxis::Controller
11
+ before( :action , &$before_controller_callback)
12
+ after( :action , &$after_controller_callback)
13
+ before( actions: [:show], &$before_show_controller_callback)
14
+ end
15
+ class MyStage < Praxis::RequestStages::RequestStage
16
+ end
17
+
18
+ let(:controller){ MyController.new( double("request") ) }
19
+ let(:action_name) { :unknown }
20
+ let(:action){ double("action", name: action_name )}
21
+ let(:context){ double("context", controller: controller , action: action )}
22
+ subject(:stage) { MyStage.new(:action, context) }
23
+
24
+ context ".run" do
25
+ context 'for an abstract stage' do
26
+ subject(:stage) {Praxis::RequestStages::RequestStage.new(:action, context)}
27
+ it 'complains about having to implement execute' do
28
+ expect{stage.run}.to raise_error(NotImplementedError,/Subclass must implement Stage#execute/)
29
+ end
30
+ end
31
+
32
+ it "sets up and execute callbacks" do
33
+ expect(stage).to receive(:setup!)
34
+ expect(stage).to receive(:setup_deferred_callbacks!)
35
+ expect(stage).to receive(:execute)
36
+ expect(stage).to receive(:execute_callbacks).twice
37
+ expect(stage).to receive(:execute_controller_callbacks).twice
38
+ stage.run
39
+ end
40
+
41
+
42
+ context 'when before controller callbacks return a Response' do
43
+ let(:action_name) { :show }
44
+
45
+ after do
46
+ stage.run
47
+ end
48
+ it 'only calls the before callbacks' do
49
+ expect(stage).to receive(:execute_callbacks).once.and_call_original
50
+ expect(stage).to receive(:execute_controller_callbacks).once.and_call_original
51
+ end
52
+ it 'stops executing any other ones in the before chain' do
53
+ expect($before_controller_callback).to receive(:call).once
54
+ end
55
+ it 'does not call "execute"' do
56
+ expect(stage).to_not receive(:execute)
57
+ end
58
+ it 'does not execute any "after" callbacks either' do
59
+ expect($after_controller_callback).to_not receive(:call)
60
+ end
61
+ end
62
+ end
63
+
64
+ context ".execute" do
65
+ let(:double_stage_ok){ double("ok stage") }
66
+
67
+ context 'when all stages suceed' do
68
+ before do
69
+ expect(double_stage_ok).to receive(:run).twice
70
+ stage.instance_variable_set(:@stages, [double_stage_ok, double_stage_ok])
71
+ end
72
+
73
+ it "runs them all" do
74
+ stage.execute
75
+ end
76
+ it 'returns nil' do
77
+ expect( stage.execute ).to be(nil)
78
+ end
79
+ end
80
+ context 'when one stage returns a Response' do
81
+ it "runs no further stages after that" do
82
+ double_stage_fail = double("fail stage")
83
+ expect(double_stage_ok).to receive(:run).once
84
+ expect(double_stage_fail).to receive(:run).once.and_return(Praxis::Responses::Ok.new)
85
+ stage.instance_variable_set(:@stages, [double_stage_ok, double_stage_fail, double_stage_ok])
86
+ stage.execute
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+
93
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Praxis::RequestStages::Action do
4
+
5
+ let(:controller) do
6
+ Class.new do
7
+ include Praxis::Controller
8
+ end.new(request)
9
+ end
10
+
11
+ let(:action) { double("action", name: "foo") }
12
+ let(:response){ Praxis::Responses::Ok.new }
13
+ let(:app){ double("App", controller: controller, action:action, request: request)}
14
+ let(:action_stage){ Praxis::RequestStages::Action.new(action.name,app) }
15
+
16
+ let(:request) do
17
+ env = Rack::MockRequest.env_for('/instances/1?cloud_id=1&api_version=1.0')
18
+ env['rack.input'] = StringIO.new('something=given')
19
+ env['HTTP_VERSION'] = 'HTTP/1.1'
20
+ env['HTTP_HOST'] = 'rightscale'
21
+ request = Praxis::Request.new(env)
22
+ request.action = action
23
+ request
24
+ end
25
+
26
+
27
+ context '.execute' do
28
+ before do
29
+ expect(controller).to receive(action_stage.name).with(request.params_hash).and_return(controller_response)
30
+ end
31
+ let(:controller_response){ controller.response }
32
+ it 'should always call the right controller method' do
33
+ action_stage.execute
34
+ end
35
+ it 'should save the request reference inside the response' do
36
+ action_stage.execute
37
+ expect(controller.response.request).to eq(request)
38
+ end
39
+
40
+ context 'if the controller method returns a string' do
41
+ let(:controller_response){ "this is the body"}
42
+ it 'set the response body with it (and save the request too)' do
43
+ action_stage.execute
44
+ expect(controller.response.body).to eq("this is the body")
45
+ end
46
+ end
47
+ context 'if the controller method returns a response object' do
48
+ let(:controller_response){ Praxis::Responses::Created.new }
49
+ it 'set that response in the controller' do
50
+ action_stage.execute
51
+ expect(controller.response).to eq(controller_response)
52
+ end
53
+ end
54
+ context 'if the controller method returns neither a string or a response' do
55
+ let(:controller_response){ nil }
56
+ it 'an error is raised ' do
57
+ expect{ action_stage.execute }.to raise_error(/Only Response objects or Strings allowed/)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -7,7 +7,7 @@ describe Praxis::RequestStages::Validate do
7
7
  # controller for the specs instead of creating a simple controller.
8
8
  let(:controller) { Instances }
9
9
 
10
- let(:action) { controller.actions[:show] }
10
+ let(:action) { controller.definition.actions[:show] }
11
11
 
12
12
  let(:request) do
13
13
  env = Rack::MockRequest.env_for('/instances/1?cloud_id=1&api_version=1.0')
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Praxis::Responses::InternalServerError do
5
+
6
+ context 'a basic response' do
7
+ subject(:response) { Praxis::Responses::InternalServerError.new }
8
+ it 'always sets the json content type' do
9
+ expect(response.name).to be(:internal_server_error)
10
+ expect(response.status).to be(500)
11
+ expect(response.body).to be_empty
12
+ expect(response.headers).to have_key('Content-Type')
13
+ expect(response.headers['Content-Type']).to eq('application/json')
14
+ expect(response.instance_variable_get(:@error)).to be(nil)
15
+ end
16
+ end
17
+
18
+ context '.format!' do
19
+ let(:error) { double('error', message: 'error message', backtrace: [1, 2], cause: cause) }
20
+ subject(:response) { Praxis::Responses::InternalServerError.new(error: error) }
21
+ before do
22
+ expect(response.body).to be_empty
23
+ response.format!
24
+ end
25
+
26
+ context 'without a cause' do
27
+ let(:cause) { nil }
28
+ it 'it fills message and backtrace' do
29
+ expect(response.body).to eq({name: error.class.name, message: error.message, backtrace: error.backtrace})
30
+ end
31
+ end
32
+
33
+ context 'with a cause' do
34
+ let(:cause) { Exception.new('cause message') }
35
+ it 'it fills message, backtrace and cause' do
36
+ expect(response.body.keys).to eq([:name, :message, :backtrace, :cause])
37
+ expect(response.body[:name]).to eq(error.class.name)
38
+ expect(response.body[:message]).to eq(error.message)
39
+ expect(response.body[:backtrace]).to eq(error.backtrace)
40
+ expect(response.body[:cause]).to eq({ name: cause.class.name, message: cause.message, backtrace: cause.backtrace })
41
+ end
42
+ end
43
+ end
44
+
45
+ context 'its response template' do
46
+ let(:template){ Praxis::ApiDefinition.instance.responses[:internal_server_error] }
47
+ it 'is registered with the ApiDefinition' do
48
+ expect(template).to be_kind_of(Praxis::ResponseTemplate)
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Praxis::Responses::ValidationError do
5
+
6
+ context 'a basic response' do
7
+ subject(:response) { Praxis::Responses::ValidationError.new }
8
+ it 'always sets the json content type' do
9
+ expect(response.name).to be(:validation_error)
10
+ expect(response.status).to be(400)
11
+ expect(response.body).to be_empty
12
+ expect(response.headers).to have_key('Content-Type')
13
+ expect(response.headers['Content-Type']).to eq('application/json')
14
+ expect(response.instance_variable_get(:@errors)).to be(nil)
15
+ expect(response.instance_variable_get(:@exception)).to be(nil)
16
+ end
17
+ end
18
+
19
+ context '.format!' do
20
+ let(:errors) { [1,2] }
21
+ let(:cause){ Exception.new( "cause message") }
22
+ let(:exception_message){ "exception message" }
23
+ let(:exception){ nil }
24
+ subject(:response) { Praxis::Responses::ValidationError.new(errors: errors, exception: exception) }
25
+ before do
26
+ expect(response.body).to be_empty
27
+ end
28
+ context 'when errors' do
29
+ it 'it fills the errors key' do
30
+ response.format!
31
+ expect(response.body).to eq({name: 'ValidationError', errors: errors})
32
+ end
33
+ end
34
+
35
+ context 'without errors but with an exception' do
36
+ let(:errors) { nil }
37
+ let(:exception){ double("exception", message: exception_message, cause: cause)}
38
+ before do
39
+ response.format!
40
+ end
41
+ context 'without cause' do
42
+ let(:cause){ nil}
43
+ it 'it fills the message' do
44
+ expect(response.body).to eq({name: 'ValidationError', message: exception_message} )
45
+ end
46
+ end
47
+
48
+ context 'with a cause' do
49
+ it 'it fills the message' do
50
+ expect(response.body.keys).to eq([:name,:message,:cause])
51
+ expect(response.body[:name]).to eq('ValidationError')
52
+ expect(response.body[:message]).to eq(exception_message)
53
+ expect(response.body[:cause]).to eq({name: cause.class.name, message: cause.message })
54
+ end
55
+ end
56
+ end
57
+
58
+ end
59
+
60
+ context 'its response template' do
61
+ let(:template){ Praxis::ApiDefinition.instance.responses[:validation_error] }
62
+ it 'is registered with the ApiDefinition' do
63
+ expect(template).to be_kind_of(Praxis::ResponseTemplate)
64
+ end
65
+ end
66
+ end
@@ -72,9 +72,14 @@ describe Praxis::Router do
72
72
  end
73
73
 
74
74
  context ".add_route" do
75
+ before do
76
+ expect(router).to receive(:warn).with("other conditions not supported yet")
77
+ end
78
+
75
79
  let(:route){ double('route', options: [1], version: 1, verb: 'verb', path: 'path')}
76
80
  let(:target){ double('target') }
77
81
  let(:verb_router){ double('verb_router') }
82
+
78
83
  it 'wraps the target with a VersionMatcher' do
79
84
  router.instance_variable_set( :@routes, {'verb'=>verb_router} ) # Ugly, but no need to have a reader
80
85
  expect(verb_router).to receive(:on) do|path, args|# .with(route.path, call: "foo")
@@ -86,7 +91,6 @@ describe Praxis::Router do
86
91
  end
87
92
 
88
93
  it "raises warning when options are specified in route" do
89
- expect(router).to receive(:warn).with("other conditions not supported yet")
90
94
  expect(router.add_route(proc {'target'},route)).to eq(['path'])
91
95
  end
92
96
  end
@@ -0,0 +1,15 @@
1
+ module Concerns
2
+ module Authenticated
3
+ extend ActiveSupport::Concern
4
+ include Praxis::Callbacks
5
+
6
+ included do
7
+ before :action do |controller|
8
+ auth_data = controller.request.headers['Authorization']
9
+ if auth_data && auth_data !~ /secret/
10
+ Praxis::Responses::Unauthorized.new(body: 'Authentication info is invalid')
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'authenticated'
2
+ require_relative 'log_wrapper'
3
+
4
+ module Concerns
5
+ module BasicApi
6
+ extend ActiveSupport::Concern
7
+
8
+ # Basic Api controllers will need the Authenticated and LogWrapper concerns
9
+ include Concerns::Authenticated
10
+ include Concerns::LogWrapper
11
+ end
12
+ end
@@ -0,0 +1,14 @@
1
+ module Concerns
2
+ module LogWrapper
3
+ extend ActiveSupport::Concern
4
+ include Praxis::Callbacks
5
+
6
+ included do
7
+ before :around do |controller, callee|
8
+ # Log something at the beginning
9
+ callee.call
10
+ # Log something at the end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ class BaseClass
2
+ # Do NOT use a controller inherited base class to add concerns!
3
+ # If you do that, you'd be sharing state (i.e. overriding/adding) from concerns across controllers
4
+ # which will probably lead to sharing issues you didn't expect.
5
+ # For example: any controller adding after/before/around filters will be visible
6
+ # to any other controllers sharing the concern.
7
+ # Include a concern to all of them instead
8
+
9
+ # Inheritance of classes should be independent from the concerns.
10
+ # I.e., you can use class inheritance in cases where it makes sense from an OO point of view
11
+ # but for the most part, you can probably share code through modules/concerns too.
12
+ def this_is_shared
13
+ end
14
+ end
@@ -1,14 +1,39 @@
1
- class Instances
1
+ class Instances < BaseClass
2
2
  include Praxis::Controller
3
3
 
4
4
  implements ApiResources::Instances
5
-
5
+ include Concerns::BasicApi
6
+
6
7
  before :validate, actions: [:index] do |controller|
7
8
  #p [:before, :validate, :params_and_headers, controller.request.action.name]
8
9
  end
9
10
 
10
- before actions: [:show] do
11
+ before actions: [:show] do |controller|
11
12
  #puts "before action"
13
+ if controller.request.params.fail_filter
14
+ Praxis::Responses::Unauthorized.new
15
+ end
16
+ end
17
+
18
+ around :validate, actions: [:show] do |controller, blk|
19
+ #puts "Before validate decorator (for show)"
20
+ blk.call
21
+ #puts "After validate decorator"
22
+ end
23
+ around :action do |controller, blk|
24
+ #puts "Decorator one (all actions) start"
25
+ blk.call
26
+ #puts "Decorator one end"
27
+ end
28
+ around :action, actions: [:show] do |controller, blk|
29
+ #puts "Decorator two (show action) start"
30
+ blk.call
31
+ #puts "Decorator two end"
32
+ end
33
+ around :action, actions: [:index] do |controller, blk|
34
+ #puts "Decorator three (index action) start"
35
+ blk.call
36
+ #puts "Decorator three end"
12
37
  end
13
38
 
14
39
  def index(response_content_type:, **params)
@@ -31,7 +56,7 @@ class Instances
31
56
  headers = {
32
57
  'Status' => '201',
33
58
  'Content-Type' => Instance.identifier,
34
- 'Location' => self.class.action(:show).primary_route.path.expand(cloud_id: cloud_id, id: instance.id)
59
+ 'Location' => self.class.definition.actions[:show].primary_route.path.expand(cloud_id: cloud_id, id: instance.id)
35
60
  }
36
61
 
37
62
  part = Praxis::MultipartPart.new(part_body, headers)
@@ -51,7 +76,8 @@ class Instances
51
76
 
52
77
  result = {
53
78
  destination_path: destination_path,
54
- file: file.dump
79
+ file: file.dump,
80
+ options: request.payload['options'].dump
55
81
  }
56
82
 
57
83
  response.body = JSON.pretty_generate(result)
@@ -0,0 +1,17 @@
1
+ class Volumes < BaseClass
2
+ include Praxis::Controller
3
+
4
+ implements ApiResources::Volumes
5
+ include Concerns::BasicApi
6
+
7
+ before actions: [:show] do |controller|
8
+ #puts "before action for volumes"
9
+ end
10
+
11
+ def show(id:, **other_params)
12
+ response.body = { id: id, other_params: other_params }
13
+ response.headers['Content-Type'] = 'application/vnd.acme.volume'
14
+ response
15
+ end
16
+
17
+ end
@@ -9,6 +9,7 @@ Praxis::Application.configure do |application|
9
9
  end
10
10
  map :app, 'app/' do
11
11
  map :models, 'models/**/*'
12
+ map :concerns, '**/concerns/**/*'
12
13
  map :controllers, '**/controllers/**/*'
13
14
  map :responses, '**/responses/**/*'
14
15
  end
@@ -10,17 +10,17 @@ Praxis::ApiDefinition.define do
10
10
  media_type 'multipart/form-data'
11
11
  end
12
12
 
13
- response_template :bulk_response do |media_type:, parts: |
13
+ response_template :bulk_response do |media_type: nil, parts: |
14
14
  status 200
15
15
  media_type 'multipart/form-data'
16
16
 
17
- parts[:media_type] ||= media_type if parts.kind_of? Hash
17
+ parts[:media_type] ||= media_type if ( media_type && parts.kind_of?(Hash) )
18
18
  parts(parts)
19
19
  end
20
20
 
21
21
  trait :authenticated do
22
22
  headers do
23
- header :host
23
+ key "Authorization", String, required: false
24
24
  end
25
25
  end
26
26
 
@@ -1,5 +1,5 @@
1
1
  class Volume < Praxis::MediaType
2
- identifier 'application/json'
2
+ identifier 'application/vnd.acme.volume'
3
3
 
4
4
  attributes do
5
5
  attribute :id, Integer
@@ -33,6 +33,7 @@ class VolumeSnapshot < Praxis::MediaType
33
33
  attribute :href
34
34
  end
35
35
 
36
+ member_view :default, using: :default
36
37
  end
37
38
 
38
39
  end
@@ -29,6 +29,11 @@ module ApiResources
29
29
  params do
30
30
  attribute :response_content_type, String, default: response_content_type
31
31
  end
32
+ headers do
33
+ # BOTH ARE EQUIVALENT
34
+ #key "FOO", String, required: true
35
+ header "FOO", /bar/
36
+ end
32
37
 
33
38
  response :ok, media_type: response_content_type
34
39
  end
@@ -45,6 +50,7 @@ module ApiResources
45
50
  attribute :id
46
51
  attribute :junk, String, default: ''
47
52
  attribute :some_date, DateTime, default: DateTime.parse('2012-12-21')
53
+ attribute :fail_filter, Attributor::Boolean, default: false
48
54
  end
49
55
  payload do
50
56
  attribute :something, String
@@ -64,7 +70,7 @@ module ApiResources
64
70
  response :bulk_response ,
65
71
  parts: {
66
72
  like: :created,
67
- media_type: Instance # Could be left blank and will inherit
73
+ location: /\/instances\//
68
74
  }
69
75
 
70
76
  # Using a block for parts to defin a sub-request
@@ -107,9 +113,10 @@ module ApiResources
107
113
  attribute :id
108
114
  end
109
115
 
110
- payload Praxis::Multipart do
116
+ payload Praxis::Multipart, allow_extra: true do
111
117
  key 'destination_path', String, required: true
112
- key 'file', Attributor::FileUpload, required: true
118
+ key 'file', Attributor::FileUpload, required: false
119
+ extra 'options'
113
120
  end
114
121
 
115
122
  response :ok, media_type: 'application/json'
@@ -0,0 +1,27 @@
1
+ module ApiResources
2
+ class Volumes
3
+ include Praxis::ResourceDefinition
4
+
5
+ media_type Volume
6
+ version '1.0'
7
+
8
+ use :authenticated
9
+
10
+ action :show do
11
+ routing do
12
+ get '/:id'
13
+ end
14
+
15
+ response :ok
16
+
17
+ params do
18
+ attribute :id
19
+ attribute :junk, String, default: ''
20
+ attribute :some_date, DateTime, default: DateTime.parse('2012-12-21')
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -31,3 +31,35 @@ class PeopleResource
31
31
  end
32
32
 
33
33
  end
34
+
35
+ class AddressResource
36
+ include Praxis::ResourceDefinition
37
+
38
+ description 'Address resource'
39
+
40
+ media_type Address
41
+
42
+ version '1.0'
43
+
44
+ routing do
45
+ prefix "/addresses"
46
+ end
47
+
48
+ action :index do
49
+ description 'index description'
50
+ routing do
51
+ get ''
52
+ end
53
+ end
54
+
55
+ action :show do
56
+ description 'show description'
57
+ routing do
58
+ get '/:id'
59
+ end
60
+ params do
61
+ attribute :id, Integer, required: true
62
+ end
63
+ end
64
+
65
+ end