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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +51 -0
- data/CONTRIBUTING.md +3 -2
- data/Gemfile +1 -1
- data/README.md +5 -3
- data/lib/api_browser/app/css/main.css +4 -0
- data/lib/api_browser/app/js/directives/attribute_description.js +17 -14
- data/lib/api_browser/app/js/directives/type_label.js +7 -7
- data/lib/api_browser/app/sass/modules/_body.scss +8 -1
- data/lib/api_browser/app/views/directives/attribute_description/_example.html +9 -4
- data/lib/api_browser/app/views/directives/attribute_table_row/_links.html +4 -3
- data/lib/api_browser/app/views/resource/_actions.html +11 -5
- data/lib/praxis/action_definition/headers_dsl_compiler.rb +6 -6
- data/lib/praxis/action_definition.rb +19 -4
- data/lib/praxis/api_definition.rb +3 -3
- data/lib/praxis/application.rb +1 -0
- data/lib/praxis/bootloader_stages/routing.rb +3 -3
- data/lib/praxis/bootloader_stages/warn_unloaded_files.rb +1 -0
- data/lib/praxis/callbacks.rb +35 -0
- data/lib/praxis/controller.rb +18 -35
- data/lib/praxis/file_group.rb +12 -7
- data/lib/praxis/links.rb +4 -0
- data/lib/praxis/media_type_collection.rb +21 -15
- data/lib/praxis/request.rb +2 -7
- data/lib/praxis/request_stages/action.rb +10 -7
- data/lib/praxis/request_stages/request_stage.rb +45 -10
- data/lib/praxis/request_stages/validate.rb +7 -3
- data/lib/praxis/responses/http.rb +0 -24
- data/lib/praxis/responses/internal_server_error.rb +40 -0
- data/lib/praxis/responses/validation_error.rb +1 -0
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +5 -24
- data/praxis.gemspec +3 -3
- data/spec/functional_spec.rb +62 -17
- data/spec/praxis/action_definition_spec.rb +14 -6
- data/spec/praxis/callbacks_spec.rb +107 -0
- data/spec/praxis/controller_spec.rb +0 -38
- data/spec/praxis/file_group_spec.rb +5 -0
- data/spec/praxis/media_type_collection_spec.rb +20 -7
- data/spec/praxis/request_spec.rb +15 -1
- data/spec/praxis/request_stage_spec.rb +93 -0
- data/spec/praxis/request_stages_action_spec.rb +61 -0
- data/spec/praxis/request_stages_validate_spec.rb +1 -1
- data/spec/praxis/responses/internal_server_error_spec.rb +51 -0
- data/spec/praxis/responses/validation_error_spec.rb +66 -0
- data/spec/praxis/router_spec.rb +5 -1
- data/spec/spec_app/app/concerns/authenticated.rb +15 -0
- data/spec/spec_app/app/concerns/basic_api.rb +12 -0
- data/spec/spec_app/app/concerns/log_wrapper.rb +14 -0
- data/spec/spec_app/app/controllers/base_class.rb +14 -0
- data/spec/spec_app/app/controllers/instances.rb +31 -5
- data/spec/spec_app/app/controllers/volumes.rb +17 -0
- data/spec/spec_app/config/environment.rb +1 -0
- data/spec/spec_app/design/api.rb +3 -3
- data/spec/spec_app/design/media_types/volume.rb +1 -1
- data/spec/spec_app/design/media_types/volume_snapshot.rb +1 -0
- data/spec/spec_app/design/resources/instances.rb +10 -3
- data/spec/spec_app/design/resources/volumes.rb +27 -0
- data/spec/support/spec_resource_definitions.rb +32 -0
- data/tasks/praxis_app_generator.thor +2 -2
- 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
|
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
|
data/spec/praxis/request_spec.rb
CHANGED
@@ -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
|
data/spec/praxis/router_spec.rb
CHANGED
@@ -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.
|
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
|
data/spec/spec_app/design/api.rb
CHANGED
@@ -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
|
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?
|
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
|
-
|
23
|
+
key "Authorization", String, required: false
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
@@ -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
|
-
|
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:
|
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
|