praxis 0.15.0 → 0.16.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/CHANGELOG.md +28 -2
- data/lib/api_browser/Gruntfile.js +6 -1
- data/lib/praxis.rb +2 -14
- data/lib/praxis/action_definition.rb +45 -16
- data/lib/praxis/api_definition.rb +36 -17
- data/lib/praxis/api_general_info.rb +59 -16
- data/lib/praxis/request_stages/request_stage.rb +2 -6
- data/lib/praxis/resource_definition.rb +50 -17
- data/lib/praxis/router.rb +10 -11
- data/lib/praxis/routing_config.rb +65 -0
- data/lib/praxis/tasks/routes.rb +8 -4
- data/lib/praxis/trait.rb +107 -0
- data/lib/praxis/types/media_type_common.rb +1 -1
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +1 -1
- data/spec/functional_spec.rb +16 -3
- data/spec/praxis/action_definition_spec.rb +107 -5
- data/spec/praxis/api_definition_spec.rb +67 -44
- data/spec/praxis/api_general_info_spec.rb +28 -14
- data/spec/praxis/media_type_spec.rb +33 -6
- data/spec/praxis/resource_definition_spec.rb +46 -12
- data/spec/praxis/router_spec.rb +9 -15
- data/spec/praxis/routing_config_spec.rb +89 -0
- data/spec/praxis/trait_spec.rb +38 -0
- data/spec/spec_app/app/controllers/instances.rb +6 -2
- data/spec/spec_app/design/resources/instances.rb +19 -10
- data/spec/spec_app/design/resources/volumes.rb +3 -3
- data/spec/support/spec_resource_definitions.rb +11 -6
- data/tasks/thor/example.rb +43 -35
- metadata +8 -6
- data/lib/praxis/skeletor/restful_routing_config.rb +0 -55
- data/spec/praxis/restful_routing_config_spec.rb +0 -101
@@ -9,7 +9,7 @@ describe Praxis::ResourceDefinition do
|
|
9
9
|
its(:version) { should eq('1.0') }
|
10
10
|
its(:version_options) { should eq({using: [:header,:params]}) }
|
11
11
|
|
12
|
-
its(:
|
12
|
+
its(:prefix) { should eq('/people') }
|
13
13
|
|
14
14
|
its(:actions) { should have(2).items }
|
15
15
|
its(:metadata) { should_not have_key(:doc_visibility) }
|
@@ -22,6 +22,7 @@ describe Praxis::ResourceDefinition do
|
|
22
22
|
|
23
23
|
its([:actions]) { should have(2).items }
|
24
24
|
its([:metadata]) { should be_kind_of(Hash) }
|
25
|
+
its([:traits]) { should eq [:test] }
|
25
26
|
end
|
26
27
|
|
27
28
|
context '.action' do
|
@@ -34,7 +35,7 @@ describe Praxis::ResourceDefinition do
|
|
34
35
|
expect(index).to be_kind_of(Praxis::ActionDefinition)
|
35
36
|
expect(index.description).to eq("index description")
|
36
37
|
end
|
37
|
-
|
38
|
+
|
38
39
|
it 'complains if action names are not symbols' do
|
39
40
|
expect do
|
40
41
|
Class.new do
|
@@ -46,7 +47,43 @@ describe Praxis::ResourceDefinition do
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
context 'action_defaults' do
|
51
|
+
let(:resource_definition) do
|
52
|
+
Class.new do
|
53
|
+
include Praxis::ResourceDefinition
|
54
|
+
media_type Person
|
55
|
+
|
56
|
+
def self.name
|
57
|
+
'FooBar'
|
58
|
+
end
|
49
59
|
|
60
|
+
action_defaults do
|
61
|
+
routing do
|
62
|
+
prefix '/people/:id'
|
63
|
+
end
|
64
|
+
|
65
|
+
params do
|
66
|
+
attribute :id
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
action :show do
|
71
|
+
routing do
|
72
|
+
get ''
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'are applied to actions' do
|
80
|
+
action = resource_definition.actions[:show]
|
81
|
+
expect(action.params.attributes).to have_key(:id)
|
82
|
+
expect(action.routes.first.path.to_s).to eq '/people/:id'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
50
87
|
context 'setting other values' do
|
51
88
|
subject(:resource_definition) { Class.new {include Praxis::ResourceDefinition } }
|
52
89
|
|
@@ -64,7 +101,7 @@ describe Praxis::ResourceDefinition do
|
|
64
101
|
end
|
65
102
|
|
66
103
|
|
67
|
-
context '.
|
104
|
+
context '.trait' do
|
68
105
|
subject(:resource_definition) { Class.new {include Praxis::ResourceDefinition } }
|
69
106
|
it 'raises an error for missing traits' do
|
70
107
|
expect { resource_definition.use(:stuff) }.to raise_error(Praxis::Exceptions::InvalidTrait)
|
@@ -81,9 +118,10 @@ describe Praxis::ResourceDefinition do
|
|
81
118
|
def self.name
|
82
119
|
'FooBar'
|
83
120
|
end
|
121
|
+
|
84
122
|
silence_warnings do
|
85
123
|
payload { attribute :inherited_payload, String }
|
86
|
-
headers {
|
124
|
+
headers { key "Inherited-Header", String }
|
87
125
|
params { attribute :inherited_params, String }
|
88
126
|
response :not_found
|
89
127
|
end
|
@@ -95,11 +133,7 @@ describe Praxis::ResourceDefinition do
|
|
95
133
|
|
96
134
|
let(:action) { resource_definition.actions[:index] }
|
97
135
|
|
98
|
-
it '
|
99
|
-
expect(resource_definition.action_defaults).to have(4).items
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'delegates defaults to the action' do
|
136
|
+
it 'are applied to the action' do
|
103
137
|
expect(action.payload.attributes).to have_key(:inherited_payload)
|
104
138
|
expect(action.headers.attributes).to have_key("Inherited-Header")
|
105
139
|
expect(action.params.attributes).to have_key(:inherited_params)
|
@@ -110,7 +144,7 @@ describe Praxis::ResourceDefinition do
|
|
110
144
|
|
111
145
|
context 'with nodoc! called' do
|
112
146
|
before do
|
113
|
-
resource_definition.nodoc!
|
147
|
+
resource_definition.nodoc!
|
114
148
|
end
|
115
149
|
|
116
150
|
it 'has the :doc_visibility option set' do
|
@@ -133,7 +167,7 @@ describe Praxis::ResourceDefinition do
|
|
133
167
|
resource_definition.canonical_path :reset
|
134
168
|
}.to raise_error(/'canonical_path' can only be defined once./)
|
135
169
|
end
|
136
|
-
end
|
170
|
+
end
|
137
171
|
context 'if none specified' do
|
138
172
|
subject(:resource_definition) do
|
139
173
|
Class.new do
|
@@ -143,7 +177,7 @@ describe Praxis::ResourceDefinition do
|
|
143
177
|
end
|
144
178
|
end
|
145
179
|
it 'defaults to the :show action' do
|
146
|
-
expect(subject.canonical_path).to eq(subject.actions[:show])
|
180
|
+
expect(subject.canonical_path).to eq(subject.actions[:show])
|
147
181
|
end
|
148
182
|
end
|
149
183
|
context 'with an undefined action' do
|
data/spec/praxis/router_spec.rb
CHANGED
@@ -75,9 +75,6 @@ describe Praxis::Router do
|
|
75
75
|
end
|
76
76
|
|
77
77
|
context ".add_route" do
|
78
|
-
before do
|
79
|
-
expect(router).to receive(:warn).with("other conditions not supported yet")
|
80
|
-
end
|
81
78
|
|
82
79
|
let(:route){ double('route', options: [1], version: 1, verb: 'verb', path: 'path')}
|
83
80
|
let(:target){ double('target') }
|
@@ -93,9 +90,6 @@ describe Praxis::Router do
|
|
93
90
|
router.add_route(target ,route)
|
94
91
|
end
|
95
92
|
|
96
|
-
it "raises warning when options are specified in route" do
|
97
|
-
expect(router.add_route(proc {'target'},route)).to eq(['path'])
|
98
|
-
end
|
99
93
|
end
|
100
94
|
|
101
95
|
context ".call" do
|
@@ -116,34 +110,34 @@ describe Praxis::Router do
|
|
116
110
|
router.add_route(double("P"),double("route1", verb: 'POST', path: '/', options: {} , version: request_version))
|
117
111
|
# Hijack the callable block in the routes (since there's no way to point back to the registered route object)
|
118
112
|
router.instance_variable_get( :@routes )['POST'] = post_target_router
|
119
|
-
|
113
|
+
|
120
114
|
end
|
121
115
|
|
122
116
|
context 'for routes without wildcards (a single POST route)' do
|
123
|
-
|
117
|
+
|
124
118
|
context 'and an incoming POST request' do
|
125
119
|
it "finds a match and invokes it the route" do
|
126
120
|
expect(router.call(request)).to eq(router_response_for_post)
|
127
121
|
end
|
128
122
|
end
|
129
123
|
context 'and an incoming PUT request' do
|
130
|
-
let(:request_verb) { 'PUT' }
|
124
|
+
let(:request_verb) { 'PUT' }
|
131
125
|
it "does not find a route" do
|
132
126
|
response_code, _ , _ = router.call(request)
|
133
127
|
expect(response_code).to be(404)
|
134
128
|
end
|
135
129
|
end
|
136
130
|
end
|
137
|
-
|
131
|
+
|
138
132
|
context 'for routes with wildcards (a POST and a * route)' do
|
139
133
|
before do
|
140
134
|
router.add_route(double("*"),double("route2", verb: 'ANY', path: '/*', options: {} , version: request_version))
|
141
135
|
# Hijack the callable block in the routes (since there's no way to point back to the registered route object)
|
142
|
-
router.instance_variable_get( :@routes )['ANY'] = any_target_router
|
136
|
+
router.instance_variable_get( :@routes )['ANY'] = any_target_router
|
143
137
|
end
|
144
|
-
|
138
|
+
|
145
139
|
context 'and an incoming PUT request' do
|
146
|
-
let(:request_verb) { 'PUT' }
|
140
|
+
let(:request_verb) { 'PUT' }
|
147
141
|
it "it can successfully find a match using the wildcard target" do
|
148
142
|
expect(router.call(request)).to eq(router_response_for_wildcard)
|
149
143
|
end
|
@@ -164,7 +158,7 @@ describe Praxis::Router do
|
|
164
158
|
end
|
165
159
|
|
166
160
|
context "when not_found is returned" do
|
167
|
-
let(:request_verb) { 'DELETE' }
|
161
|
+
let(:request_verb) { 'DELETE' }
|
168
162
|
let(:router_response){ :not_found }
|
169
163
|
|
170
164
|
|
@@ -175,7 +169,7 @@ describe Praxis::Router do
|
|
175
169
|
end
|
176
170
|
|
177
171
|
context 'with X-Cascade disabled' do
|
178
|
-
let(:config) { Praxis::Application.instance.config.praxis }
|
172
|
+
let(:config) { Praxis::Application.instance.config.praxis }
|
179
173
|
before do
|
180
174
|
expect(config).to receive(:x_cascade).and_return(false)
|
181
175
|
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Praxis::RoutingConfig do
|
4
|
+
|
5
|
+
let(:resource_definition) do
|
6
|
+
Class.new do
|
7
|
+
include Praxis::ResourceDefinition
|
8
|
+
def self.name; 'MyResource'; end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:routing_block) { Proc.new{} }
|
13
|
+
let(:default_route_prefix) { "/" + resource_definition.name.split("::").last.underscore }
|
14
|
+
|
15
|
+
subject(:routing_config){ Praxis::RoutingConfig.new(&routing_block) }
|
16
|
+
|
17
|
+
its(:version) { should eq('n/a') }
|
18
|
+
its(:prefix ) { should eq('') }
|
19
|
+
|
20
|
+
context '#prefix' do
|
21
|
+
it 'sets the prefix' do
|
22
|
+
routing_config.prefix '/'
|
23
|
+
expect(routing_config.prefix).to eq('/')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'is additive' do
|
27
|
+
routing_config.prefix '/people/:id'
|
28
|
+
routing_config.prefix '/address'
|
29
|
+
expect(routing_config.prefix).to eq('/people/:id/address')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'strips duplicated /s' do
|
33
|
+
routing_config.prefix '/'
|
34
|
+
routing_config.prefix '/people'
|
35
|
+
expect(routing_config.prefix).to eq('/people')
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#add_route' do
|
41
|
+
let(:path) { '/people' }
|
42
|
+
let(:options) { {} }
|
43
|
+
let(:route) { routing_config.add_route 'GET', path, **options}
|
44
|
+
|
45
|
+
it 'returns a corresponding Praxis::Route' do
|
46
|
+
expect(route).to be_kind_of(Praxis::Route)
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'appends the Route to the set of routes' do
|
50
|
+
expect(routing_config.routes).to include(route)
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'passing options' do
|
54
|
+
let(:options){ {name: 'alternative', except: '/special' } }
|
55
|
+
|
56
|
+
it 'uses :name to name the route' do
|
57
|
+
expect(route.name).to eq('alternative')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'does NOT pass the name option down to mustermann' do
|
61
|
+
expect(Mustermann).to receive(:new).with(path, hash_excluding({name: 'alternative'}))
|
62
|
+
expect(route.name).to eq('alternative')
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'passes them through the underlying mustermann object (telling it to ignore unknown ones)' do
|
66
|
+
expect(Mustermann).to receive(:new).with(path, hash_including(ignore_unknown_options: true, except: '/special'))
|
67
|
+
expect(route.options).to eq( { except: '/special' })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'with prefix defined' do
|
72
|
+
before do
|
73
|
+
routing_config.prefix '/parents/:parent_id'
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'includes the prefix in the route path' do
|
77
|
+
expect(route.path.to_s).to eq '/parents/:parent_id/people'
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'for paths beginning with //' do
|
81
|
+
let(:path) { '//people' }
|
82
|
+
it 'does not include the prefix in the route path' do
|
83
|
+
expect(route.path.to_s).to eq '/people'
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
+
|
3
|
+
describe Praxis::Trait do
|
4
|
+
|
5
|
+
subject(:trait) do
|
6
|
+
Praxis::Trait.new do
|
7
|
+
description 'my awesome trait'
|
8
|
+
|
9
|
+
routing do
|
10
|
+
prefix '/:app_name'
|
11
|
+
end
|
12
|
+
|
13
|
+
response :something
|
14
|
+
response :nothing
|
15
|
+
|
16
|
+
params do
|
17
|
+
attribute :app_name, String
|
18
|
+
attribute :order, String,
|
19
|
+
description: "Field to sort by."
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'describe' do
|
26
|
+
subject(:describe) { trait.describe }
|
27
|
+
|
28
|
+
its([:description]) { should eq('my awesome trait') }
|
29
|
+
|
30
|
+
its([:responses, :something]) { should eq Hash.new }
|
31
|
+
its([:responses, :nothing]) { should eq Hash.new }
|
32
|
+
|
33
|
+
its([:params, :app_name, :type, :name]) { should eq 'String' }
|
34
|
+
its([:params, :order, :type, :name]) { should eq 'String' }
|
35
|
+
its([:routing, :prefix]) { should eq '/:app_name'}
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -3,7 +3,7 @@ class Instances < BaseClass
|
|
3
3
|
|
4
4
|
implements ApiResources::Instances
|
5
5
|
include Concerns::BasicApi
|
6
|
-
|
6
|
+
|
7
7
|
before :validate, actions: [:index] do |controller|
|
8
8
|
#p [:before, :validate, :params_and_headers, controller.request.action.name]
|
9
9
|
end
|
@@ -32,7 +32,7 @@ class Instances < BaseClass
|
|
32
32
|
blk.call
|
33
33
|
#puts "Decorator two end"
|
34
34
|
end
|
35
|
-
|
35
|
+
|
36
36
|
around :action, actions: [:index] do |controller, blk|
|
37
37
|
#puts "Decorator three (index action) start"
|
38
38
|
blk.call
|
@@ -104,4 +104,8 @@ class Instances < BaseClass
|
|
104
104
|
response
|
105
105
|
end
|
106
106
|
|
107
|
+
def exceptional(cloud_id:, splat:)
|
108
|
+
response.headers['Content-Type'] = 'application/vnd.acme.instance'
|
109
|
+
response
|
110
|
+
end
|
107
111
|
end
|
@@ -4,18 +4,16 @@ module ApiResources
|
|
4
4
|
|
5
5
|
media_type Instance
|
6
6
|
version '1.0'
|
7
|
-
|
7
|
+
|
8
8
|
# :show action is the canonical path for this resource.
|
9
9
|
# Note that the following is redundant, since :show is the default canonical path if none is defined.
|
10
10
|
canonical_path :show
|
11
|
-
|
12
|
-
routing do
|
13
|
-
prefix '/clouds/:cloud_id/instances'
|
14
|
-
end
|
15
11
|
|
16
|
-
|
17
|
-
|
12
|
+
prefix '/clouds/:cloud_id/instances'
|
13
|
+
|
14
|
+
trait :authenticated
|
18
15
|
|
16
|
+
action_defaults do
|
19
17
|
requires_ability :read
|
20
18
|
|
21
19
|
params do
|
@@ -31,7 +29,7 @@ module ApiResources
|
|
31
29
|
params do
|
32
30
|
attribute :response_content_type, String, default: 'application/vnd.acme.instance;type=collection'
|
33
31
|
end
|
34
|
-
|
32
|
+
|
35
33
|
headers do
|
36
34
|
# BOTH ARE EQUIVALENT
|
37
35
|
#key "FOO", String, required: true
|
@@ -130,14 +128,14 @@ module ApiResources
|
|
130
128
|
routing do
|
131
129
|
any '/:id/terminate'
|
132
130
|
end
|
133
|
-
|
131
|
+
|
134
132
|
requires_ability :terminate
|
135
133
|
|
136
134
|
params do
|
137
135
|
attribute :id
|
138
136
|
end
|
139
137
|
|
140
|
-
payload do
|
138
|
+
payload do
|
141
139
|
attribute :when, DateTime
|
142
140
|
end
|
143
141
|
|
@@ -175,6 +173,17 @@ module ApiResources
|
|
175
173
|
response :ok
|
176
174
|
end
|
177
175
|
|
176
|
+
|
177
|
+
action :exceptional do
|
178
|
+
routing do
|
179
|
+
prefix '//clouds/:cloud_id/otherinstances'
|
180
|
+
get '/_action/*', except: '*/_action/exceptional'
|
181
|
+
end
|
182
|
+
params do
|
183
|
+
attribute :splat, Attributor::Collection.of(String), required: true
|
184
|
+
end
|
185
|
+
response :ok
|
186
|
+
end
|
178
187
|
# OTHER USAGES:
|
179
188
|
# note: these are all hypothetical, pending, brainstorming usages.
|
180
189
|
|
@@ -1,5 +1,11 @@
|
|
1
1
|
require_relative 'spec_media_types'
|
2
2
|
|
3
|
+
Praxis::ApiDefinition.define do
|
4
|
+
trait :test do
|
5
|
+
description 'testing trait'
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
3
9
|
class PeopleResource
|
4
10
|
include Praxis::ResourceDefinition
|
5
11
|
|
@@ -11,9 +17,9 @@ class PeopleResource
|
|
11
17
|
|
12
18
|
canonical_path :show
|
13
19
|
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
trait :test
|
21
|
+
|
22
|
+
prefix '/people'
|
17
23
|
|
18
24
|
action :index do
|
19
25
|
description 'index description'
|
@@ -34,6 +40,7 @@ class PeopleResource
|
|
34
40
|
|
35
41
|
end
|
36
42
|
|
43
|
+
|
37
44
|
class AddressResource
|
38
45
|
include Praxis::ResourceDefinition
|
39
46
|
|
@@ -43,9 +50,7 @@ class AddressResource
|
|
43
50
|
|
44
51
|
version '1.0'
|
45
52
|
|
46
|
-
|
47
|
-
prefix "/addresses"
|
48
|
-
end
|
53
|
+
prefix '/addresses'
|
49
54
|
|
50
55
|
action :index do
|
51
56
|
description 'index description'
|