praxis 0.9 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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